Script repository

Users password self-service information

Updated on: Jan 18, 2026, Views: 7921

Password self-service

The scripts email a report containing all users and information about their password self-service enrollment. To execute the script, create a scheduled task configured for the Domain object type and add a managed domain to the Activity Scope. The domain does not determine the users to include into the report and will only be used to execute the scheduled task itself.

Script 1: Report embedded into email body

Parameters

  • $ouDNs - the distinguished names (DNs) of the Organizational Units. Only users from the OUs will be included into the report. For information on how to get the DNs, see Get the DN of a directory object. To search in all the managed domains, set the variable to an empty array.
  • $reportType - the type of report to be generated. Can only be Enrolled or Not enrolled.
  • $to - email addresses of the report recipients.
  • $subject - the email notification subject.
  • $reportHeader - the email notification header.
  • $reportFooter - the email notification footer.
$ouDNs = @("CN=Users,DC=domain,DC=com", "OU=Sales,DC=domain,DC=com") # TODO modify me.
$reportType = "Enrolled" # TODO: modify me

# E-mail settings
$to = "recipient@domain.com" # TODO: modify me
$subject = "Password self-service statistics" # TODO: modify me
$reportHeader = "<b>Password self-service statistics.</b><br/><br/>" # TODO: modify me
$reportFooter = "<hr /><p><i>Please do not reply to this e-mail, it was sent to you for notification purposes only.</i></p>" # TODO: modify me

function GetUserGuids($dn, $list)
{
    $searcher = $Context.BindToObjectByDN($dn)
    $searcher.SearchFilter = "(sAMAccountType=805306368)"
    $searcher.SearchScope = "ADS_SCOPE_SUBTREE"
    $searcher.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
    $searcher.PageSize = 500
    $searcher.SetPropertiesToLoad(@("objectGUID"))
    
    try
    {
        $searchResultIterator = $searcher.ExecuteSearch()
        $searchResults = $searchResultIterator.FetchAll()
        
        $searchResults | %%{[void]$list.Add([Guid]$_.Properties["objectGUID"].Value)}
    }
    finally
    {
        # Release resources
        if ($searchResultIterator){ $searchResultIterator.Dispose() }
    }
}

# Bind to the 'Password Self-Service Statistics' container.
$passwordSelfServiceStatisticsPath = $Context.GetWellKnownContainerPath("PasswordSelfServiceStatistics")
$passwordSelfServiceStatistics = $Context.BindToObject($passwordSelfServiceStatisticsPath)

# Get the enrollment report.
$reportIsBeingGenerated = $True
do
{
    try
    {
        $report = $passwordSelfServiceStatistics.GetReport("ADM_PSSREPORTTYPE_ENROLLMENT")
    }
    catch [System.Runtime.InteropServices.COMException]
    {
        if ($_.Exception.ErrorCode -eq "-2147024875")
        {
            # Report is being generated. Wait 10 seconds.
            Start-Sleep -Seconds 10
            continue
        }
        else
        {
            $reportIsBeingGenerated = $False
            $Context.LogMessage($_.Exception.Message, "Error")
            return
        }
    }
    
    if ($report.GenerateDate -lt [System.Datetime]::UtcNow.AddHours(-1))
    {
        $passwordSelfServiceStatistics.ResetReportCache("ADM_PSSREPORTTYPE_ENROLLMENT")
    }
    else
    {
        $reportIsBeingGenerated = $False
    }
}
while ($reportIsBeingGenerated)

# Get user guids.
$userGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
foreach ($dn in $ouDNs)
{
    GetUserGuids $dn $userGuids
}

