I got a good question from a Petri reader on Twitter not long ago. I was asked about finding local user accounts on a list of servers. Seems like a reasonable task and something that PowerShell can handle quite nicely. If you merely want to enumerate local user accounts, WMI is a great place to start. We can use the Win32_UserAccount class.
You can use with Get-WMIObject or Get-CIMInstance. Using the latter is preferred these days as it uses the PowerShell remoting ports and is much more firewall friendly. With either cmdlet it is also very easy to list users from multiple computers.
When developing a PowerShell expression using WMI, I like to test locally first if at all possible. This ensures my syntax is correct.
get-ciminstance win32_useraccount
That seemed pretty easy. My computer does not belong to a domain so the Domain property reflects the computername. As with most things in PowerShell, there is more here than meets the eye. I could re-run this command and pipe to Select-Object *, or I can use Get-CIMClass to discover the properties.
get-cimclass win32_useraccount | select -expand cimclassproperties | Select Name,CimType
Once you know the properties, it is simple enough to include them.
get-ciminstance win32_useraccount | Select Name,Description,Status,Disabled,AccountType | sort Status | format-table -groupby Status -Property Name,Description,Disabled,AccountType
Here’s a nice report with accounts grouped on whether they are disabled or not. I can repeat this process and specify a remote computer.
get-ciminstance win32_useraccount -comp chi-fp02 | sort Status | format-table -groupby Status Property Name,Description,Disabled,AccountType,PSComputername
Since I knew I was going to format results as a table, I can omit the Select-Object portion of my expression. Of course in PowerShell if I can do something for one computer, I can do it for many.
get-ciminstance win32_useraccount -ComputerName chi-web02,chi-fp02,chi-core01
I can do just about anything I want with the result from here. I can export to a CSV file, create an HTML report or re-format.
get-ciminstance win32_useraccount -ComputerName chi-web02,chi-fp02,chi-core01 |
sort PSComputername,Disabled,Name |
format-Table -groupby PSComputername -property Caption,Name,Disabled
I’ve typed out the computer names, but you can get those values from anywhere, even a text list using Get-Content.
$computers = get-content c:\scripts\mycomputers.txt
get-ciminstance win32_useraccount -ComputerName $computers |
sort PSComputername,Disabled,Name |
format-Table -groupby PSComputername -property Caption,Name,Disabled
Or perhaps need to limit your search. Don’t pipe your command to Where-Object to filter. Instead take advantage of WMI filtering and do it at the source, i.e. the remote server.
This is especially important if you are querying many remote computers. Let’s say you want to find all the disabled local accounts. Again, test locally first.
Get-CimInstance Win32_Useraccount -filter "Disabled = 'true'"
WMI filters use the legacy operators like the = sign and instead of the PowerShell option $True or $False, you need to use the string. Now it is a simple matter to extend this to my list of computers.
Get-CimInstance Win32_Useraccount -filter "Disabled = 'true'" -ComputerName $computers
Or perhaps I need to find all enabled local accounts other than Administrator.
Get-CimInstance Win32_Useraccount -filter "Disabled = 'false' AND name <>'Administrator'" -ComputerName $computers
I’ve been using Get-CIMInstace, which I think is the better choice. If necessary, you can also use Get-WMIObject. The syntax is identical.
Get-wmiobject Win32_Useraccount -filter "Disabled = 'false' AND name <>'Administrator'" -ComputerName $computers
You get the same information, although technically this is a different type of object.
Before we wrap up, let me point out that if you query a domain controller you will get domain accounts.
Sometimes this can be useful, but if your goal is to identify local user accounts on domain members, you’ll need to make sure no domain controllers are in your list. By the way, if you need to use alternate credentials, you will need to create CIMSessions for each remote computers. My examples have been ad-hoc with PowerShell setting up and tearing down temporary CIMSessions.
$csess = new-cimsession -ComputerName $computers -Credential globomantics\administrator
Now I have a collection of CIMSessions, which I can use like this:
get-ciminstance win32_useraccount -filter "name='LocalAdmin'" -CimSession $csess | Select Name,Disabled,PSComputername
And because I can reuse these sessions, subsequent CIM commands run much faster.
get-ciminstance win32_useraccount -filter "disabled='false'" -CimSession $csess |
Sort PSComputername |
Select Name,Description,SID,PSComputername,@{Name="ReportDate";Expression={Get-Date}} |
Export-CSV c:\work\LocalAccounts.csv –NoTypeInformation
With a single command I created a CSV report of enabled local accounts. I could set this up as a PowerShell scheduled job to audit local accounts on a monthly basis, send them via email, or log them to a SQL database. Once you understand some PowerShell fundamentals and grasp the power of the pipeline, you’ll be amazed at what you can accomplish.