Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Get Local Administrators with WMI and PowerShell

Posted on July 1, 2011

Earlier this week I was helping someone out on a problem working with the local administrators group. There are a variety of ways to enumerate the members of a local group. The code he was using involved WMI. I hadn't really worked with the WMI approach in any great detail so I thought I'd see how this might work in PowerShell. I ended up with a function to enumerate members of the local administrators group on a computer, as well as test if an account belongs to the group.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

The first function, Get-LocalAdministrators, will connect to a remote computer (it defaults to the local) and returns an object for each member like this:

[cc lang="DOS"]
Name : LocalAdmins
Fullname :
Caption : JDHLAB\LocalAdmins
Description :
Domain : JDHLAB
SID : S-1-5-21-3957442467-353870018-3926547339-1148
LocalAccount : False
Disabled :
Computer : CLIENT1
[/cc]

If I simply wanted a name, that would be pretty easy and I'd use a different approach. But I wanted richer information so that I could sort out what accounts were local, or disabled. I worked under the assumption that I would query a group of machines and save the data to a CSV file so I could later slice and dice the data. Or work with it further in PowerShell. Here's the function.

[cc lang="PowerShell"]
Function Get-LocalAdministrators {

[cmdletbinding()]

Param(
[Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[switch]$AsJob)

Begin {

Set-StrictMode -Version latest
Write-Verbose "Starting $($myinvocation.mycommand)"

#define an new array for computernames if this is run as a job
$computers=@()
}

Process {
foreach ($computer in $computername) {
$computers+=$Computer
$sb={Param([string]$computer=$env:computername)
Try {
Write-Verbose "Querying $computer"
$AdminsGroup=Get-WmiObject -Class Win32_Group -computername $Computer -Filter "SID='S-1-5-32-544' AND LocalAccount='True'" -errorAction "Stop"
Write-Verbose "Getting members from $($AdminsGroup.Caption)"

$AdminsGroup.GetRelated() | Where {$_.__CLASS -match "Win32_UserAccount|Win32_Group"} |
Select Name,Fullname,Caption,Description,Domain,SID,LocalAccount,Disabled,
@{Name="Computer";Expression={$Computer.ToUpper()}}
}
Catch {
Write-Warning "Failed to get administrators group from $computer"
Write-Error $_
}
} #end scriptblock
if (!$AsJob) {
Invoke-Command -ScriptBlock $sb -ArgumentList $computer
}
} #foreach computer
} #process

End {
#create a job is specified
if ($AsJob) {
Write-Verbose "Creating remote job"
#create a single job targeted against all the computers. This will execute on each
#computer remotely
Invoke-Command -ScriptBlock $sb -ComputerName $computers -asJob
}

Write-Verbose "Ending $($myinvocation.mycommand)"
}
} #end function
[/cc]

The main part of the function uses WMI to query the Win32_Group class. Because the group may have been renamed, the filter searches for it by well-known SID.

[cc lang="PowerShell"]
$AdminsGroup=Get-WmiObject -Class Win32_Group -computername $Computer -Filter "SID='S-1-5-32-544' AND LocalAccount='True'" -errorAction "Stop""
[/cc]

Once the group is found, you could use an Associators Of query to find all the related objects, whcih would include the group members. But Associators Of queries are not easy to construct, assuming you even knew about them. Instead, in PowerShell the WMI object has a method called GetRelated(). This method in essence runs an Associators Of query for you. But obviously this is much easier.

[cc lang="PowerShell"]
$AdminsGroup.GetRelated() | Where {$_.__CLASS -match "Win32_UserAccount|Win32_Group"} |
Select Name,Fullname,Caption,Description,Domain,SID,LocalAccount,Disabled,
@{Name="Computer";Expression={$Computer.ToUpper()}}
[/cc]

The method allows you to specify a resultant class as a parameter, but it doesn't speed up the process. It only filters the output. So I didn't bother and instead piped the results to Where-Object to get user and group accounts. I also select a few key properties to write to the pipeline. This query takes a little time to run and there isn't any way to speed it up. Although I have something to alleviate the pain.

I decided this would be a good reason to use a background job. So I included a function parameter to run the entire command as a job. You'll notice that in the process block I'm creating a script block.

[cc lang="PowerShell"]
Process {
foreach ($computer in $computername) {
$computers+=$Computer
$sb={Param([string]$computer=$env:computername)
Try {
[/cc]

The script block takes a computername as a parameter. If I'm running the command normally (ie no job), then the script block executes for each computer passed as a parameter or piped in.

[cc lang="PowerShell"]
if (!$AsJob) {
Invoke-Command -ScriptBlock $sb -ArgumentList $computer
}
[/cc]

The command runs locally and queries the remote computer. But if I decide to run this as a job, I wait until the End scriptblock since I wanted to create one job. In order for this to work, I need to keep track of all the pipelined computer names so I keep adding them to $computers in the Process script block. After I've built the list, I again use Invoke-Command, but this time I also specify that the command runs ON the remote machines.

[cc lang="PowerShell"]
if ($AsJob) {
Write-Verbose "Creating remote job"
#create a single job targeted against all the computers. This will execute on each
#computer remotely
Invoke-Command -ScriptBlock $sb -ComputerName $computers -asJob
}
[/cc]

The end result is a job created locally with child jobs that run on all the computers specified. This allows me to keep working in PowerShell, and get the results when I want. Because the command is executing simultaneously it runs a little faster overall. I realize the WMI queries aren't the speediest, but I end up with valuable information.

The other function is a simple test: does this account belong to the administrators group?

[cc lang="PowerShell"]
Function Test-IsLocalAdministrator {

[cmdletbinding()]

Param(
[Parameter(Position=0,HelpMessage="Enter a user or group name in the domain\username format")]
[ValidatePattern("\w+\\\w+")]
[string]$Name="$env:userdomain\$env:username",
[Parameter(Position=1)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:computername
)

Set-StrictMode -Version latest

Write-Verbose "Starting $($myinvocation.mycommand)"
#Split Name into domain and name parts
$Domain=$Name.Split("\")[0]
$Member=$Name.Split("\")[1]

Try {
Write-Verbose "Querying $computername"

$AdminsGroup=Get-WmiObject -Class Win32_Group -computername $Computername -Filter "SID='S-1-5-32-544' AND LocalAccount='True'" -errorAction "Stop"
Write-Verbose "Getting members from $($AdminsGroup.Caption)"
Write-Verbose "Testing $($name.ToUpper())"
$Found=$AdminsGroup.GetRelationships("Win32_GroupUser") | Where {$_.PartComponent -match "Domain=""$Domain"",Name=""$Member"""}

If ($found) {
Write $True
}
else {
Write $False
}
}
Catch {
Write-Warning "Failed to get administrators group from $computername"
Write-Error $_
}

Finally {
Write-Verbose "Ending $($myinvocation.mycommand)"
}

} #end function
[/cc]

Here I took a slightly different approach. I still get the admins group with the same WMI query. But this time I use the GetRelationships() method which is a .NET equivalent of a References WMI query.

[cc lang="PowerShell"]
$Found=$AdminsGroup.GetRelationships("Win32_GroupUser") | Where {$_.PartComponent -match "Domain=""$Domain"",Name=""$Member"""}
[/cc]

This type of query is quick, at least for groups and returns WMI paths of related objects. All I have to do is parse the PartComponent property and use a regular expression match to see if the domain and account name match. You have to specify the name in domain\name format. If $found exists, the function writes $True. I think Test functions should be simple, quick and concise.

These functions have limited error handling and don't support alternate credentials, although you could certainly add that. I'll be the first to admit that these may not be the best ways to achieve these results, but they are viable options and I am happy with the way I incorporated support for background jobs in the function.

If you would like to try these out, download get-wmiadmin.ps1 and load the functions into your PowerShell session.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

12 thoughts on “Get Local Administrators with WMI and PowerShell”

  1. Adrian Johnson says:
    July 28, 2011 at 3:13 pm

    net localgroup Administrators

    1. Jeffery Hicks says:
      July 28, 2011 at 5:09 pm

      Nothing wrong with an old school approach although you can’t use this remotely as is.

  2. BJT says:
    August 4, 2011 at 9:23 am

    Hi,
    When I run the script, the groups aren’t reported. Any ideas as to how solve that?
    BJT

    1. Jeffery Hicks says:
      August 4, 2011 at 9:27 am

      The script file only defines the function so if you just run the script nothing will happen. You need to either copy and paste the function into your PowerShell session or dot source the script like this:

      PS C:\> . path-to-script \get-wmiadmin.ps1

      Then you should be able to run: Help Get-LocalAdministrators
      If that works, you can then run the function, Get-LocalAdministrators

  3. BJT says:
    August 4, 2011 at 9:45 am

    OK, I forgot to mention that I put the function in a script from which I called the function (last line: Get-Content ‘G:ScriptsnUser – hbwToevoegenEnOpvragenLocalAdminsservers.txt’ | Get-LocalAdministrators -AsJob)
    I do get the usernames, but no groupnames; they stay empty. I also changed the line with the WMI filter to only report the groups, but that had no effect either.
    $AdminsGroup.GetRelated() | Where-Object {$_.__CLASS -match “Win32_UserAccount|Win32_Group”}

    I always find it difficult to find the correct classes, and I’m not sure this Win32_Group class is the good…

    1. Jeffery Hicks says:
      August 4, 2011 at 9:52 am

      Can you get the function to work properly for the local computer?

      1. BJT says:
        August 4, 2011 at 10:23 am

        Than the script seems to hang; tried it on 2 different servers (W2K3 and W2K8).

      2. Jeffery Hicks says:
        August 4, 2011 at 11:36 am

        These are member servers,right? Does using -verbose provide any more info? Does it work if you run interactively on those servers?

    2. Jeffery Hicks says:
      August 4, 2011 at 9:57 am

      This function doesn’t list groups, it only retrieves the local Administrators group and then enumerates the members. Or are you saying it doesn’t return groups that belong to the local administrators group?

      1. BJT says:
        August 4, 2011 at 10:32 am

        Correct: it doesn’t return the groups that are added to the local admin group. I know there should be at least one domain admin group.

      2. Jeffery Hicks says:
        August 4, 2011 at 3:56 pm

        It took me awhile to get there but I get the same results as well, although I thought it tested fine before. I don’t get groups where querying remotely. Double-checking my function.

      3. Jeffery Hicks says:
        August 4, 2011 at 5:08 pm

        I think I was misled earlier because some of my account names looked like groups but they weren’t. The bottom line is that this won’t work as written without some extra steps on your part. As I suspected, the command to get the Win32_Groups objects involves another hop to query a domain controller. This type of 2nd hop is not permitted by default for security reasons. Now, you can get this to work, but you must enable CredSSP on the client, where you are running the script and all the computers you want to query. I don’t have room to go into all the gory details now. Take a look at http://rkeithhill.wordpress.com/2009/05/02/powershell-v2-remoting-on-workgroup-joined-computers-%E2%80%93-yes-it-can-be-done/ on how to get started. The same concepts apply to a domain. Once implemented you can use Invoke-Command. I put the function in a script followed by a command to run the function.

        PS S:\> invoke-command -FilePath C:\work\get-admins.ps1 -comp chi-fp01.globomantics.local -cred $cred -auth credssp

        You have to include a credential object and specify the authentication.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d