Knowledge Base

Our dedicated Product Specialist team is always ready to help you when you need it the most. Contact Support

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:

Email Report Sample

Publication date: February 1, 2024
Modification date: February 1, 2024

Was this article helpful?

Related Articles