<< Back to Script Library

Get AD User Forced to Change Password

Lists all users in Active Directory with "User must change password at next logon" selected
Version: 2.1.6
Created: 2023-04-11
Modified: 2023-06-23
Creator: Rein Leen
Downloads: 16
Tags: Active Directory
The Script Copy Script Copied to clipboard
<#
.SYNOPSIS
    Lists all users in Active Directory with "User must change password at next logon" selected.

.DESCRIPTION
    Lists all users in Active Directory with "User must change password at next logon" selected.
    Not getting any results means there are no user accounts with the setting active within the given parameters.

    This script is intended to be used within ControlUp as an action. If the .NET engine is used all optional parameter are truly optional.
    The classic engine cannot handle empty optional parameters. If the .NET engine is not used pass "none" to leave an optional parameter empty.

.EXAMPLE
    Parameters:
        DaysBeforeAccountExpiration = 30
        SearchBases = none
        Users = none
        IncludeDisabledUserAccounts = none

    Running the ControlUp Action using the above parameters retrieves all active user accounts that are due to expire within the next 30 days.

.PARAMETER IncludeDisabledUserAccounts
    If True, disabled user accounts will be included in the results.

.PARAMETER Searchbases
    The distinguished names of the OUs to search in. Ignored if Users is specified. Multiple OU's in string format are supported when split with a semicolon (;).

.NOTES
    Author: 
        Rein Leen
    Contributor(s):
        Bill Powell
        Gillian Stravers
    Context: 
        Machine
    Modification_history:
        Rein Leen       23-05-2023      Version ready for release
#>

#region [parameters]
[CmdletBinding()]
Param (
    [Parameter(Position = 0, Mandatory = $false, HelpMessage = 'Include disabled user accounts (True), or exclude them (False)')]
    [string]$IncludeDisabledUserAccounts,
    # A valid Distinguished name always contains two domainComponents.
    [Parameter(Position = 1, Mandatory = $false, HelpMessage = "The distinguished names of the OUs to search in. Multiple OU's in string format are supported when split with a semicolon.")]
    [string]$Searchbases    
)
#endregion [parameters]

#region [prerequisites]
# Required dependencies
#Requires -Version 5.1
#Requires -Modules ActiveDirectory
#Requires -RunAsAdministrator

# Import modules (required in .NET engine)
Import-Module -Name ActiveDirectory

#region ControlUpScriptingStandards
$VerbosePreference = $(if( $PSBoundParameters[ 'verbose' ] ) { $VerbosePreference } else { 'SilentlyContinue' })
$DebugPreference = $(if( $PSBoundParameters[ 'debug' ] ) { $DebugPreference } else { 'SilentlyContinue' })
$ErrorActionPreference = $(if( $PSBoundParameters[ 'erroraction' ] ) { $ErrorActionPreference } else { 'Stop' })
$ProgressPreference = 'SilentlyContinue'

[int]$outputWidth = 400
if( ( $PSWindow = (Get-Host).UI.RawUI ) -and ( $WideDimensions = $PSWindow.BufferSize ) )
{
    $WideDimensions.Width = $outputWidth
    $PSWindow.BufferSize = $WideDimensions
}
#endregion ControlUpScriptingStandards

#region [functions]
# Function to get the ControlUp engine under which the script is running.
function Get-ControlUpEngine {
    $runtimeEngine = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $PID"
    switch ($runtimeEngine.ProcessName) {
        'cuAgent.exe' {
                return '.NET'
            }
        'powershell.exe' {
                return 'Classic'
            }
    }
}

