Public/ActiveDirectory.ps1
|
# Nebula.Tools: Active Directory ==================================================================================================================== function Find-ADAccountExpirations { <# .SYNOPSIS Finds Active Directory users with an account expiration date and optionally extends it. .DESCRIPTION Searches AD for users that have a valid account expiration value set. By default, only enabled accounts are returned; use -IncludeDisabled to include disabled accounts. You can filter by expiration date (TargetDate) and/or by email domain (FilterDomain), export results to CSV, and, if requested, extend the account expiration date. Output is objects by default (pipeline-friendly). Use -AsTable for formatted display. Use -WhatIf to simulate the extension without applying changes. .OUTPUTS PSCustomObject with: Name, SamAccountName, Email, AccountExpirationUTC. When -AsTable is used, output is formatted for display and not ideal for further pipeline use. .PARAMETER TargetDate Reference expiration date (string) in "yyyy-MM-dd" format or any DateTime-compatible format. If provided, the function matches accounts expiring on or before this date. Use -ExactDate to match only accounts expiring exactly on this date. If TargetDate is not specified and FilterDomain is not set, the function throws an error. .PARAMETER FilterDomain Filter on the user's email (e.g., "@contoso.com" or "contoso.com"). The filter is treated as a wildcard and special characters are escaped. Matching is done against the Mail attribute and is case-insensitive. .PARAMETER ExactDate If present, match only accounts expiring exactly on TargetDate (same date). By default TargetDate matches expirations on or before the date. .PARAMETER ExportCsv If present, exports results to a CSV file in ExportPath (or current directory). The CSV includes additional fields: Department and Company. .PARAMETER ExportPath Output folder for the CSV export. Defaults to the current location. The file name is "AD_Users_Expires_<yyyy-MM-dd>.csv" or "AD_Users_Expires_ALL-DATES.csv". .PARAMETER AsTable If present, formats results as a table for display. When used, output is no longer a clean object stream for further pipeline processing. .PARAMETER IncludeDisabled If present, includes disabled accounts. By default, only enabled accounts are returned. .PARAMETER ExtendExpiration If present, extends the expiration for the matched accounts to the date provided in ExtendTo. The function uses ShouldProcess; use -WhatIf to preview changes safely. .PARAMETER ExtendTo New expiration date (string) to apply when using -ExtendExpiration. Must be a valid DateTime-compatible format. .PARAMETER TargetServer Server/Domain Controller used for AD queries and updates. If omitted, the default domain controller is used. .EXAMPLE Find-ADAccountExpirations -TargetDate "2027-01-01" Returns enabled accounts that expire on or before January 1, 2027. .EXAMPLE Find-ADAccountExpirations -FilterDomain "@contoso.com" -ExportCsv Finds enabled accounts with email in contoso.com and exports to CSV. .EXAMPLE Find-ADAccountExpirations -TargetDate "2027-01-01" -ExactDate Finds accounts expiring exactly on January 1, 2027. .EXAMPLE Find-ADAccountExpirations -FilterDomain "contoso.com" -IncludeDisabled -AsTable Includes disabled accounts and formats output as a table. .EXAMPLE Find-ADAccountExpirations -TargetDate "2027-01-01" -ExtendExpiration -ExtendTo "2027-12-31" -WhatIf Previews extending expiration to December 31, 2027 for matching accounts. .EXAMPLE Find-ADAccountExpirations -TargetServer "dc01.contoso.com" -FilterDomain "@contoso.com" | Select-Object -ExpandProperty Name Outputs only the Name values by keeping object output (no -AsTable). .LINK https://kb.gioxx.org/Nebula/Tools/usage/active-directory#find-adaccountexpirations .NOTES Requirements: ActiveDirectory module (RSAT), permissions to read/modify AD users. Accounts with no expiration, or invalid/zero file time values are excluded. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( # Accept dates as strings to avoid PowerShell interpreting 2027-01-01 as arithmetic [string]$TargetDate, [string]$FilterDomain, [switch]$ExactDate, [switch]$ExportCsv, [string]$ExportPath, [switch]$AsTable, [switch]$IncludeDisabled, [switch]$ExtendExpiration, [string]$ExtendTo, [string]$TargetServer ) try { Import-Module ActiveDirectory -ErrorAction Stop | Out-Null } catch { throw "ActiveDirectory module not available. Install RSAT or the AD module first." } $hasTargetDate = -not [string]::IsNullOrWhiteSpace($TargetDate) $hasExtendTo = -not [string]::IsNullOrWhiteSpace($ExtendTo) if ([string]::IsNullOrWhiteSpace($FilterDomain) -and -not $hasTargetDate) { throw "TargetDate is required when FilterDomain is not specified." } $TargetDateDT = $null if ($hasTargetDate) { try { $TargetDateDT = [DateTime]::Parse($TargetDate) } catch { throw "TargetDate is not a valid date: $TargetDate" } } $ExtendToDT = $null if ($ExtendExpiration) { if (-not $hasExtendTo) { throw "ExtendTo is required when using -ExtendExpiration." } try { $ExtendToDT = [DateTime]::Parse($ExtendTo) } catch { throw "ExtendTo is not a valid date: $ExtendTo" } } $csvLabel = if ($hasTargetDate) { $TargetDateDT.ToString('yyyy-MM-dd') } else { "ALL-DATES" } $exportRoot = if ($ExportPath) { $ExportPath } else { (Get-Location).Path } $csvPath = Join-Path $exportRoot "AD_Users_Expires_$csvLabel.csv" # Escape wildcard characters so the user can pass "@contoso.com" safely $emailPattern = $null if (-not [string]::IsNullOrWhiteSpace($FilterDomain)) { $emailPattern = '*' + [System.Management.Automation.WildcardPattern]::Escape($FilterDomain.Trim()) + '*' } $adProperties = @('accountExpires', 'mail') if ($ExportCsv) { $adProperties += 'department', 'company' } $adFilter = if ($IncludeDisabled) { '*' } else { 'Enabled -eq $true' } $adParams = @{ Filter = $adFilter Properties = $adProperties } if (-not [string]::IsNullOrWhiteSpace($TargetServer)) { $adParams['Server'] = $TargetServer } $rawResults = Get-ADUser @adParams | Where-Object { $fileTime = $_.accountExpires if (-not $fileTime) { return $false } if ($fileTime -eq 0 -or $fileTime -eq 9223372036854775807) { return $false } try { $expirationDate = ([DateTime]::FromFileTimeUtc([Int64]$fileTime)).Date } catch { return $false } ( -not $hasTargetDate -or ( ($ExactDate -and $expirationDate -eq $TargetDateDT.Date) -or (-not $ExactDate -and $expirationDate -le $TargetDateDT.Date) ) ) -and ( -not $emailPattern -or ( -not [string]::IsNullOrWhiteSpace($_.mail) -and $_.mail -like $emailPattern ) ) } | Select-Object ` Name, SamAccountName, DistinguishedName, mail, department, company, accountExpires $rawResults = @($rawResults) $results = $rawResults | Select-Object ` Name, SamAccountName, DistinguishedName, @{ n = 'Email'; e = { $_.mail } }, @{ n = 'AccountExpirationUTC'; e = { try { [DateTime]::FromFileTimeUtc([Int64]$_.accountExpires) } catch { $null } } }, @{ n = 'AccountExpirationLocal'; e = { try { [DateTime]::FromFileTime([Int64]$_.accountExpires) } catch { $null } } } $results = @($results) $displayResults = $results | Select-Object Name, SamAccountName, Email, AccountExpirationUTC | Sort-Object AccountExpirationUTC if ($AsTable) { $displayResults | Format-Table -AutoSize } else { $displayResults } if ($ExportCsv) { if (-not (Test-Path -Path $exportRoot)) { throw "ExportPath does not exist: $exportRoot" } if (Test-Path $csvPath) { Write-Information "$csvPath already exists. Deleting it first ..." -InformationAction Continue Remove-Item -Path $csvPath -Force } $exportResults = $rawResults | Select-Object ` Name, SamAccountName, DistinguishedName, @{ n = 'Email'; e = { $_.mail } }, @{ n = 'Department'; e = { $_.department } }, @{ n = 'Company'; e = { $_.company } }, @{ n = 'AccountExpirationUTC'; e = { [DateTime]::FromFileTimeUtc($_.accountExpires) } }, @{ n = 'AccountExpirationLocal'; e = { [DateTime]::FromFileTime($_.accountExpires) } } $exportResults | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 Write-Information "CSV exported to $csvPath" -InformationAction Continue } if ($ExtendExpiration) { Write-Information "" -InformationAction Continue Write-Information "About to set account expiration for $($results.Count) user(s) to:" -InformationAction Continue Write-Information " $ExtendToDT" -InformationAction Continue Write-Information "Tip: use -WhatIf to preview safely." -InformationAction Continue Write-Information "" -InformationAction Continue foreach ($u in $results) { $label = "$($u.SamAccountName) ($($u.Email))" if ($PSCmdlet.ShouldProcess($label, "Set account expiration to $ExtendToDT")) { $setParams = @{ Identity = $u.SamAccountName DateTime = $ExtendToDT } if (-not [string]::IsNullOrWhiteSpace($TargetServer)) { $setParams['Server'] = $TargetServer } Set-ADAccountExpiration @setParams } } Write-Information "" -InformationAction Continue Write-Information "Done." -InformationAction Continue } } |