| Abych se nezpronevěřoval své dobré tradici psát tu česky, tak to ještě přepíšu i do češtiny.
Nepotřebovali jste někdy vypsat uživatele, kterým už brzo vyprší (expiruje) heslo? Tak tenhle skript to dělá. Vypíše uživatele, jimž vyprší heslo za několik dnů - za tolik, kolik zadáte do proměnné $notifyDaysBeforeExpiration. Jenže to není tak jednoduché. Protože existuje technologie fine grained password policies (neboli granular password policies), tak se nám to trošku komplikuje. On ten skript potom neumí sám zjistit jaké účty má "předhledat" jako "podezřelé". Takže ještě musíte vyplnit také proměnnou $passwordExpiration.
Buď do ní dejte vaši skutečnou hodnotu v počtu dnů, po kolika normálně lidem heslo vyprší - výchozí ve Windows je 42 dnů. Pokud se to hodně liší na různých účtech, dejte tam tu nejvyšší hodnotu. A pokud nevíte, dejte tam prostě nějakou velkou hodnotu, nad kterou vás to už nezajímá. Pokud tam dáte třeba 365, bude to znamena, že "předhledáváme" účty, kterým vyprší (expiruje) heslo obvykle za 1 rok, nebo dříve.
#==============================================
#== (C) Ondrej Sevecek, 2013 ================
#== ondrej@sevecek.com ================
#== www.sevecek.com ================
#==============================================
#==============================================
#== Input parameters ========================
#==============================================
# Specify a distinguished name path to the AD container in which you whish to search for password expiration adepts
$searchRootDN = 'OU=People,OU=Company,DC=gopas,DC=virtual'
# Specify number of days which is common for a password to expire
# Note that there are two password expiration policies possible. You can either use the domain-wide group policy
# based password policy which applies to all domain users. Or if your domain is running with at least
# domain functional level (DFL) Windows 2008, you may implement the fine grained (granular) password policies
# which apply to some groups or even individual users only.
# Because of this complexinnes, the script cannot determine the number of days automatically. The script must
# first search for all adept users whos passwords may expire in the days specified and only then it checks
# the real expiration date from every such suspect user's properties (msDS-UserPasswordExpiryTimeComputed attribute)
# The problem with the msDS-UserPasswordExpiryTimeComputed is that the attribute is a computed (constructed)
# attribute and AD LDAP cannot directly search for computed (constructed) attributes
$passwordExpiration = 42
# This is the number of days before expiration that you consider critical to notify the users
$notifyDaysBeforeExpiration = 5
#==============================================
#== The code goes here ======================
#==============================================
function Convert-Int64Part ([object] $comLargeInt, [string] $part)
{
$intOrNull = $null
if ($comLargeInt -ne $null) {
$intOrNull = $comLargeInt.GetType().InvokeMember($part, [System.Reflection.BindingFlags]::GetProperty, $null, $comLargeInt, $null)
}
return $intOrNull
}
function Convert-LargeInt ([object] $comLargeInt)
{
$intOrNull = $null
if ($comLargeInt -ne $null) {
$highPart = $null
$lowPart = $null
$highPart = Convert-Int64Part $comLargeInt "HighPart"
$lowPart = Convert-Int64Part $comLargeInt "LowPart"
if (($highPart -ne $null) -and ($lowPart -ne $null)) {
$bytes = [System.BitConverter]::GetBytes($highPart)
$byteArray = [System.Byte[]] @(0,0,0,0,0,0,0,0)
[System.Array]::Copy($bytes, 0, $byteArray, 4, 4)
$highPart = [System.BitConverter]::ToInt64($byteArray, 0)
$bytes = [System.BitConverter]::GetBytes($lowPart)
$lowPart = [System.BitConverter]::ToUInt32($bytes, 0)
$intOrNull = $lowPart + $highPart
}
}
return $intOrNull
}
$criticalAge = $passwordExpiration - $notifyDaysBeforeExpiration
$criticalTicks = ((Get-Date).AddDays(-$criticalAge) - [DateTime]::Parse('1601-01-01')).Ticks
$criticalTicksFuture = ((Get-Date).AddDays($notifyDaysBeforeExpiration) - [DateTime]::Parse('1601-01-01')).Ticks
# Get all suspect users whos passwords would expire in the "common" password expiration period
# We cannot directly search for computed (constructed) msDS-UserPasswordExpiryTimeComputed attribute
$expiringFilter = '(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=65536)(pwdLastSet<={0}))' -f $criticalTicks
$adepts = dsquery * $searchRootDN -filter $expiringFilter -limit 0 | % {
# Determine if the suspect user's password really expires from its msDS-UserPasswordExpiryTimeComputed attribute
# The attribute is actually a computed (constructed) attribute and cannot be searched for directly
$oneDN = $_.Trim('"')
$adeptUser = [ADSI] ('LDAP://{0}' -f $oneDN)
$adeptUser.RefreshCache('msDS-UserPasswordExpiryTimeComputed')
$actualExpiration = Convert-LargeInt $adeptUser.'msDS-UserPasswordExpiryTimeComputed'.Value
if ($actualExpiration -ne $null) {
$actualExpirationDT = [DateTime]::FromFileTime($actualExpiration)
$actualExpirationDateTime = $actualExpirationDT.ToString('s')
$actualExpirationDays = ($actualExpirationDT - (Get-Date)).Days
} else {
$actualExpirationDateTime = 'none'
$actualExpirationDays = $null
}
# Here go all the suspect users. If their password is really critical now, the .critical member will
# contain true. Otherwise the list contains also the users whos passwords don't really expire in the critical
# period just for debug purposes.
$oneUser = New-Object PSObject
$oneUser | Add-Member -MemberType NoteProperty -Name displayName -Value ([string] $adeptUser.displayName.Value)
$oneUser | Add-Member -MemberType NoteProperty -Name email -Value ([string] $adeptUser.mail.Value)
$oneUser | Add-Member -MemberType NoteProperty -Name expiration -Value $actualExpirationDateTime
$oneUser | Add-Member -MemberType NoteProperty -Name daysRemaining -Value $actualExpirationDays
$oneUser | Add-Member -MemberType NoteProperty -Name critical -Value ($actualExpiration -le $criticalTicksFuture)
$oneUser | Add-Member -MemberType NoteProperty -Name dn -Value $oneDN
$oneUser
}
Write-Host 'All suspect users found, outup only for debug purposes:'
Write-Host ('Only those with .critical member with $true value will actually expire in {0} days' -f $notifyDaysBeforeExpiration)
$adepts
Write-Host ('All the users whos passwords will expire in {0} days' -f $notifyDaysBeforeExpiration)
$adepts | ? { $_.critical -eq $true } | ft displayName, email, expiration, daysRemaining
A následující doplňek těm lidem, pokud tedy mají emailovou adresu, pošle rovnou i zprávu, že si to heslo mají pozměnit. Zase je potřeba trošku aktualizovat vstupní proměnné, ale to je už jen maličkost:
$adepts | ? { ($_.critical -eq $true) -and ($_.email -ne '') -and ($_.email -ne $null) } | % {
#=============================================
# Input parameters here please:
$serverSMTP = 'smtp.gopas.virtual'
$fromField = 'pwd-expiration@gopas.cz'
$subjectField = 'Your password will expire soon'
$body = 'Dear {0}, your password will expire in {1} days. Please change it before it expires.'
#=============================================
# The code follows
$finalBody = $body -f $_.displayName, $_.daysRemaining
$smtpServer = New-Object Net.Mail.SmtpClient($serverSMTP)
$smtpServer.Send($fromField, $_.email, $subjectField, $finalBody)
}
|