Skip to content

Instantly share code, notes, and snippets.

@phyoewaipaing
Last active November 12, 2023 14:02
Show Gist options
  • Save phyoewaipaing/a5ae4ad9e6a21c1815381c83f741584e to your computer and use it in GitHub Desktop.
Save phyoewaipaing/a5ae4ad9e6a21c1815381c83f741584e to your computer and use it in GitHub Desktop.
Get Local Account & Password Info on Local/Remote Computers in Workgroup or Domain
<#
.SYNOPSIS
Script that will search local/domain users accounts with password information on local / multiple computers.
.DESCRIPTION
This script will search the local & domain users account with password expiry date and last login time on workgroup/domain joined or domain controllers computers.
Please note that it will take quite some time if it's running on domain environment with lots of users objects as it will query all the users/groups in the domain.
To query the remote computer, WinRm needs to be enabled (with Port 5985).
Use -WriteVerbose switch to output the execution log in console.
You can also verify that if specific security group exists on computers and explore it's members locally or remotely.
Example usage:
Get_Local_Users_Info_v1.1.ps1 -ComputerName 'Computer1','Computer2','Computer3'
Author: Phyoe Wai Paing
Country: Myanmar(Burma)
Released: 10/23/2016
Changed Log:
version 1.0 : 10/23/2016 : Initial Release
version 1.1 : 15/02/2022 : Remove win32_NTDomain and replace with win32_ComputerSystem and remove duplicate wmi calls. Reduce execution time from ~30sec to ~1sec on workgroup computers.
Detect standalone computer, domain member or domain controllers with win32_ComputerSystem.DomainRoles. Changed Attribute Name (LocalMemberOf => DomainMemberOf) on domain controllers.
.EXAMPLE
Get_Local_Users_Info_v1.1.ps1 -GroupName Administrators
This will search all users in the local Administrators group.
.EXAMPLE
Get_Local_Users_Info.ps1 -ComputerName 'Computer1','Computer2','Computer3' | sort LastLogon | Format-Table -autosize
This will search all local users on 'Computer1','Computer2' & 'Computer3'. The result will be sorted by the LastLogon Time.
.EXAMPLE
Get_Local_Users_Info.ps1 -ComputerName 'Computer1','Computer2','Computer3' -GroupName "Administrators" | sort PasswordLastSet | Format-Table -autosize
This will search all users in the Administrators group on 'Computer1','Computer2' & 'Computer3'. The result will be sorted by the Password Last Set Time.
.PARAMETER ComputerName
Specify the name of computer or computers on which you will search the local accounts info. The remote computers must be powershell remoting already enabled.
.PARAMETER GroupName
Specify the name of the local group in which you will search the users.
.LINK
You can find this script and more at: https://www.scriptinghouse.com/
#>
param([array]$ComputerName,[string]$GroupName,[switch]$WriteVerbose)
If ($ComputerName -ne 'localhost' -AND $ComputerName -AND !$cred)
{
$cred = Get-Credential
}
################# Check if the port for WinRM is Opened ##############################################
function Test-Port { param([string]$Destination,[string]$Port)
$Socket= New-Object Net.Sockets.TcpClient
$IAsyncResult= [IAsyncResult] $Socket.BeginConnect($Destination,$Port, $null, $null)
$success=$IAsyncResult.AsyncWaitHandle.WaitOne(100,$true)
Return $success
$Socket.close()
}
################## Script Block to execute locally or remotely #########################################
$Script_Block= {
$stopwatch = [system.diagnostics.stopwatch]::StartNew() ## The timer is inserted just for debuggin purpose ##
If ($WriteVerbose) { Write-Host -fore yellow "$($stopWatch.Elapsed) : Started script execution" }
################ Get the culture & Parse the datetime from net user's date/time output, we need this because 'net user' outputs different date/time format depending on locale user's set #############################
$Culture_Name=(Get-Culture).Name
$culture = New-Object System.Globalization.CultureInfo $Culture_Name
$IPAddress = $Args[0]
$GroupName = $Args[1]
$output=@()
$ComName="`"$env:ComputerName`""
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Going to query users and groups by Get-WmiObject win32_GroupUser" }
$WMI_Result = Get-WmiObject win32_GroupUser ## fetch the WMI result in advance so that the script executes faster
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Have got the users and groups by Get-WmiObject win32_GroupUser" }
$LocalUsers = @()
$DomainUsers = @()
$ComInfo = Get-WmiObject win32_ComputerSystem
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Have got the domain by Get-WmiObject win32_ComputerSystem" }
################## If $GroupName parameter is defined then, then enumerate users & groups from WMI #################
If ($GroupName)
{
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Group Name going to be enumerated" }
$Users_Unmodified = $WMI_Result | where {$_.GroupComponent -match "Win32_Group.Domain=$ComName,Name=`"$GroupName`"" -AND $_.PartComponent -match "Win32_UserAccount.Domain=$ComName" } | foreach { $_.PartComponent }
################# If Local Users are found, then create ps object for each local users ######################
If ($Users_Unmodified)
{
$Users_Unmodified | foreach {
$User = New-Object -TypeName PsObject -Property @{IPAddress="";Computer="";Account="";LocalMemberOf="";AccountType="";PasswordLastSet="";LastLogon="";Active="";PasswordExpires="";UserCanChangePass="";Description=""; }
$User.Account = $_.Split(',')[1].Split('=')[1].Trim('`"')
$User.IPAddress = $IPAddress
## If the DomainRole propety of $ComInfo is '0' or '2' then it's a standalone computer ##
If ( @(0,2) -contains $ComInfo.DomainRole)
{ $User.Computer = "WorkGroup" }
else {$User.Computer = "Domain" }
$User.AccountType = "Local"
$LocalUsers += $User
}
################ Check if the computer is domain joined and if yes,check if there are domain users in the local user's group ##################################
## If the DomainRole propety of $ComInfo is '1','3','4' or '5' then it's a domain computer or domain controller ##
If ( @(1,3,4,5) -contains $ComInfo.DomainRole)
{
$DomainName = ($ComInfo.Domain -split '\.')[0];
$Domain_Users_Unmodified = $WMI_Result | where {$_.GroupComponent -match "Win32_Group.Domain=$ComName,Name=`"$GroupName`"" -AND $_.PartComponent -match "Win32_UserAccount.Domain=$DomainName" } | foreach { $_.PartComponent }
If($Domain_Users_Unmodified)
{
$Domain_Users_Unmodified | foreach {
$User = New-Object -TypeName PsObject -Property @{IPAddress="";Computer="";Account="";LocalMemberOf="";AccountType="";PasswordLastSet="";LastLogon="";Active="";PasswordExpires="";UserCanChangePass="";Description=""; }
$User.Account = $_.Split(',')[1].Split('=')[1].Trim('`"')
$User.IPAddress = $IPAddress
## If the DomainRole propety of $ComInfo is '1','3','4' or '5' then it's a domain computer or domain controller ##
If ( @(1,3) -contains $ComInfo.DomainRole)
{
$User.Computer = "Domain"
}
elseif (@(4,5) -contains $ComInfo.DomainRole)
{
$User.Computer = "Domain Controller"
}
else
{
$User.Computer = "Workgroup"
}
$User.AccountType = "Domain"
$DomainUsers += $User
}
}
}
}
else
{
################ if local users are not found for the specific group, then check for if the computer is domain controller & change the query type #############
## '4' or '5' in the DomainRole property means that the system is a domain controller ##
If ((@(4,5) -contains $ComInfo.DomainRole))
{
$DomainName = ($ComInfo.Domain -split '\.')[0];
$Users_Unmodified = $WMI_Result | where {$_.GroupComponent -match ",Name=`"$GroupName`"" -AND $_.PartComponent -match "Win32_UserAccount.Domain=`"$DomainName`"" -AND $_.PartComponent -Notmatch "\$" } | foreach { $_.PartComponent }
$Users_Unmodified | sort | unique | foreach {
$User = New-Object -TypeName PsObject -Property @{IPAddress="";Computer="";Account="";LocalMemberOf="";AccountType="";PasswordLastSet="";LastLogon="";Active="";PasswordExpires="";UserCanChangePass="";Description=""; }
$User.Account = $_.Split(',')[1].Split('=')[1].Trim('`"')
$User.IPAddress = $IPAddress
$User.Computer = "Domain Controller"
$User.AccountType = "Domain"
$LocalUsers += $User
}
}
else
{
Write-Host -fore Red "There are no users in `"$GroupName`" Group on $IPAddress."; Exit;
}
}
}
else
{
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : No Need to enumerate the Group. Going to enumerate all users on system." }
############### if the group name is not specified as parameter, then look for all users on the machine ################################
$Users_Unmodified = $WMI_Result | where {$_.GroupComponent -match "Win32_Group.Domain=$ComName" -AND $_.PartComponent -match "Win32_UserAccount.Domain=$ComName" } | foreach { $_.PartComponent}
### we need to remove duplicate values from WMI result, if the user account is in different groups #####
$Users_Split = $Users_Unmodified | sort | unique | foreach { $_.Split(',')[1].Split('=')[1].Trim('`"') }
$Users_Split | foreach {
$User = New-Object -TypeName PsObject -Property @{IPAddress="";Computer="";Account="";LocalMemberOf="";AccountType="";PasswordLastSet="";LastLogon="";Active="";PasswordExpires="";UserCanChangePass="";Description=""; }
$User.Account = $_
$User.IPAddress = $IPAddress
If ((@(4,5) -contains $ComInfo.DomainRole))
{
$User.Computer = "Domain Controller"
}
elseIf ((@(1,3) -contains $ComInfo.DomainRole))
{
$User.Computer = "Domain"
}
else
{
$User.Computer = "Workgroup"
}
$User.AccountType = "Local"
$LocalUsers += $User
}
################ Check if the computer is domain joined and if yes,check if there are domain users in the local user's group ##################################
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Going to check if the user is domain joined, then enumerate domain users in local users' group" }
############ Execute the statement only if the computer is domain joined & not being a domain controller ####
If ((@(1,3) -contains $ComInfo.DomainRole))
{
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Being domain joined, going to split out the user info from WMI_Result" }
$DomainName = ($ComInfo.Domain -split '\.')[0];
$Domain_Users_Unmodified = $WMI_Result | where {$_.GroupComponent -match "Win32_Group.Domain=$ComName" -AND $_.PartComponent -match "Win32_UserAccount.Domain=$DomainName" } | foreach { $_.PartComponent }
############ If there are domain users in the local group, then create Ps Objects for them ###############
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : If there are domin users in local group, then going to create objects for them from WMI_Result" }
$Domain_Users_Split = $Domain_Users_Unmodified | sort | unique | foreach { $_.Split(',')[1].Split('=')[1].Trim('`"') }
If($Domain_Users_Split)
{
$Domain_Users_Split | foreach {
$User = New-Object -TypeName PsObject -Property @{IPAddress="";Computer="";Account="";LocalMemberOf="";AccountType="";PasswordLastSet="";LastLogon="";Active="";PasswordExpires="";UserCanChangePass="";Description=""; }
$User.Account = $_
$User.IPAddress = $IPAddress
If ((@(4,5) -contains $ComInfo.DomainRole))
{
$User.Computer = "Domain Controller"
}
elseIf ((@(1,3) -contains $ComInfo.DomainRole))
{
$User.Computer = "Domain"
}
else
{
$User.Computer = "Workgroup"
}
$User.AccountType = "Domain"
$DomainUsers += $User
}
}
}
########### if the local users are not found then, check if the machine is a domain controller & look for domain users ######
If ((@(4,5) -contains $ComInfo.DomainRole))
{
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : As it's a domain controller, we're going to get the domain users now." }
$DomainName = ($ComInfo.Domain -split '\.')[0];
$Users_Unmodified = $WMI_Result | where {$_.PartComponent -match "Win32_UserAccount.Domain=`"$DomainName`"" -AND $_.PartComponent -Notmatch "\$" } | foreach { $_.PartComponent }
$Users_Split = $Users_Unmodified | sort | unique | foreach { $_.Split(',')[1].Split('=')[1].Trim('`"') }
$Users_Split | foreach {
$User = New-Object -TypeName PsObject -Property @{IPAddress="";Computer="";Account="";DomainMemberOf="";AccountType="";PasswordLastSet="";LastLogon="";Active="";PasswordExpires="";UserCanChangePass="";Description=""; }
$User.Account = $_
$User.IPAddress = $IPAddress
$User.Computer = "Domain Controller"
$User.AccountType = "Domain"
$MemberOf = ($WMI_Result | where { $_.PartComponent -match ",Name=`"$($User.Account)`"" -AND $_.PartComponent -match "Win32_UserAccount.Domain=`"$DomainName`"" } | foreach { $_.GroupComponent.Split(',')[1].Split('=')[1].Trim('`"')}) -join ','
$User.DomainMemberOf = $MemberOf
$LocalUsers += $User
}
}
}
######################### Inject properties from net user output to PsObject ################
#### If the AccountType is Local, inject 'net user' output to $LocalUsers objects, otherwise N/A properties to $DomainUser objects ###
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Going to get local users info by net user" }
$LocalUsers | foreach {
$AccountName= $_.Account
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Going to get local users info for $AccountName by net user" }
$AccountInfo = net user $AccountName ## get the 'net user' info
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Have got the local users info for $AccountName by net user" }
If (![string]$IPAddress)
{
$resultObj.IPAddress = "Localhost"
}
## We will query the $WMI_Result with the local computer name only if the computer is not a domain controller ##
If (!(@(4,5) -contains $ComInfo.DomainRole))
{
$MemberOf = ($WMI_Result | where { $_.PartComponent -match ",Name=`"$AccountName`"" -AND $_.PartComponent -match "Win32_UserAccount.Domain=$ComName" } | foreach { $_.GroupComponent.Split(',')[1].Split('=')[1].Trim('`"')}) -join ','
$_.LocalMemberOf = $MemberOf
}
$_.Active = ($AccountInfo | find /I 'Account active').TrimStart('Account active')
############### Parse the user 'Password last set' time from net user output ##################
$Date_Unparsed = ($AccountInfo | find /I 'Password last set').TrimStart('Password last set')
## Remove all unknown characters from time value (like ?) ##
$Date_Parsed = ([char[]]$Date_Unparsed | ? { $_ -match "\d|\/|\s|:|[A-z]" }) -join ''
$_.PasswordLastSet = [datetime]::parse($Date_Parsed, $culture)
############### Parse the user 'last logon' time from net user output ########################
$Date_Unparsed = ($AccountInfo | find /I 'Last logon').TrimStart('Last logon')
$Date_Parsed = ([char[]]$Date_Unparsed | ? { $_ -match "\d|\/|\s|:|[A-z]" }) -join ''
Try
{
$LastLogon = [datetime]::parse($Date_Parsed, $culture)
}
catch
{
$LastLogon = $Date_Parsed
}
$_.LastLogon = $LastLogon
############### Parse the user 'Password expires' time from net user output ##################
$Date_Unparsed = ($AccountInfo | find /I 'Password expires').TrimStart('Password expires')
$Date_Parsed = ([char[]]$Date_Unparsed | ? { $_ -match "\d|\/|\s|:|[A-z]" }) -join ''
Try
{
$PasswordExpires = [datetime]::parse($Date_Parsed, $culture)
}
catch
{
$PasswordExpires = $Date_Parsed
}
$_.PasswordExpires = $PasswordExpires
$_.UserCanChangePass = ($AccountInfo | find /I 'User may change password').TrimStart('User may change password')
$_.Description = ($AccountInfo | findstr 'Comment').TrimStart('Comment').Trim() ## You need to use findstr, otherwise not found error occurs.
$output+=$_
}
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Getting user info by net user and object creation finished" }
############### If Domain Users exists in local group, then fetch 'MemberOf' property and inject N/A to other properties, otherwise, you need ####
############### to use CredSSP to fetch 'net user /domain' on client computer from domain controller, which requires additional configuration ######
If ($DomainUsers)
{
Write-Host -fore yellow "$DomainUsers"
$DomainUsers | sort | unique | foreach {
$AccountName= $_.Account
If (![string]$IPAddress)
{
$resultObj.IPAddress = "Localhost"
}
$MemberOf = ($WMI_Result | where {$_.PartComponent -match "Win32_UserAccount.Domain=$DomainName,Name=`"$AccountName`"" } | foreach { $_.GroupComponent.Split(',')[1].Split('=')[1].Trim('`"')}) -join ','
$_.LocalMemberOf = $MemberOf
$_.Active = 'N/A'
$_.PasswordLastSet = 'N/A'
$_.LastLogon = 'N/A'
$_.PasswordExpires = 'N/A'
$_.UserCanChangePass = 'N/A'
$_.Description = 'N/A'
$output+=$_
}
}
If ($WriteVerbose) { write-host -fore yellow "$($stopWatch.Elapsed) : Going to return the Script Block output" }
$stopWatch.reset();
return $output;
}
############################### The actual Action starts from here, if not localhost then do remote execution #############################
if($ComputerName -AND $ComputerName -ne "localhost")
{
$Remote_Job=@()
$Output=@()
$ComputerName | foreach { If(Test-Port $_ 5985) {[array]$Computers_Connected += $_ }
}
$Computers_NotConnected = $ComputerName | where { $Computers_Connected -NotContains $_ }
$Computers_NotConnected | foreach { Write-Host -fore red "Cannot connect to $_ computer."}
$Computers_Connected | foreach { $Remote_Job += Invoke-Command -credential $cred -Computername $_ -ScriptBlock $Script_Block -ArgumentList $_,$GroupName -AsJob
}
$Remote_Job | foreach { $Output += $_ | Wait-Job | Receive-Job | select IPAddress,Computer,Account,*MemberOf,AccountType,PasswordLastSet,LastLogon,Active,PasswordExpires,UserCanChangePass,Description; Remove-Job $_ }
$Output
}
else
{
Invoke-Command $Script_Block -ArgumentList 'localhost',$GroupName | select IPAddress,Computer,Account,*MemberOf,AccountType,PasswordLastSet,LastLogon,Active,PasswordExpires,UserCanChangePass,Description
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment