The Admin’s First Steps: Local Group Membership

Summary: Richard Siddaway talks about using Windows PowerShell to discover the membership of local groups.

Hey, Scripting Guy! Question Hey, Scripting Guy! I’ve just starting using Windows PowerShell to administer my systems, and I’ve been told I need to check the membership of local groups on all my servers. How can I do that?

— AK

Hey, Scripting Guy! Answer Hello AK,

Honorary Scripting Guy, Richard Siddaway here today—filling in for my good friend, The Scripting Guy. Windows PowerShell is great because you have a number of ways to perform most tasks. You’ll see an example of that in this post, where there are three ways to solve this issue. This is a strength but also a weakness because it can confuse people who are starting to use Windows PowerShell. My advice has always been to find something that works and stick with it. You get more issues solved that way.

When we look at local groups, the most important is the Administrators group. This bestows full control of the system and allows you to do anything to that machine. We’ll use that group as an example throughout this post, but the ideas can be applied to any local group.

Of the three ways to find local group membership, the oldest method uses the WinNT ADSI provider. ADSI is a scripting interface to directory services. It’s normally used for scripting against Active Directory, but you can also use it against local machines.

$group = [ADSI]”WinNT://./Administrators”

 @($group.Invoke(“Members”)) |

foreach {$_.GetType().InvokeMember(“Name”, ‘GetProperty’, $null, $_, $null)}

The ADSI scripting interface isn’t directly available in Windows PowerShell in the same way it is in VBScript. Instead, you have to use the System.DirectoryServices.DirectoryEntry .NET Framework class, which provides a wrapper for ADSI. A shortcut, known as a type accelerator has been provided so you don’t have to type the whole class name. To add confusion, the type accelerator is known as [adsi] or [ADSI]—case doesn’t matter.

The WinNT provider is used to access the local machine by using “.” to represent the system together with the name of the group—in this case, Administrators. You then have to use the Invoke() method of the $group object to get the members that you can pipe to Foreach-Object, which uses the InvokeMember() method to get the member name. The output will look something like this:



Personally, I don’t like using “.” to represent the local machine. It’s easy to overlook, and I have seen problems when using it with WMI. I prefer to use $env:COMPUTERNAME, and pick the machine’s name from the environmental variables:

$group =[ADSI]”WinNT://$($env:COMPUTERNAME)/Administrators”

 @($group.Invoke(“Members”)) |

foreach {$_.GetType().InvokeMember(“Name”, ‘GetProperty’, $null, $_, $null)}

This is OK if your machine is in a workgroup; but in a domain, you might get answers like this:


Domain Admins


It’s not easy to separate local users from domain accounts by using this approach. Luckily, you have some alternatives. One of the alternatives involves using WMI. When I’m working with WMI, I find that the CIM cmdlets introduced in Windows PowerShell 3.0 are the easiest to work with:

$group = Get-CimInstance -ClassName Win32_Group  -Filter “Name = ‘Administrators'”

Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount |

select -ExpandProperty Caption

Use the Win32_Group class to get the WMI object that represents the group. Use that object in Get-CimAssociatedInstance to find the Win32_UserAccount instances that are associated with that group. On a workgroup machine, you get this:



And on a domain machine, you get this:



Now you can see which of the results are local accounts and which are domain accounts. But (and isn’t there always a but?) you don’t get the nested domain groups. You need to find the associated groups, which simply involves another call to Get-CimAssociatedInstance:

$group = Get-CimInstance -ClassName Win32_Group  -Filter “Name = ‘Administrators'”

Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount |

select -ExpandProperty Caption

Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_Group |

select -ExpandProperty Caption

Because you are displaying the same data, you can output from multiple calls (generally its viewed as a bad thing), and you get something like this:



MANTICOREDomain Admins

Now you can see the domain and machine name, so you know which entries are local, and you get the nested groups. If you can’t use the CIM cmdlets, you can fall back on the WMI cmdlets.

$query = “ASSOCIATORS OF {Win32_Group.Domain=’$($env:COMPUTERNAME)’,Name=’Administrators’} WHERE ResultClass = Win32_UserAccount”

Get-WmiObject -Query $query | Select -ExpandProperty Caption

$query = “ASSOCIATORS OF {Win32_Group.Domain=’$($env:COMPUTERNAME)’,Name=’Administrators’} WHERE ResultClass = Win32_Group”

Get-WmiObject -Query $query | Select -ExpandProperty Caption

This is a bit more complicated than the CIM cmdlet version because you have to create a WMI Query Language (WQL) query to discover the associated Win32_UserAccount and Win32_Group instances. The difficult part is getting the contents of the {} correct. The easiest way is to look at the __RELPATH property of the returned object when you use the following command:

Get-WmiObject -Class Win32_Group -Filter “Name = ‘Administrators'” | fl *

Remember to change the double quotes to single quotes to make the query work.

You get the same results as with the CIM cmdlets:



MANTICOREDomain Admins

The third and final way to get this data is to drop back to the .NET Framework class and use the System.DirectoryServices.AccountManagement classes that were introduced in .NET Framework 3.5.

Add-Type -AssemblyName System.DirectoryServices.AccountManagement

$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine

$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $env:COMPUTERNAME

$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName

$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, ‘Administrators’)

$group.Members |

select @{N=’Domain’; E={$_.Context.Name}}, samaccountName

This may look a bit more complicated, but most of it is concerned with defining the objects that you want to work with. The script starts by using Add-Type to load the System.DirectoryServices.AccountManagement .NET Framework classes into Windows PowerShell. Not all.NET Framework classes are loaded by default—only those that are thought to be of the most use to the most people.

