Set AD User Account Expiration Date

Sets the expiration date of one or multiple user accounts in AD.
Version 2.1.7
Created on 2023-04-11
Modified on 2023-06-23
Created by Rein Leen
Downloads: 16

The Script Copy Script Copied to clipboard
<#
.SYNOPSIS
    Sets the expiration date of one or multiple user accounts in AD.

.DESCRIPTION
    Sets the expiration date of one or multiple user accounts in AD.

    This script is intended to be used within ControlUp as an action.

.EXAMPLE
    Parameters:
        Users = User1, User2
        Date = 2024-12-31

    Running the ControlUp Action using the above parameters will set the accounts of User1 and User2 to expire on December 31 2024

.EXAMPLE
    Parameters:
        Users = User1, User2@controlup.local
        Date = 2023-06-24 17:00 +3:30

    Running the ControlUp Action using the above parameters will set the accounts of User1 and User2 to expire on June 24 2023 1:30PM UTC (5PM in Tehran, Iran)
    Users can be passed as SamAccountName or UPN and be split with a comma (,) or semicolon (;).

.PARAMETER Users
    User(s) to set the AccountExpirationDate of based on UPN or SamAccountName. 
    Multiple users in string format are supported when split with a (,) or semicolon (;).

.PARAMETER Date
    Date (string) when the account should expire. Use the format yyyy-MM-dd HH:mm K. 
    Time and timeoffset are optional. If time and timeoffset are not set the account will expire at the end of the previous day.

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

#region [parameters]
[CmdletBinding()]
Param (
    [Parameter(Position = 0, Mandatory = $true, HelpMessage = 'User(s) to perform the action on')]
    [string]$Users,
    [Parameter(Position = 1, Mandatory = $true, HelpMessage = 'Date (string) when the account should expire. Use the format yyyy\MM\dd HH:mm K. Time and Timeoffset are optional')]
    [ValidateScript({
        $acceptedFormats = @(
          'yyyy-M-d'
          'yyyy-M-d H:mm'
          'yyyy-M-d H:mm K'
          'yyyy-M-d H:mm z'
          '\"yyyy-M-d H:mm\"'
          '\"yyyy-M-d H:mm K\"'
          '\"yyyy-M-d H:mm z\"'
        ) -as [string[]]
        [datetime]::ParseExact($_, $acceptedFormats, $null, [System.Globalization.DateTimeStyles]::None)
    })]
    [string]$Date
)
#endregion [parameters]

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

# 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 $Date
$Date = Assert-ControlUpParameter -Parameter $Date -Mandatory $true -Engine $controlUpEngine

# Validate $Users
$Users = Assert-ControlUpParameter -Parameter $Users -Mandatory $true -Engine $controlUpEngine
# Split $Users on common delimiters
$splitUsers = $Users.Split(@(',',';')).Trim()

# Active Directory always uses UTC. Therefor convert the datetime to UTC
$acceptedFormats = @(
    'yyyy-M-d'
    'yyyy-M-d H:mm'
    'yyyy-M-d H:mm K'
    'yyyy-M-d H:mm z'
    '\"yyyy-M-d H:mm\"'
    '\"yyyy-M-d H:mm K\"'
    '\"yyyy-M-d H:mm z\"'
) -as [string[]]
$utcDatetime = ([datetime]::ParseExact($Date, $acceptedFormats, $null, [System.Globalization.DateTimeStyles]::None))
#endregion [variables]

#region [actions]
foreach ($user in $splitUsers) {
    $userProperties = [hashtable]@{
        'Name' =                            $null
        'UserPrincipalName' =               $null
        'OriginalAccountExpirationDate' =   $null
        'NewAccountExpirationDate' =        $null
    }
    $userObject = Get-ADUser -LDAPFilter ('(|(UserPrincipalName={0})(SamAccountName={0}))' -f $user) -Properties accountExpires
    if ((-not [string]::IsNullOrWhiteSpace($userObject)) -and ([string]::IsNullOrWhiteSpace($userObject.Enabled))) {
        Write-Warning ('The account for user {0} is disabled. No action will be performed' -f $userObject.Name)
        $userProperties['Name'] = $userObject.Name
        $userProperties['UserPrincipalName'] = $userObject.UserPrincipalName
        $userProperties['OriginalAccountExpirationDate'] = if ($userObject.accountExpires -eq 0) {'Not set'} else {[datetime]::FromFileTime($userObject.accountExpires).ToShortDateString()}
        $userProperties['NewAccountExpirationDate'] = 'Not modified (account is disabled)'
    } elseif (-not [string]::IsNullOrWhiteSpace($userObject)) {
        Write-Verbose ('Found user {0}, setting account expiration date with to {1}' -f $userObject.Name, $Date)
        Set-ADUser -Identity $userObject.DistinguishedName -AccountExpirationDate $utcDatetime
        $userProperties['Name'] = $userObject.Name
        $userProperties['UserPrincipalName'] = $userObject.UserPrincipalName
        $userProperties['OriginalAccountExpirationDate'] = if ($userObject.accountExpires -eq 0) {'Not set'} else {[datetime]::FromFileTime($userObject.accountExpires).ToShortDateString()}
        $userProperties['NewAccountExpirationDate'] = $utcDatetime.ToShortDateString()
    } else {
        Write-Warning ('User {0} could not be found' -f $user)
        continue
    }
    [PSCustomObject]$userProperties
}
#endregion [actions]