In diagnosing a machine lockout issue I wanted to find the time that:
- Machine Password was last set in Active Directory
- Machine Password was last set locally
By default when a machine has a healthy connection to Active Directory you should see the following:
- Local and Active Directory Times should match (May be out by a few seconds)
- The time should not be older than 30 days (Unless default max. password age has been modified from 30 days)
The script can be downloaded here, instructions for use contained in the script.
# Get-MachinePasswordLastSetDate by Malcolm McCaffery (@chentiangemalc) # https://chentiangemalc.wordpress.com # # # Retrieves the date/time Windows machine account was last set # # For Location LocalMachine = Must run as local Administrator # For Location ActiveDirectory = Must run as, or specify credential, with appropriate Active Directory access # # Usage examples: # # Get-MachinePasswordLastSetDate -Location ActiveDirectory # Get-MachinePasswordLastSetDate -Location LocalMachine # Get-MachinePasswordLastSetData -Location ActiveDirectory -ComputerName server01 # Get-MachinePasswordLastSetData -Location ActiveDirectory -ComputerName server01 -Server DC02 # Get-MachinePasswordLastSetData -Location ActiveDirectory -ComputerName server01 -Server DC02 -Domain other.domain.local -Credential (Get-Credential) Set-StrictMode -Version Latest Add-Type -TypeDefinition @' public enum AccountLocation { ActiveDirectory, LocalMachine } '@ Function Get-MachinePasswordLastSetDate { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] [AccountLocation]$Location ) DynamicParam { $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary if ($Location -eq 'ActiveDirectory') { # COMPUTERNAME PARAMETER $computerAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] $computernameAttribute = New-Object System.Management.Automation.ParameterAttribute $computernameAttribute.Mandatory = $false $computernameAttribute.HelpMessage = "The name of computer object to search for in Active Directory" $computerAttributeCollection.Add($computernameAttribute) #add our paramater specifying the attribute collection $computernameParam = New-Object System.Management.Automation.RuntimeDefinedParameter('ComputerName', [string], $computerAttributeCollection) #expose the name of our parameter $paramDictionary.Add('ComputerName', $computernameParam) # CREDENTIAL Parameter $credentialAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] $credentialAttribute = New-Object System.Management.Automation.ParameterAttribute $credentialAttribute.Mandatory = $false $credentialAttribute.HelpMessage = "Specifies a user account that has permission to perform this action. The default is the current user." $credentialAttributeCollection.Add($credentialAttribute) #add our paramater specifying the attribute collection $credentialParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [PSCredential], $credentialAttributeCollection) #expose the name of our parameter $paramDictionary.Add('Credential', $credentialParam) # SERVER Parameter $serverAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] $serverAttribute = New-Object System.Management.Automation.ParameterAttribute $serverAttribute.Mandatory = $false $serverAttribute.HelpMessage = "Specifies the domain controller to use." $serverAttributeCollection.Add($serverAttribute) #add our paramater specifying the attribute collection $serverParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Server', [String], $serverAttributeCollection) #expose the name of our parameter $paramDictionary.Add('Server', $serverParam) # DOMAIN parameter $domainAttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] $domainAttribute = New-Object System.Management.Automation.ParameterAttribute $domainAttribute.Mandatory = $false $domainAttribute.HelpMessage = "Specifies the FQDN of domain to use." $domainAttributeCollection.Add($domainAttribute) #add our paramater specifying the attribute collection $domainParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Domain', [String], $domainAttributeCollection) #expose the name of our parameter $paramDictionary.Add('Domain', $domainParam) } return $paramDictionary } Process { $result = $null switch ($Location) { "ActiveDirectory" { $computerName = $paramDictionary["ComputerName"].Value $domain = $paramDictionary["Domain"].Value $credential = $paramDictionary["Credential"].Value $server = $paramDictionary["Server"].Value if ($computerName -eq $null) { $computerName = $env:COMPUTERNAME } # EASY WAY #$computer = Get-ADComputer -Identity $ComputerName -Credential $credential -Server $server -Properties "PasswordLastSet" #$result = $computer.PasswordLastSet # Using ADSI to use on machines without ActiveDirectory PowerShell Module $Searcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ErrorAction Stop $Searcher.Filter = "(&(objectCategory=Computer)(name=$ComputerName))" $Searcher.SizeLimit = 0 $Searcher.SearchRoot = ([ADSISearcher]"").SearchRoot.Path if ($server -ne $null) { $Searcher.SearchRoot = [String]::Format("LDAP://{0}/{1}",$server,([ADSISearcher]"").SearchRoot.Path.Substring(7)) } if ($domain -ne $null) { [String]$DomainDN = "LDAP://" if ($server -ne $null) { $DomainDN += $server + "/" } $DNSArray = $Domain.Split('.') for ($x = 0; $x -lt $DNSArray.Length - 1 ; $x++) { $DomainDN += "DC=$($DNSArray[$x])," } $DomainDN += "DC=$($DNSArray[$DNSArray.Length-1])" $Searcher.SearchRoot = $DomainDN } if ($credential -ne $null) { $Domain = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList $DomainDN,$($Credential.UserName),$($Credential.GetNetworkCredential().password) $Searcher.SearchRoot = $Domain } $computer = $searcher.FindOne() $pwdLastSetTime = $computer.Properties["pwdLastSet"][0] $result = [DateTime]::FromFileTime($pwdLastSetTime) } "LocalMachine" { # Scheduled Task Used To Access Registry key as SYSTEM account $taskName ="ExtractMachineSecrets" $taskDescription = "Extract Machine Policy Secrets" $regKey = "HKLM\SECURITY\Policy\Secrets\`$MACHINE.ACC\CupdTime" # the number of attempts to wait for scheduled task output $maxTries = 10 $fileName = [System.IO.Path]::GetTempFileName() if (Test-Path $filename) { Remove-Item $filename -Force } # must run as SYSTEMfs to access these registry keys # even local Administrator does not have access $principal = New-ScheduledTaskPrincipal -LogonType ServiceAccount -RunLevel Highest -UserId "SYSTEM" # use reg command line tool to export required registry data $action = New-ScheduledTaskAction -Execute "reg.exe" -Argument ([String]::Format('export "{0}" "{1}"',$regKey,$filename)) # must apply these settings or task won't run on laptop running on battery $settings = New-ScheduledTaskSettingsSet -DontStopIfGoingOnBatteries -AllowStartIfOnBatteries # create scheduled task $task = Register-ScheduledTask -TaskName $taskName -Description $taskDescription -Action $action -Principal $principal -Settings $settings -Force $start = Start-ScheduledTask -TaskName $taskName $counter = 0 while ($counter -lt $maxTries) { if (Test-Path $filename) { break } Start-Sleep -Milliseconds 500 $counter++ } if (!(Test-Path $fileName)) { Throw "$fileName not found!" } else { $content = Get-Content $fileName # extract comma separated hex values from reg file on line 3 $hexData = $content[3].Substring(9) # byte array to store the hex values [byte[]]$bytes = @() for ($i = 0; $i -lt 8; $i++) { $bytes += [Convert]::ToInt32($hexData.Split(",")[$i],16) } # convert bytes into Int64 $time = [System.BitConverter]::ToInt64($bytes,0) # convert Int64 to DateTime $result = [DateTime]::FromFileTime($time) } Unregister-ScheduledTask -TaskName $taskName -Confirm:$false Remove-Item $fileName -Force } } return $result } }