need a powershell script to get passwordlastchanged from local user accounts across multiple computers

Shawn James 21 Reputation points
2021-11-10T22:03:25.847+00:00

i need to be able to get the password last changed date added to the table of user accounts in this script.
I have a script that will go into AD and pull all the computer accounts out, then ping each computer and if its alive WMIC to it and pull all the user accounts. it looks at AD for all computer accounts and outputs that to a CSV, then it uses that csv for the list of computers to go pull local accounts from. the table currently lists the computer name, account, and a few other details. i tried to add PwdLastSet and PasswordLastSet in my select statement but it just comes up empty. any ideas?

thanks,

Get-ADComputer -Filter * -Property * | Select-Object Name,OperatingSystem,OperatingSystemVersion,ipv4Address | Export-CSV ADcomputerslist.csv -NoTypeInformation -Encoding UTF8

$server_list = Import-Csv -Path .\ADcomputerslist.csv

foreach ($Name in $server_list){
$Online = Test-Connection -ComputerName ($server_list.Name) -Quiet
if ($Online -eq "True")
{
Get-WmiObject -ComputerName ($server_list.Name) -Class Win32_UserAccount -Filter "LocalAccount = $true" |
Select-Object PSComputername, Name, Status, Disabled, AccountType, Lockout, PasswordRequired, PasswordChangeable |
Export-CSV LocalAccount.csv
}
}

Windows for business | Windows Server | User experience | PowerShell
Windows for business | Windows Server | User experience | Other
Windows for business | Windows Server | Devices and deployment | Configure application groups
0 comments No comments
{count} votes

Accepted answer
  1. Rich Matheisen 47,901 Reputation points
    2021-11-11T15:27:06.873+00:00

    The property you want isn't available in the object returned by WMI (but you knew that). So, what you want needs help from the Get-LocalUser cmdlet.

    Here's a rearranged (and slightly enhanced) version of the code I posted earlier.

    $server_list = Get-ADComputer -Filter * | 
                    Select-Object Name,OperatingSystem,OperatingSystemVersion,ipv4Address 
    $server_list | 
        Export-CSV ADcomputerslist.csv -NoTypeInformation -Encoding UTF8
    
    Invoke-Command -ComputerName $server_list -ScriptBlock {        # NOTE: if the local machine is in $server_list it will generate an error.
            $props = @{
                PSComputername = ""
                Name = ""
                Status = ""
                Disabled = ""
                AccountType = ""
                Lockout = ""
                PasswordRequired = ""
                PasswordChangeable = ""
                PasswordLastSet = ""
            }
            Get-WmiObject -Class Win32_UserAccount -Filter "LocalAccount = $true" -ErrorAction Continue 2>&1 |
                ForEach-Object{
                    if ($_.pstypenames -contains "System.Management.Automation.ErrorRecord"){
                        # skip any errors
                        # if you want to report errors, the entire ErrorRecord is available in $_
                    }
                    else {
                        $props.PSComputername = $_.PSComputername
                        $props.Name = $_.name
                        $props.Status = $_.Status
                        $props.Disabled = $_.Disabled
                        $props.AccountType = $_.AccountType
                        $props.Lockout = $_.Lockout
                        $props.PasswordRequired = $_.PasswordRequired
                        $props.PasswordChangeable = $_.PasswordChangeable
                        $props.PasswordLastSet = (Get-LocalUser $_.Name).PasswordLastSet
                    }
                    [PSCustomObject]$props
                }
    } | Export-Csv LocalAccount.csv -NoTypeInformation
    
    0 comments No comments

6 additional answers

