Security Ripcord

Cutaway Security Blog and Projects

Iterating PowerShell Objects - Get-NetLocalGroup Examples

Starting out with PowerShell can be confusing. Google helps. Code from PowerSploit, PoshSec, and others can help, but just following the project coding examples will eventually be confusing for new situations. Actually coding is the only way to get over the hump of understanding how to use PowerShell objects, comparisons, and loops. Even with experience, each situation is going to be different and confusing depending on the type of object, command let being used, and how the pipeline impacts the situation.

The following is an example of some information gathering I recently attempted. This will be a walk-thru of the steps I attempted before figuring out (with help from 3nc0d3r) actually worked in the end. For those readers that are familiar with PowerShell this will either be a “yeah, I went thru that” or a “oh, that was obvious” story. For the rest, this will be a series of commands that can be run to experience the output and frustration of dealing with PowerShell objects. The commands will be provided. It will be up to the reader to play along and determine the output. For those readers that don’t have time to play along, I will provide a brief description of the results. Play along for more details.

These examples will work better on systems that are connected to an Active Directory (AD) environment. However, these commands will work just as well on a system that is not connected to an AD environment. Tool required is the Get-NetLocalGroup command from the development branch of the PowerSploit project. For Microsoft Windows systems, the development branch can be access by installing Git software. One method for doing this is installing Git for Windows and then installing TortoiseGit. Once PowerSploit is installed in C:\Windows\System32\WindowsPowerShell\v1.0\Modules the Git branch can be switched to “dev” which will provide several features for PowerShell version 5+.

PowerSploit Functionality Test

Once PowerSploit is installed a quick functionality test can be run by listing all of the groups on the local system. If working the command will display the server name, group name, Security Identifier (SID), and description for each group on the system.

PS C:\> Get-NetLocalGroup -ListGroups

If this command failed then the PowerSploit module needs to be imported: import-module C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PowerSploit. If this fails “because the execution of scripts is disabled” then the PowerShell Execution Policy needs to be modified.

Once the groups have been listed the users on the system should be listed. Depending on the PowerShell version will depend on the type of command used to list the users. For PowerShell version 2.0 just calling the Get-NetLocalGroup command should provide a list of user and account settings. For other version of PowerShell, most specifically version 5, the command will return a GetType error. Thus the PowerSploit development branch is required as this version provides the -API option to gather account information using “API calls instead of the WinNT service provider.”

PS C:\> Get-NetLocalGroup -API

This command should return a list of users that are members of the local system’s “Administrators” group. To get the users of a specific group the -GroupName option can be provided to Get-NetLocalGroup. For instance, the same output will be provided by the following command.

PS C:\> Get-NetLocalGroup -API -GroupName "Administrators"

Combining PowerSploit Cmdlets

The strength of PowerShell is to chain Cmdlets. Chaining is referred to as “piping” on Cmdlet to another. It was the primary goal of this effort. I wanted to list all of the groups on a system and then determine which users were assigned to these groups. This would get me a list of all of the local and domain accounts configured to access the system while also providing an understanding of the permissions assigned to these accounts.

Since PowerShell is object-based the properties returned by a Cmdlet are easily accessible. When piping the results of one command to another the properties can be referenced by $_. Each of the columns returned by Get-NetLocalGroup -ListGroups should be easily accessible by referencing $_.Server or (the column data required for these examples) $_.Group. Unfortunately, doing this produces an error.

PS C:\> Get-NetLocalGroup -ListGroups | $

Basically, PowerShell does not understand the referenced properties. Thus the command needs to be more specific. Selecting specific property is done using the Select-Object Cmdlet. Adding this to the command should output the Group property information from the initial command. While it works the results are still the full output from the original command.

PS C:\> Get-NetLocalGroup -ListGroups | Select-Object $

PowerShell needs the command to be more specific. Reading the help page Select-Object command explains that the -ExpandProperty parameter is used to select a specific property. Adding this parameter provides a list of Groups on the system.

PS C:\> Get-NetLocalGroup -ListGroups | select-object $ -ExpandProperty group

Piping Properties to Another Cmdlet

Getting a list of users for all groups should be as easy as piping the Group names from this command to the Get-NetLocalGroups -API command. Strangely, this command returns nothing.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group| Get-NetLocalGroup -API -GroupName $_

Okay, maybe the property needs to be specifically referenced since it may still be the original object from the first command. Same results, nothing. Some readers may have noticed that this command leverages select instead of Select-Object. These are the same Cmdlets. The select Cmdlet is an alias of Select-Object.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | Get-NetLocalGroup -API -GroupName $

Cutaway Smash PowerShell. PowerShell laughs.

Yes, sometimes I get a bit aggravated with things that should make sense. But, before I do smash things I reach out to my friends (Thank you, 3nc0d3r). Adam helped me realize that the Select-Object part of the command is returning an array (I want to call them a list very badly. PYTHON RULES!!!!) of strings that are the Group names. Reviewing the help page for Get-NetLocalGroup more closely would have helped me realize that the -GroupName parameter only takes a single value. Note the difference between -GroupName and -ComputerName from the help page. -ComputerName accepts a <String[]> object which is an array. Whereas -GroupName only accepts a <String> or a single value.

Get-NetLocalGroup [[-ComputerName] <String[]>] [-ComputerFile <String>] [-GroupName <String>] [-API] [<CommonParameters>]