# Build the report.
$html = New-Object "System.Text.StringBuilder"
[void]$html.Append($reportHeader)
[void]$html.Append("<table border='1' width='100%%'><tr>")
[void]$html.Append("<th>Name</th>")
[void]$html.Append("<th>Parent</th>")
[void]$html.Append("<th>Enrolled</th>")
[void]$html.Append("<th>Effective Policy</th>")
[void]$html.Append("<th>Date/Time</th>")
[void]$html.Append("<th>Enrollment Invitation</th>")
[void]$html.Append("</tr>")
$records = $report.Records
for ($i = 0; $i -lt $records.Count; $i++)
{
    $record = $records.GetRecord($i)
    
    # Get user information.
    $userPath = $NULL
    $userDisplayName = $NULL
    $userParentCanonicalName = $NULL
    $userAccountIsEnabled = $NULL
    $userIsEnrolled = $NULL
    $userAccountIsExpired = $NULL
    $userInfo = $record.GetUserInfo([ref]$userPath, [ref]$userDisplayName, [ref]$userParentCanonicalName, 
        [ref]$userAccountIsEnabled, [ref]$userIsEnrolled, [ref]$userAccountIsExpired)
    
    $path = New-Object Softerra.Adaxes.Adsi.AdsPath $userPath
    $guid = [Guid]$path.Guid
    
    if ($userGuids.Count -ne 0 -and !$userGuids.Contains($guid))
    {
        continue
    }
    
    if (($reportType -eq "Enrolled" -and !$userIsEnrolled) -or 
        ($reportType -eq "Not enrolled" -and $userIsEnrolled))
    {
        continue
    }
    
    # Get event date.
    $eventDate = $record.EventDate
    if ($eventDate -eq [DateTime]::MinValue)
    {
        $eventDate = $NULL
    }
    
    # Get policy information.
    $policyPath = $NULL
    $policyName = $NULL
    $policyInfo = $record.GetEnrollmentPolicyInfo([ref]$policyPath, [ref]$policyName)
    
    if ($userIsEnrolled)
    {
        $userIsEnrolled = "Yes ($policyName)"
    }
    else
    {
        $userIsEnrolled = "No"
    }
    
    # Get invitation info.
    $successSendDate = New-Object System.Datetime 0
    $errorMessage = $NULL
    $record.GetSendInvitationInfo([ref]$successSendDate, [ref]$errorMessage)
    if ([System.String]::IsNullOrEmpty($errorMessage) -and $successSendDate -ne [Datetime]::MinValue)
    {
        $enrollmentInvitation = $successSendDate
    }
    else
    {
        $enrollmentInvitation = $errorMessage
    }
    
    # Get effective policy information.
    $effectivePolicyPath = $NULL
    $effectivePolicyName = $NULL
    $record.GetEffectivePolicyInfo([ref]$effectivePolicyPath, [ref]$effectivePolicyName)
    
    # Add information to the report.
    [void]$html.Append("<tr><td>$userDisplayName</td><td>$userParentCanonicalName</td><td>$userIsEnrolled</td><td>$effectivePolicyName</td><td>$eventDate</td><td>$enrollmentInvitation</td></tr>")
}

[void]$html.Append("</table>")
[void]$html.Append($reportFooter)

# Send mail
$Context.SendMail($to, $subject, $NULL, $html.ToString())

Script 2: Report attached as CSV

Parameters

  • $ouDNs - the distinguished names (DNs) of the Organizational Units. Only users from the OUs will be included into the report. For information on how to get the DNs, see Get the DN of a directory object. To search in all the managed domains, set the variable to an empty array.
  • $reportType - the type of report to be generated. Can only be Enrolled or Not enrolled.
  • $to - email addresses of the report recipients.
  • $from - the email address from which the notification will be sent.
  • $smtpServer - the SMTP server that will be used to send the notification.
  • $subject - the email notification subject.
  • $reportHeader - the email notification header.
  • $reportFooter - the email notification footer.
  • $csvFilePath - a path to the CSV file that will be temporary created.
  • $removeCSVFile - Set to $true to delete the CSV file after delivery.
$ouDNs = @("CN=Users,DC=domain,DC=com", "OU=Sales,DC=domain,DC=com") # TODO modify me.
$reportType = "Enrolled" # TODO: modify me

# CSV file settings
$csvFilePath = "C:\scripts\report.csv" # TODO: modify me
$removeCSVFile = $True # TODO: modify me

# Email settings
$to = "recipient@domain.com" # TODO: modify me
$from = "noreply@localhost" # TODO: modify me
$smtpServer = "mailserver.domain.com" # TODO: modify me
$subject = "Password Self-Service statistics" # TODO: modify me
$message = "Password Self-Service statistics" # TODO: modify me

function GetUserGuids($dn, $list)
{
    $searcher = $Context.BindToObjectByDN($dn)
    $searcher.SearchFilter = "(sAMAccountType=805306368)"
    $searcher.SearchScope = "ADS_SCOPE_SUBTREE"
    $searcher.PageSize = 500
    $searcher.SetPropertiesToLoad(@("objectGUID"))
    
    try
    {
        $searchResultIterator = $searcher.ExecuteSearch()
        $searchResults = $searchResultIterator.FetchAll()
        
        $searchResults | %%{[void]$list.Add([Guid]$_.Properties["objectGUID"].Value)}
    }
    finally
    {
        # Release resources
        if ($searchResultIterator){ $searchResultIterator.Dispose() }
    }
}

# Bind to the 'Password Self-Service Statistics' container.
$passwordSelfServiceStatisticsPath = $Context.GetWellKnownContainerPath("PasswordSelfServiceStatistics")
$passwordSelfServiceStatistics = $Context.BindToObject($passwordSelfServiceStatisticsPath)

