Automating Password Policy and Password Auditor Reports
The following PowerShell script contains a sample implementation of how Specops Password Policy and Breached Password Protection customers can automate sending an email to administrators containing the results of Password Policy Periodic Scanning, a list of any users found to have breached passwords, and a Password Auditor report PDF. Customers can set a script like this to run as a scheduled task that would then generate and send the email on a regular basis.
This code is provided as an example only; at a minimum customers will need to adjust the $outFolder and $mailSettings values as appropriate for their environemnt. Specops makes no guarantees that this code will work in all customer environments without additional modifications.
#Requires -PSEdition Desktop
#Requires -Modules ActiveDirectory,Specops.SpecopsPasswordPolicy
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'SilentlyContinue'
$now = Get-Date
#Define environment
$outFolder = 'C:\temp'
$mailSettings = @{
'SmtpServer' = 'mail.contoso.com'
'Port' = 25
'Subject' = "Specops Password Policy Report for $($now.tostring('yyyy-MM-dd'))"
'From' = 'noreply@contoso.com'
'To' = @('admin1@contoso.com','admin2@contoso.com')
}
### Some More Basic Setup
$head = '<style>
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
td {
border: 1px solid;
text-align: left;
padding-left: 5px;
}
</style>'
$reports = @()
###Ggenerate SPA PDF
$spaReport = New-SpaReport -OutputDirectory $outFolder -Verbose 4> $outFolder\spaReportLog.txt
$reports += $spaReport
###Build message body -- HTML formatted Periodic Scanning results
$result = Get-SppPeriodicScanningResult
if ($result.endtime -gt $(get-date).adddays(-1)) {
$messageBody = [pscustomobject]@{
'Loaded from domain controller'=$($(Get-SppPeriodicScanning).DomainControllerUsedForPeriodicScanning);
'Start time (UTC)' = $result.StartTime.ToUniversalTime();
'End time (UTC)' = $result.EndTime.ToUniversalTime();
'Processed Accounts' = $result.TotalAccountsProcessed;
'Accounts failed to process' = $result.NumberOfAccountsFailedToProcess;
'Run mode' = $result.StartMode
} | ConvertTo-Html -as list -fragment | % {$_ -replace ':</td>','</td>' }
} else {
$messageBody = "<p>No recent periodic scanning results found. Last periodic scan ran at $($result.endtime)</p>"
}
#Use this table to make pretty headers for each processor result
$processIdNames = @{
'License' = 'License Counting';
'Expiration' = 'Password Expiration';
'BreachedPasswords' = 'Breached Password Protection Express';
'BreachedPasswordsCloud' = 'Breached Password Protection Complete'
}
#Build table for each processor result (excluding SubObject, which is also hidden in SPP Domain Administration)
foreach ($processorResult in $result.ProcessorResults) {
if ($processorResult.ProcessId -ne 'SubObject') {
# Header, translating names as we go
$messageBody += "<H2>$($processIdNames[$processorResult.ProcessId.ToString()])</H2>"
# Results table with table headers row that ConvertTo-Html creates removed afterwards
$processorResult.stats | ConvertTo-Html -property name,value -as table -fragment |
% { $_ -Replace '<tr><th>Name</th><th>Value</th></tr>','' } | % {$messageBody += $_}
}
}
if ($result.HasUserDetails) {
$resultUsers = Get-SppPeriodicScanningResultUsers
$resultUsersFile = "$outFolder\bppusersreport_$($result.EndTime.ToString('yyyy-MM-dd.HHmm')).csv"
$userReport = @()
foreach ($user in $resultusers) {
$userObject = get-aduser $user.ObjectGuid -Properties passwordLastSet,lastLogonTimestamp,mail
$policy = Get-PasswordPolicyAffectingUser $userObject.SamAccountName
$passwordExpiry = Get-SppPasswordExpiration $userObject.SamAccountName
if ($userObject.$passwordLastSet) {
$passwordLastSet = "$($(New-TimeSpan $userObject.PasswordLastSet $now).days) days ago"
} else {
$passwordLastSet = 'User must change password at next logon'
}
if ($userObject.lastLogonTimestamp -gt 0) {
$lastLogon = [datetime]::FromFileTime($userObject.lastLogonTimestamp).ToUniversalTime()
} else {
$lastLogon = '(never)'
}
if ($passwordExpiry.PasswordExpirationUtc) {
$passwordExpiresIn = "$($(New-TimeSpan $now $passwordExpiry.PasswordExpirationUtc).days) days"
} else {
$passwordExpiresIn = '(never)'
}
if ($user.PasswordFoundInExpressList) {
$resultType = 'ExpressList'
}
if ($user.PasswordFoundInCompleteList) {
$resultType = 'CompleteAPI'
}
$reportEntry = [psCustomObject]@{
Account = $userObject.Name
SamAccountName = $userObject.SamAccountName
Email = $userObject.mail
DistinguishedName = $userObject.DistinguishedName
LastLogon = $lastLogon
PasswordChanged = $passwordLastSet
TimeUntilPasswordExpires = $passwordExpiresIn
PasswordPolicy = $policy.GpoName
ResultType = $resultType
}
$userReport += $reportEntry
}
$userReport | Export-Csv $resultUsersFile -Encoding UTF8 -NoTypeInformation
$reports += $resultUsersFile
}
# Compile the final message. Remove empty table created by ConvertTo-Html as we go
$message = (ConvertTo-Html -head $head -Body $messageBody | out-string) -replace "(?sm)<table>\s+</table>"
Send-MailMessage @mailSettings -Body $message -BodyAsHtml -Attachments $reports -Encoding UTF8
Sample output:
