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.
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.”
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.
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.
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.
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.
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.
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
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
-ComputerName from the help page.
-ComputerName accepts a
<String> object which is an array. Whereas
-GroupName only accepts a
<String> or a single value.
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.
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.
1 2 3
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.
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.
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
ForEach-Object Cmdlets still resulted in the same results.
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.
Once the command is run the contents of
$grps can be easily listed.
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.
Now the output just needs to be organized according to the group.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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.
Go forth and do good things,
Don C. Weber