# Function to assert the parameters are correct
function Assert-ControlUpParameter {
    param (
        [Parameter(Position = 0, Mandatory = $false)]
        [object]$Parameter,
        [Parameter(Position = 1, Mandatory = $true)]
        [boolean]$Mandatory,
        [Parameter(Position = 2, Mandatory = $true)]
        [ValidateSet('.NET','Classic')]
        [string]$Engine
    )

    # If a parameter is optional passing using a hyphen (-) or none is required when using the Classic engine. If this is the case return $null.
    if (($Mandatory -eq $false) -and (($Parameter -eq '-') -or ($Parameter -eq 'none'))) {
        return $null
    }

    # If a parameter is optional when using the .NET engine it should be empty. if this is the case return $null.
    if (($Engine -eq '.NET') -and ($Mandatory -eq $false) -and ([string]::IsNullOrWhiteSpace($Parameter))) {
        return $null
    }

    # Check if a mandatory parameter isn't null
    if (($Mandatory -eq $true) -and ([string]::IsNullOrWhiteSpace($Parameter))) {
        throw [System.ArgumentException] 'This parameter cannot be empty'
    }

    # ControlUp can add double quotes when using the .NET engine when a parameter value contains spaces. Remove these.
    if ($Engine -eq '.NET') {
        # Regex used to match double quotes
        $possiblyQuotedStringRegex = '^(?<op>"{0,1})\b(?<text>[^"]*)\1$'
        $Parameter -match $possiblyQuotedStringRegex | Out-Null
        return $Matches.text
    } else {
        return $Parameter
    }
}
#endregion [functions]

#region [variables]
$controlUpEngine = Get-ControlUpEngine

# Validate $IncludeDisabledUserAccounts
$IncludeDisabledUserAccounts = Assert-ControlUpParameter -Parameter $IncludeDisabledUserAccounts -Mandatory $false -Engine $controlUpEngine
# Convert string to boolean or set default
if (-not [string]::IsNullOrWhiteSpace($IncludeDisabledUserAccounts)) {
    [bool]$includeDisabled = [System.Convert]::ToBoolean($IncludeDisabledUserAccounts)
} else {
    [bool]$includeDisabled = $false
}

# Validate $Searchbases
$Searchbases = Assert-ControlUpParameter -Parameter $Searchbases -Mandatory $false -Engine $controlUpEngine
# Split $SeachBases on ";"
if (-not [string]::IsNullOrWhiteSpace($SearchBases)){
    $splitSearchBases = $SearchBases.Split(';').Trim()
}
#endregion [variables]

#region [actions]
Write-Verbose ('Starting actions')
if ([string]::IsNullOrWhiteSpace($Searchbases)) {
    $splitSearchBases = @((Get-ADDomain).DistinguishedName)
    Write-Verbose ('No searchbase included, querying entire directory on "{0}"' -f $Searchbases[0])
}

$ADPropertyList = "Name,UserPrincipalName,DistinguishedName,CN,Path" -split ','

$splitSearchBases | ForEach-Object {
    $searchbase = $_            
    try {
        if ($includeDisabled -eq $true) {
            $users = Get-ADUser -Filter * -SearchBase $searchbase -Properties cn,pwdlastset | Where-Object { ($_.pwdlastset -eq 0) -and (-not [string]::IsNullOrWhiteSpace($_.UserPrincipalName)) } | Select-Object  $ADPropertyList           
        } else {
            $users = Get-ADUser -Filter 'enabled -eq $true' -SearchBase $searchbase -Properties cn,pwdlastset | Where-Object { ($_.pwdlastset -eq 0) -and (-not [string]::IsNullOrWhiteSpace($_.UserPrincipalName)) } | Select-Object  $ADPropertyList 
        }
        # Return found users
        Write-Verbose ('Found {0} users' -f $users.Count)
        $users | ForEach-Object {
            $user = $_
            $cn = "CN=" + $user.CN
            if ($user.DistinguishedName.StartsWith($cn)) {
                $user.CN = $cn
                $TrimLength = $cn.Length + 1
                $user.Path = $user.DistinguishedName.Substring($TrimLength, $user.DistinguishedName.Length - $TrimLength)
            }
            $user
        }
    } catch [System.ArgumentException], [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
        Write-Warning ('Searchbase {0} not found' -f $searchbase)
    } catch {
        Write-Warning ('Unknown error on searchbase {0}' -f $searchbase)
    }
} | Sort-Object -Property Path,CN | Format-Table -Property Name,UserPrincipalName,CN,Path -AutoSize

Write-Verbose ('Finished actions')

#endregion [actions]