# Get the enrollment report.
$reportIsBeingGenerated = $True
do
{
    try
    {
        $report = $passwordSelfServiceStatistics.GetReport("ADM_PSSREPORTTYPE_ENROLLMENT")
    }
    catch [System.Runtime.InteropServices.COMException]
    {
        if ($_.Exception.ErrorCode -eq "-2147024875")
        {
            # Report is being generated. Wait 10 seconds.
            Start-Sleep -Seconds 10
            continue
        }
        else
        {
            $reportIsBeingGenerated = $False
            $Context.LogMessage($_.Exception.Message, "Error")
            return
        }
    }
    
    if ($report.GenerateDate -lt [System.Datetime]::UtcNow.AddHours(-1))
    {
        $passwordSelfServiceStatistics.ResetReportCache("ADM_PSSREPORTTYPE_ENROLLMENT")
    }
    else
    {
        $reportIsBeingGenerated = $False
    }
}
while ($reportIsBeingGenerated)

# Get user guids.
$userGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
foreach ($dn in $ouDNs)
{
    GetUserGuids $dn $userGuids
}

# Build the report.
$reportRecords = New-Object System.Collections.ArrayList
$records = $report.Records
for ($i = 0; $i -lt $records.Count; $i++)
{
    $record = $records.GetRecord($i)
    
    # Get user information.
    $userPath = $NULL
    $userDisplayName = $NULL
    $userParentCanonicalName = $NULL
    $userAccountIsEnabled = $NULL
    $userIsEnrolled = $NULL
    $userAccountIsExpired = $NULL
    $userInfo = $record.GetUserInfo([ref]$userPath, [ref]$userDisplayName, [ref]$userParentCanonicalName, 
        [ref]$userAccountIsEnabled, [ref]$userIsEnrolled, [ref]$userAccountIsExpired)
    
    $path = New-Object Softerra.Adaxes.Adsi.AdsPath $userPath
    $guid = [Guid]$path.Guid
    
    if ($userGuids.Count -ne 0 -and !$userGuids.Contains($guid))
    {
        continue
    }
    
    if (($reportType -eq "Enrolled" -and !$userIsEnrolled) -or 
        ($reportType -eq "Not enrolled" -and $userIsEnrolled))
    {
        continue
    }
    
    # Get event date.
    $eventDate = $record.EventDate
    if ($eventDate -eq [DateTime]::MinValue)
    {
        $eventDate = $NULL
    }
    
    # Get policy information.
    $policyPath = $NULL
    $policyName = $NULL
    $policyInfo = $record.GetEnrollmentPolicyInfo([ref]$policyPath, [ref]$policyName)
    
    if ($userIsEnrolled)
    {
        $userIsEnrolled = "Yes ($policyName)"
    }
    else
    {
        $userIsEnrolled = "No"
    }
    
    # Get invitation info.
    $successSendDate = New-Object System.Datetime 0
    $errorMessage = $NULL
    $record.GetSendInvitationInfo([ref]$successSendDate, [ref]$errorMessage)
    if ([System.String]::IsNullOrEmpty($errorMessage) -and $successSendDate -ne [Datetime]::MinValue)
    {
        $enrollmentInvitation = $successSendDate
    }
    else
    {
        $enrollmentInvitation = $errorMessage
    }
    
    # Get effective policy information.
    $effectivePolicyPath = $NULL
    $effectivePolicyName = $NULL
    $record.GetEffectivePolicyInfo([ref]$effectivePolicyPath, [ref]$effectivePolicyName)
    
    # Add information to the report.
    $reportRecord = [PSCustomObject][ordered]@{
        "Name" = $userDisplayName
        "Parent" = $userParentCanonicalName
        "Enrolled" = $userIsEnrolled
        "Effective Policy" = $effectivePolicyName
        "Date/Time" = $eventDate
        "Enrollment Invitation" = $enrollmentInvitation
    }
    $reportRecords.Add($reportRecord)
}

# Export to CSV.
$reportRecords | Sort-Object @sortParameters | Export-csv -NoTypeInformation -Path $csvFilePath

# Send mail 
Send-MailMessage -To $to -from $from -SmtpServer $smtpServer -Subject $subject -Body $message -Attachments $csvFilePath

if ($removeCSVFile)
{
    # Remove temporary file.
    Remove-Item $csvFilePath -Force
}

Comments 3

You must be signed in to comment.

  • Andrew Hancock

    Andrew Hancock

    I'm not that great with Powershell and this worked great for us after a little tweaking to fit our environment.
    Thanks!

  • Ben

    Ben

    This is great, but can you modify it to instead output an excel file or .csv?

    • Support

      Support

      Hello Ben,

      We added the script that generates the report as a CSV file and sends it as attachment via email.

Got questions?

Support Questions & Answers

We use cookies to improve your experience.
By your continued use of this site you accept such use.
For more details please see our privacy policy and cookies policy.