Sort by: Most helpful
  1. Rich Matheisen 47,901 Reputation points
    2021-11-11T03:22:58.963+00:00

    I think I'd try it this way, without the Test-Connection which can take a long time. By using Invoke-Command you'll introduce some level of parallelism instead of running Get-WmiObject one at a time.

    $server_list = Get-ADComputer -Filter * | 
                    Select-Object Name,OperatingSystem,OperatingSystemVersion,ipv4Address 
    $server_list | 
        Export-CSV ADcomputerslist.csv -NoTypeInformation -Encoding UTF8
    
    Invoke-Command -ComputerName $server_list -ScriptBlock {        # NOTE: if the local machine is in $server_list it will generate an error.
            Get-WmiObject -Class Win32_UserAccount -Filter "LocalAccount = $true" 
        } 2&>1 |
            ForEach-Object{
                if ($_.pstypenames -contains "System.Management.Automation.ErrorRecord"){
                    # skip any errors
                    # if you want to report errors, the entire ErrorRecord is available in $_
                }
                else {
                    $_ | Select-Object PSComputername, Name, Status, Disabled, AccountType, Lockout, PasswordRequired, PasswordChangeable        }
            } | Export-Csv LocalAccount.csv -NoTypeInformation
    
    0 comments No comments

  2. Shawn James 21 Reputation points
    2021-11-11T13:04:49.313+00:00

    Thanks for looking at this.

    This looks like a good rewrite of the script I put up. I haven't tried it yet, but I don't see where it will pull the users last password changed date, which is the addition I'm not understanding how to add.

    I need this script to do the same things its currently doing, create a list of computers and use that list as a base for the second part where it goes out and looks at local users on each of those computers, but additionally i need it to grab the password last changed date for each of those local users as well.


  3. Shawn James 21 Reputation points
    2021-11-11T14:54:13.283+00:00

    thank you, that still doesn't really solve what i need though.

    with that script i have to be logged into the local PC and know the user accounts, and run this against each account. i know there are ways to automate it finding each account, but regardless i have to touch every machine this way through physical access or RDP.

    I need to collect this information across 150 windows based systems. I'm attempting to run a script from a single location that will first pull every asset out of AD, and then go to every asset and export not only the the local user accounts but additionally when the passwords were last changed for those local accounts.

    0 comments No comments

  4. Shawn James 21 Reputation points
    2021-11-11T15:42:21.703+00:00

    the following script will do this. it needs to be ran with admin privs and the activedirectory module needs to be imported first.

    $strExportPath = ("LocalUsers_",$(Get-Date -Format yyyyMMdd_HHmm),".csv" -join "")

    $arrComputers = Get-ADComputer -Filter * -Property * | Select-Object Name,OperatingSystem,OperatingSystemVersion,ipv4Address
    $arrComputers | Export-CSV ADcomputerslist.csv -NoTypeInformation -Encoding UTF8

    $arrPasswordData = @()

    $arrComputers | ForEach-Object {

    $strComputerName = $_.Name
    
    
    $Online = Test-Connection -ComputerName ($strComputerName) -Quiet
        if ($Online -eq "True")
          {
            "Getting Local Account information for " + $strComputerName 
             $strDateTime = Get-Date
    
            get-wmiobject -ComputerName $strComputerName -class "Win32_UserAccount" -filter {LocalAccount = True } | foreach {
            $strADSI =[ADSI]"WinNT://$strComputerName/$($_.Name),user"
                $intPasswordAge = $strADSI.PasswordAge[0] / 86400
                $strLastLogin = $strADSI.LastLogin[0]
                $intMaxPasswordAge = $strADSI.MaxPasswordAge[0] / 86400
    
            If ($intPasswordAge -gt $intMaxPasswordAge){
            $strPasswordStatus = "Password has Expired"
            }
            Else {
            $strPasswordStatus = "Password will expire in " + [Math]::Round($intMaxPasswordAge - $intPasswordAge) + " days."
            } 
    
            $strPasswordData = New-Object -TypeName PSObject
            $strPasswordData |Add-Member -type NoteProperty -name Host -value $strComputerName
            $strPasswordData |Add-Member -type NoteProperty -name Username -value $_.Name 
            $strPasswordData |Add-Member -type NoteProperty -name AccountType -value $_.AccountType 
            $strPasswordData |Add-Member -type NoteProperty -name Disabled -value $_.Disabled 
            $strPasswordData |Add-Member -type NoteProperty -name Lockout -value $_.Lockout 
            $strPasswordData |Add-Member -type NoteProperty -name Status -value $_.Status 
            $strPasswordData |Add-Member -type NoteProperty -name PasswordRequired -value $_.PasswordRequired
            $strPasswordData |Add-Member -type NoteProperty -name PasswordChangeable -value $_.PasswordChangeable
            $strPasswordData |Add-Member -type NoteProperty -name PasswordSet -value ((Get-Date).AddSeconds(-$intPasswordAge)) 
            $strPasswordData |Add-Member -type NoteProperty -name PasswordAge -value ([Math]::Round($intPasswordAge))
            $strPasswordData |Add-Member -type NoteProperty -name MaxPasswordAge -value ([Math]::Round($intMaxPasswordAge ))
            $strPasswordData |Add-Member -type NoteProperty -name PasswordStatus -value $strPasswordStatus
    
            $arrPasswordData += $strPasswordData
            Clear-Variable strPasswordData
            }
         }
        Else
        {
            $strComputerName + " is currently unreachable"
        }
    

    }

    "Saving results to " + $strExportPath
    $arrPasswordData | Export-Csv ""path_to_output.csv"


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.