After that, you use the ContextType class to say that you are looking at the local machine rather than the domain. The System.DirectoryServices.AccountManagement classes aren’t used much, which is a shame because they are powerful and they span local and domain level activities.

You can then use the context type and the local machine name to create a context. The IdentityType defines what you are using to identify objects—in this case, the SamAccountName.

Now you can use all of that data in the FindByIdentity() method of the GroupPrincipal class to find the group. The Members property holds the members as you might expect; but the final piece is that you have to dig into the Context of the member to discover whether the object comes from the domain or from the local machine.

Luckily the code runs much faster than I can write about it and you get results like this:

Domain             SamAccountName

——                  ————–

WIN12R2         Administrator   Domain Admins   Richard 

Working through these three methods illustrates an important point: If there are multiple methods available, you should investigate them to discover how they work, any issues that occur for you, and which one works best with your skills and way of working.

The last part of this journey is to turn your code into a function that you can use across multiple remote machines. I’m going to use the System.DirectoryServices.AccountManagement approach so I can illustrate how these classes can be used against remote machines without the need to rely on Windows PowerShell remoting.

function get-localgroupmember {





   [string[]]$computername = $env:COMPUTERNAME



Add-Type -AssemblyName System.DirectoryServices.AccountManagement

$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine




foreach ($computer in $computername) {

  $context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer

  $idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName

  $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, ‘Administrators’)

  $group.Members |

  select @{N=’Server’; E={$computer}}, @{N=’Domain’; E={$_.Context.Name}}, samaccountName

} # end foreach

} # end PROCESS



“Win12R2”, “W12SUS” | get-localgroupmember

Start by defining the function and parameter. In this case, you’re only interested in the remote computer name. When you use an array as the type, you can also do this:

get-localgroupmember -computername Win12R2, w12sus

…but only if you have the Foreach loop in the PROCESS block. (You don’t have to capitalize these blocks of code—I do it to make my code more readable.)

You only need to load the .NET Framework classes, define the context, and identity types once. So those steps go into the BEGIN block, which executes once when the first object in the pipeline hits the function.

The PROCESS block loops through the computers and creates a context for each machine. The Administrators group is found and the membership list is extracted. You are working against remote machines so the computer is added to the output to identify to which machine the results apply.

You can use this function on the pipeline as shown or interactively by passing one or more computer names to the function.

If you want to extend the use of this script you could:

  • Make the group name a variable.
  • Save the results for comparison against a future examination to track changes.

AK, that’s how you use Windows PowerShell to check the membership of your local groups. Next time I’ll have another idea for you to try as you bring more automation into your environment.

If you would like to read more in this series, check out these posts:

Bye for now.


Richard Siddaway is based out of the UK, and he spends his time automating anything and everything for Kelway, Ltd. A Windows PowerShell MVP since 2007, Richard is a prolific blogger, mainly about Windows PowerShell (see Richard Siddaway’s Blog: Of PowerShell and Other Things), and a frequent speaker at user groups and Windows PowerShell conferences. He has written a number of Windows PowerShell books: PowerShell in Practice, PowerShell and WMI, PowerShell in Depth (co-author); and PowerShell Dive (co-editor). He is currently finishing Learn Active Directory Management in a Month of Lunches, which features a lot of Windows PowerShell. All of the books are available from Manning Publications Co.

Thanks, Richard.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Comments (9)

  1. Anonymous says:

    I got a script to get the membership and I’m using as this:

    Get-LocalGroupMembership -Computername server1 -Group Administrators

    but I’m getting the following error:

    The term ‘Get-LocalGroupMembership’ is not recognized as the name of a cmdlet, function, script file, or operable progr
    am. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Suggestion [3,General]: The command Get-LocalGroupMembership was not found, but does exist in the current location. Wind
    ows PowerShell doesn’t load commands from the current location by default. If you trust this command, instead type “.Get-LocalGroupMembership”. See “get-help about_Command_Precedence” for more details.

    Previously I ran the script as .Get-LocalGroupMembership.ps1 with the same result, could you please help me with this?

  2. Anonymous says:


    This blog is very useful and is very closely related to the topic on which iam working, my question is "i need a script/query based on wmi/wql that find out the time when the user was added to local administration group on this local computer"

    The script will be run locally on the machine

  3. Neil Fairall says:

    Thanks to Bill Prentice for confirming the issue I am seeing. Guess I will have to use a more complex method.

  4. David Wyatt says:

    Even if you have to fall back on Get-WmiObject, there's a slightly easier way to find the associated members.  Instead of writing an "ASSOCIATORS OF" query, you can do this:

    $group = Get-WmiObject Win32_Group -Filter 'Name="Administrators"'

    $group.GetRelated('Win32_UserAccount') | Select-Object -ExpandProperty Caption

  5. ss says:

    Hi..Iam poor at could we apply this script on list of servers…pls explain

  6. Bill Prentice says:

    The last method – using System.DirectoryServices.AccountManagement – will only work on machines which have no ‘orphaned’ SIDs, i.e. the SIDs can be resolved. If you have a normal network there will be orphaned SIDs from domain accounts or groups that have
    been deleted at some point. Those will cause the third method to fail.

    This is a design decision and has been true since at least July of 2009 (
    towards the bottom is MS support’s comments). Microsoft has yet to fix this 5 years on.

  7. RAbel says:

    $Administrators = [ADSI]"WinNT://$env:COMPUTERNAME/Administrators,group"
    $members = $Administrators.Invoke("Members")
    $members = $members | %{([ADSI]$_).Name}

  8. DAB says:

    Nice one RAbel

Skip to main content