Training
Get a free hour of SANS training

Experience SANS training through course previews.

Learn More
Learning Paths
Can't find what you are looking for?

Let us help.

Contact us
Resources
Join the SANS Community

Become a member for instant access to our free resources.

Sign Up
For Organizations
Interested in developing a training plan to fit your organization’s needs?

We're here to help.

Contact Us
Talk with an expert

Month of PowerShell - Using The Grouping Operator (a.k.a. What are all these ()?)

From the things I wish someone told me when I started with PowerShell department: using the PowerShell grouping operator!

Authored byJoshua Wright
Joshua Wright

#monthofpowershell

When I first started working with PowerShell, commands like this were confusing:

PS C:\temp> (Get-Date -Date ((Get-Date).ToUniversalTime()) -UFormat %s)
1657915725.84934

PowerShell uses parentheses for a few different functions, well-documented online. In this article we'll look at the grouping operator [code]()[/code].

Many languages use parentheses to specify operator precedence in expressions:

PS C:\temp> (3 + 5) * 2
16

When PowerShell sees something inside [code]()[/code], it evaluates that first, then it evaluates the remainder of the expression. This is pretty intuitive, since we use it for lots of math equations too. The grouping operator works the same way: statements inside the parentheses are executed first (within the current statement in the pipeline).

Let's look at a practical example.

A Grouping Operator Example

I have two image files with similar file names. File size and date/time are a match too, but I want to know if the files are exactly the same. This is a job for [code]Get-FileHash[/code]:

PS C:\temp> Get-ChildItem


    Directory: C:\temp


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         7/15/2022   8:01 PM         453684 IMG_3306 copy.jpg
-a----         7/15/2022   8:01 PM         453684 IMG_3306.jpg


PS C:\temp> Get-FileHash .\IMG_3306.jpg

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          41F6DDF12CA703FCB428ED8FF702130613A905DE297FB5EBB577A06CC394C440       C:...


PS C:\temp> Get-FileHash '.\IMG_3306 copy.jpg'

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          9A239588772945419F4D6BBC3DDBF870A60A30C028269B6FF5D20EF4E3A8D357       C:...

We see that the different SHA256 hashes indicates that the files are not the same, but what if we wanted to test this condition in a script? We could do something like this:

PS C:\temp> $hash1 = Get-FileHash .\IMG_3306.jpg
PS C:\temp> $hash2 = Get-FileHash '.\IMG_3306 copy.jpg'
PS C:\temp> $hash1.Hash -EQ $hash2.Hash
False

By declaring the [code]Get-FileHash[/code] output as variables, we can access the [code]Hash[/code] property for each file and test if they are equal using the PowerShell [code]-Eq[/code] operator. The hash values are different, so we get the [code]False[/code] result.

This approach is fine, and in a script it's perfectly legible, but it declared two variables, [code]$hash1[/code] and [code]$hash2[/code]. This is not necessary, since we can use the grouping operator instead:

PS C:\temp> (Get-FileHash IMG_3306).Hash -EQ (Get-FileHash '.\IMG_3306 copy.jpg').Hash
False

In this example we surround the [code]Get-FileHash[/code] steps in parentheses, and access the [code]Hash[/code] property using dot notation. PowerShell processes the expressions from left to right, executing the statements within the parentheses first before accessing the [code]Hash[/code] property and comparing the two values with [code]-EQ[/code].

Variable Declaration Unnecessary

Once you get the hang of using the grouping operator, you start to realize you don't need to declare variables to access the properties or methods of a PowerShell command. For example, all PowerShell arrays have a property called [code]Count[/code] which is the number of elements in the array. You can access this value with any command that returns an array type:

PS C:\temp> Get-ChildItem


    Directory: C:\temp


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         7/15/2022   8:01 PM         453684 IMG_3306 copy.jpg
-a----         7/15/2022   8:01 PM         453684 IMG_3306.jpg


PS C:\temp> (Get-ChildItem).Count
2

In my part 4 article on working with the event log I used an example where I counted the number of event logs available on Windows:

PS C:\temp> (Get-WinEvent -ListLog *).Count
442

Using the grouping operator we can execute the specified command ([code]Get-WinEvent -Listlog *[/code]), then access a member property using dot notation. Neat!

When we work with dates, we use a special object type for date and time data. In PowerShell, it's common to identify the object type using the grouping operator and the [code]GetType()[/code] method:

PS C:\Temp> Get-Date

Saturday, July 16, 2022 10:42:03 AM


PS C:\Temp> (Get-Date).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     DateTime                                 System.ValueType

Here we see the type is [code]DateType[/code]. We can use [code]Get-Member[/code] to identify the properties and methods available for a [code]DateType[/code] object:

PS C:\Temp> Get-Date | Get-Member


   TypeName: System.DateTime

Name                 MemberType     Definition
----                 ----------     ----------
Add                  Method         datetime Add(timespan value)
AddDays              Method         datetime AddDays(double value)
AddHours             Method         datetime AddHours(double value)
AddMilliseconds      Method         datetime AddMilliseconds(double value)
AddMinutes           Method         datetime AddMinutes(double value)
AddMonths            Method         datetime AddMonths(int months)
AddSeconds           Method         datetime AddSeconds(double value)
AddTicks             Method         datetime AddTicks(long value)
AddYears             Method         datetime AddYears(int value)
CompareTo            Method         int CompareTo(System.Object value), int CompareTo(dat...
Equals               Method         bool Equals(System.Object value), bool Equals(datetim...
GetDateTimeFormats   Method         string[] GetDateTimeFormats(), string[] GetDateTimeFo...
GetHashCode          Method         int GetHashCode()
...

Let's say you want to get a date/time for 2 days from now. You can use the grouping operator to invoke [code]Get-Date[/code], then call the [code]AddHours()[/code] method:

PS C:\temp> (Get-Date).AddHours(48)

Monday, July 18, 2022 10:47:19 AM

Remember, statements inside the parentheses are executed first. Here, [code]Get-Date[/code] is executed first, which returns a [code]DateType[/code] object. The [code]48[/code] inside of the next set of parentheses is also evaluated, but since it's a literal and not an expression nothing is executed. Then the result of calling [code]Get-Date[/code] (the [code]DateType[/code] object) is used to invoke the [code]AddHours()[/code] method.

You could have accomplished this same task (getting the date/time 2 days from now) using variables instead:

PS C:\temp> $now = Get-Date
PS C:\temp> $twodaysfromnow = $now.AddHours(48)
PS C:\temp> $twodaysfromnow

Monday, July 18, 2022 10:51:48 AM

This is is also fine. PowerShell purists may point out that this solution declares an unnecessary variable, but it is a perfectly acceptable way of solving the problem at hand.

Simpler Differential Analysis

In my article on threat hunting with PowerShell differential analysis I showed how you can build a baseline of configuration data for a system in a known-good state, then compare the current state of a system under investigation to identify a threat actor. In my SEC504 class we go a bit further in our analysis of how a Command & Control (C2) framework looks when it migrates into an existing process to hide its presence on the system, but we apply the same differential analysis process.

Let's take a look. First, on Windows I'll get a list of the DLLs associated with the Windows Explorer process, saving the output to [code]explorer-modules-baseline.txt[/code]:

PS C:\Temp> Get-Process -name explorer -Module | Select-Object ModuleName | Out-File explorer-modules-baseline.txt
PS C:\Temp> Get-Content .\explorer-modules-baseline.txt -First 10

ModuleName
----------
Explorer.EXE
ntdll.dll
KERNEL32.DLL
KERNELBASE.dll
msvcp_win.dll
ucrtbase.dll
combase.dll

This file has a list of all the DLLs normally associated with the Explorer process. Next, as an attacker I'll use Metasploit Meterpreter as my C2 to migrate to the Explorer process, leaving the initial exploit process and blending in to an otherwise legitimate process:

meterpreter > migrate -N explorer.exe
[*] Migrating from 8268 to 4332...
[*] Migration completed successfully.

Now, I want to use differential analysis to identify if the normal DLL list associated with the Explorer process has changed using [code]Compare-Object[/code]. I'll create a new file called [code]explorer-modules-current.txt[/code] with the current DLL information:

PS C:\Temp> Get-Process -Name explorer -Module | Select-Object Modulename | Out-File explorer-modules-current.txt
PS C:\Temp>

To use [code]Compare-Object[/code], I need to supply [code]-ReferenceObject[/code] and [code]-DifferenceObject[/code] arguments. [code]Compare-Object[/code] won't read the files directly, but I can use the grouping operator with [code]Get-Content[/code] to read the files and return the object data:

PS C:\Temp> Compare-Object -ReferenceObject (Get-Content .\explorer-modules-baseline.txt) -DifferenceObject (Get-Content .\explorer-modules-current.txt)
InputObject                                              SideIndicator
-----------                                              -------------
PSAPI.DLL                                                =>
cdprt.dll                                                =>
Windows.Globalization.dll                                =>
execmodelclient.dll                                      =>
execmodelproxy.dll                                       =>

These 5 DLLs are added by Metasploit Meterpreter during the [code]migrate[/code] operation, a valuable Indicator of Compromise (IoC).

Conclusion

In this article we looked at how the PowerShell grouping operator works. We can use the grouping operator instead of declaring a temporary variable to access an object's properties and methods (e.g., [code](Get-Date).AddHours(12)[/code]), and as a way to eliminate temporary variables (e.g., [code](Get-FileHash IMG_3306).Hash -EQ (Get-FileHash '.\IMG_3306 copy.jpg').Hash[/code]).

Remember: statements inside the parentheses are executed first (within the current statement in the pipeline). Next time you see a confusing PowerShell statement with lots of parentheses, just look to the inner-most parentheses first and work your way out to understand the order of execution. Once you grasp that process, complex PowerShell statements become a lot easier to break down.

-Joshua Wright

Return to Getting Started With PowerShell


Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.

Month of PowerShell - Using The Grouping Operator (a.k.a. What are all these ()?) | SANS Institute