Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's English Pages

:

Engineering and troubleshooting by Directory Master!
MCM: Directory

Quick Launch

Ondrej Sevecek's English Pages > Posts > Getting all user accounts whos passwords will expire soon
April 26
Getting all user accounts whos passwords will expire soon

Have you ever had a need to get all the users whos passwords are nearing their expiration? The following script lists all the user accounts whos passwords will expire in a number of days from now - as specified in the $notifyDaysBeforeExpiration input variable.

Note that the script counts with and thus should work even in environments which use the fine grained password policies (also known as granular password policies) technology.

#==============================================
#==  (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

If you also want to send out SMTP email notifications to the users, you can combine it with the following script. Just modify some input variables to adapt to your particular environment:

$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)
}

 

Comments

There are no comments for this post.

Add Comment

Title


You do not need to provide any value this column. It will automatically fill with the name of the article itself.

Author *


Body *


Type the year of the start of the WW1 *


This simple antispam field seems to work well. Just put here the number.

Attachments