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: