I wrote a function a while back that is used to query a local group on a remote or local system (or systems) and based on the –Depth parameter, will perform a recursive query for all members of that group to include local and domain groups and users. I felt that it was something worth sharing out just in case someone has a need for it. It also isn’t the same script that I wrote back in January here: https://learn-powershell.net/2013/01/22/find-and-report-members-of-a-local-group/

To avoid an issue with circular groups (probably not the technical name for it), I use a hash table to manage the local/domain groups to ensure that they are not queried again later on. By circular groups, I am talking about groups that are members of a parent group that may have the parent group listed as a member later on down the group membership. A more understandable example is here:

Because Administrators exist in Group3 and Administrators has Group3 as a member, this madness will never stop or will stop when it hits some limit on depth. Regardless, this would be a very annoying thing to have happen.

Nothing really new here. I am setting up my parameters for Computername, a Group name (which defaults to’Administrators and the Depth limit. You will notice that I am using [int]::MaxValue which is 2147483647 which is my ”unlimited” recursion. If someone has a nested group that far down, then my hats off to you!

Get-LocalGroupMember is my first helper function when it comes to getting members of a group. In this case, I am gathering the members of a local group. A number of possibilities exist here such as whether the member is actually a User, or if it is another group that is either local on the system or a domain group. Based on the group type, it will either call itself again for the local group or call the Get-DomainGroupMember function which will be explained next. You can tell from my $Groups variable that it is the hash table used to make sure that the group hasn’t already queried as well as the counter which helps to determine the depth level of the current group and its members.

The Get-DomainGroupMember is my second helper function used to get group members. As the name implies, this will gather the group memberships that have been queried. the NetBIOSDomain name is also used here to find out the actual distinguishedName of the group so I can be used with the [ADSI] accelerator to make the query for group members. As with my Get-LocalGroupMember function, this makes use of the same hash table and counter to handle circular groups and recursion depth.

Process {
#region Get Local Group Members
ForEach ($Computer in $Computername) {
$Script:Groups = @{}
$Script:Counter=0
# Bind to the group object with the WinNT provider.
$ADSIGroup = [ADSI]"WinNT://$Computer/$Group,group"
Write-Verbose ("Checking {0} membership for {1}" -f $Group,$Computer)
$Groups[$Group] += ,'Local'
Get-LocalGroupMember -LocalGroup $ADSIGroup
}
#endregion Get Local Group Members
}

Here is where everything happens for the queries. Everything that exists in the Process block is here for a reason so I am not needlessly making the same variable creation for each computer passed through the pipeline. Each computer will have a fresh counter and new hash table to handle recursion depth and circular groups, respectfully. Because the first group queried is the one that we defined in the Group parameter, it is already known to be a local group and Get-LocalGroupMember is called first.

I also have some runspace stuff in the code as well, but being how I have already talked about this in other articles, I figured it wasn’t worth mentioning again. If you really want to see the code behind the runspaces, check out those articles here.

Ok, enough talk about the code! It is time to see Get-LocalGroupMembership in action.

Get-LocalGroupMembership | Format-Table –AutoSize

You will notice that the groups are only nested 2 levels deep. Also important to notice is that I have some circular groups here with Sysops Admins under Enterprise Admins. Sysops Admins which is also listed under Administrators in which Sysops Admins has… Enterprise Admins listed as a member and thus the circle of member begins! Or at least it would have begun had the code not caught it.

I do not have other remote systems to use –Computername but trust me in that it does work against remote systems.

Get-LocalGroupMembership -Depth 1 | Format-Table –AutoSize

This is an example of setting the Depth parameter to only go 1 level deep.

That is all for this function. Feel free to download it from the Script Repository (link below) and let me know what you think!

Hi Boe,
Awesome Script, thanks for sharing. This script doesn’t work in Powershell 5.0. There is an issue in the Get-LocalGroupMember function. I made the following changes and it seems to work ok. Thanks heaps again for sharing such an excellent script.

Craig, it’s not entirely clear to me exactly what was changed. I see some new entries in italics, but not all of them pass a basic check for syntax, for example:
foreach{([System.DirectoryServices.DirectoryEntry]$)}

I’ve tried to re-type these line for line because of the HTML smart quotes issue but it’s not working correctly.
If you could upload a copy of your modifications that are working, I’d VERY much appreciate it as I’m sure others would also! Thanks!!

Boe, Thanks for this incredible script!
I’d like to know if you update it with ENABLE/DISABLE information?
I didn’t find any script which brings me this information.
Is there any parameter in your script about it?
Thanks!

Boa, Thanks a lot for your script!
I’d like to know if you update it to bring Enable/Disable status?
Is there any parameter to bring that?
I’m looking for a script which can brings it and I didn’t find, anywhere! 😦
Thanks!!!

I am wondering, instead of searching only the “Local Administrators group” on a given server, can the script search all the local groups at once instead of having to specified each local group to search?

i figured out how to run against a list of remote servers using a text file and get-content cmdlet. i output the results to a .csv file. what I’ve noticed is the Name column has the NTID but there’s no reference to a domain. Since we have multiple domains, is there a way to add and show the domain that the member is in and output that to the .csv file?

Hi,
I tried to use your script by using as:
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?

Yeah… I tried running this on Windows 2003 x86 with PS 2.0, and the output is exactly nothing. It doesn’t matter what parameters I include or omit. I’m just trying to pull the membership of the local administrators group; from the server, I run the following:

This should be possible as you can check to see if the object is an account or not and look at the properties of the object (whether local or domain) and determine whether it is disabled or enabled. If I can get some time, I can dive deeper into this, but I think that this is very doable.

Similar to Kevin, we need an output from this script which includes the sAMAccountName attribute of the domain objects as well, but not for comparision purposes, so using the GUID instead won’t work in our situation. I have been unable to determine how to modify the script to generate this required output. Could you point me in the right direction?

Hello,
First i would like to congratulate you for your work and for sharing this amazing script. However , in the scenario where member users are disabled , i would like to add another collum to the output for example isDisabled ( true or false ). i had many attempts to get it work but no success. Can you help me with this ?
Kind Regards

Hello,
First i would like to congratulate you for your work and for sharing this amazing script. However , in the scenario where member users are disabled , i would like to add another collum to the output for example isDisabled ( true or false ). i had many attempts to get it work but no success. Can you help me with this ?
Kind Regards

Great function Mr. Prox. It is extremely useful to me as I am working on auditing local admin access to several servers. One of the things that I have not been able to figure out is for it to show the SAM Account name for users instead of the Display Names. I need to figure out a way to do this so I could cross-reference the results (exported to a CSV) with results of another script pulling the status (enabled/disabled) of the accounts.

We have several members in the groups whose accounts are actually disabled, and I’m trying to show that so when we present the report, people don’t freak out. Any ideas?

Hmm, using your code snippet, I’m getting unique numbers for users, but none of them match the GUIDs in AD. I think I’ll also need to put in a check that if the object is a group, or local, it marks it as such instead of being blank. It is a step in the right direction, and I appreciate the help. (for some reason I can’t reply to your reply.)

I’m fairly new to Powershell, and can figure out the logic behind it, it is just the syntax that I need to learn. Again, thank you for your assistance. For fun, here’s a script I wrote that calls the Get-LocalMembership function (asking for an individual server, or the option to read a list of servers from a text file), then outputting everything into a CSV.