Retrieve Time Machine Password Last Reset via PowerShell

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.

https://onedrive.live.com/redir?resid=93A8E9D387076121!23829&authkey=!AJXBw6m_bmSSRko&ithint=file%2czip

# 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
    }
}

About chentiangemalc

specializes in end-user computing technologies. disclaimer 1) use at your own risk. test any solution in your environment. if you do not understand the impact/consequences of what you're doing please stop, and ask advice from somebody who does. 2) views are my own at the time of posting and do not necessarily represent my current view or the view of my employer and family members/relatives. 3) over the years Microsoft/Citrix/VMWare have given me a few free shirts, pens, paper notebooks/etc. despite these gifts i will try to remain unbiased.
This entry was posted in .NET, Active Directory, PowerShell and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s