Since the pipeline is sending an array to the final Cmdlet the elements need to be iterated. The ForEach-Object Cmdlet is designed to iterate over a list or objects. Typically, I prefer to assign a variable name to the elements for easy reference in the loop. But, again, this didn’t work.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach ($name in $_) {Get-NetLocalGroup -API -GroupName $name}

Neither did all of the other ways. Yes, I’m a glutten for punishment. But I don’t want to overlook something obvious by not trying quick changes.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach ($name in $ {Get-NetLocalGroup -API -GroupName $name}
PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach ($name in $_) {Get-NetLocalGroup -API -GroupName $}
PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach ($name in $ {Get-NetLocalGroup -API -GroupName $}

Looking back at the example Adam provided me I realized that he used ForEach-Object but he did not try to reference each element. He exampled worked but ended in an error.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach-object {Get-NetLocalGroup -API -GroupName $_}

Troubleshooting Cmdlet Output

While the command may have output everything, the fact that it ended in an error bugged me. Adding a few lines to this code helps identify the offensive element. Each system will be different. This command failed while processing the Guests group on this system.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach-object {Write-Host "`n====";$_;Write-Host "====";Get-NetLocalGroup -API -GroupName $_}

PowerShell actually has methods for handling errors gracefully. Each Cmdlet accepts a -ErrorAction parameter. Passing this parameter the SilentlyContinue value generally allows a command to continue through errors. In this case, passing this value to the Get-NetLocalGroup and ForEach-Object Cmdlets still resulted in the same results.

PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach-object -ErrorAction SilentlyContinue {Write-Host "`n====";$_;Write-Host "====";Get-NetLocalGroup -API -GroupName $_}
PS C:\> Get-NetLocalGroup -ListGroups | select group -ExpandProperty group | foreach-object {Write-Host "`n====";$_;Write-Host "====";Get-NetLocalGroup -ErrorAction SilentlyContinue -API -GroupName $_}

Why Do Single Line Commands When You Can Do So Much More

All of this work and I still haven’t accomplished the simple task of getting a list of users in each group on the system. Yes, there has to be easier ways, but I am too invested in this process to give up now. It was at this point that I broke down and decided that doing everything on one line was not necessary.

PowerShell can store the values returned by commands. The following command will store the list of group names in the $grps variable. This variable can then be referenced in other follow-on commands. This is also excellent for development as scripts are usually a combination of command and variable interactions rather than a single pipeline command set.

PS C:\> $grps = Get-NetLocalGroup -ListGroups | select Group -ExpandProperty Group

Once the command is run the contents of $grps can be easily listed.

PS C:\> $grps

Combining this with the previous loops outputs the users from each group. Frustratingly, this command is actually much more successful than the previous, single-lined, command. While there are still errors, this command iterates over these errors and processes all of the following group names. I have no clue why this occurs, but, I’ll take it.

PS C:\> foreach ($g in $grps){Get-NetLocalGroup -API -GroupName $g}

Now the output just needs to be organized according to the group.

foreach ($g in $grps){Write-Host "====";$g;Write-Host "====";Get-NetLocalGroup -API -GroupName $g}

Wait, There Are Group Accounts in My User Accounts

No, I am not a perfectionist. Yes, I need useful output to help others understand and be able to assess risk. Thus, the presence of group accounts in the output for user accounts is a distraction. These extra objects need to be removed.

Each account object returned using the -API option includes the computer name, account name, Security Identifier (SID), IsGroup, and IsDomain values (try running the command without the -API in PowerShell version 2 to see the additional fields it provides). Using the methods already described, each of these properties can be referenced from each object.

To do this each of the outputs for individual local account groups needs to be stored and processed individually. Tom Liston will be happy to hear that PowerShell leverages Perl-like hashes instead of Python-esk dictionaries. Doing so requires a little setup by defining the hash variable and then processing information into it. To speed this up a little and to demonstrate the power of PowerShell scripting, I have added comments inline with each command to explain the storage and processing of each object and properties.

# Get a list of all of the group names for a system
$grps = Get-NetLocalGroup -ListGroups | select Group -ExpandProperty Group

# Create a hash table
$ghash = @{}

# Iterate thru objects and create an array for each value
foreach ($g in $grps){$ghash[$g] = $()}

# Print out all accounts stored for each local group
foreach ($g in $ghash.Keys) {if ($ghash.$g) {; write-host "===========";write-host "#" $g; write-host "===========";$ghash.$g}}

# Print out only user accounts for each local group
foreach ($g in $ghash.Keys) {if ($ghash.$g) {write-host "`n==========="; write-host "#" $g; write-host "==========="; foreach($u in $ghash.$g){if(!($u.IsGroup)) {$u} } } }


This post has evaluated how PowerShell uses objects and properties to provide a robust administrative experience. It has provided various methods and pitfalls when iterating and leveraging these objects and properties to process and pipe information to additional Cmdlets. Users new to any programming language and shell are going to experience a learning curve prior to effective and consistent implementation. Hopefully this post will help each reader move through this process more quickly and also provide a good point of reference when a command line or script doesn’t work.


Thank you to: 3nc0d3r, harmj0y, cptjesus, and _wald0.

Go forth and do good things,
Don C. Weber