banner

Intro to PowerShell

Last updated 

Photo Credits: Unsplash and Tech Icons

Introduction

I am a huge fan of PowerShell and generally believe it receives less attention and admiration than it deserves. Without ever intending to, I have become a PowerShell advocate, pushing my personal and professional acquaintances to make greater use of it.

(That said, it does have some shortcomings when used as a general-purpose language, which I'll document in this article.)

Most individuals in a technology-related career field will be vaguely familiar with PowerShell as one of two shells distributed with modern Windows operating systems. Generally, it is perceived as an updated replacement for Command Prompt, or CMD.

While it is true that PowerShell is an updated alternative for CMD, it goes far beyond the capabilities that command prompt or any other widely used system shell natively supports - to include Bash or Sh. That's because PowerShell is based on .NET (more on this in the next section), giving it access to native programming features and libraries more akin to Python, Java, or other general-purpose programming languages relying on an interpreter or runtime environment.

PowerShell's extensive feature set combined with its native availability on the vast majority of the world's workstations (all reasonably modern Windows distributions) provides a platform to create and distribute complex scripts, programs, and even GUI apps without compiled binaries or (non-native) dependencies.

Note that PowerShell is cross-platform and can be used on Linux as well.

PowerShell's Runtime Environment

PowerShell is built on .NET and runs on the Common Language Runtime (CLR).

More specifically - older versions of PowerShell were built on .NET Framework, which is Windows-only. Newer versions of PowerShell (6 and up) are open source and run on the cross-platform .NET Core, which - confusingly - is now just called .NET.

So what is .NET?

The .NET CLR is comparable to Java's JVM. It performs JIT (just in time) compilation of supported languages into native code for the host machine to execute.

If you are also unfamiliar with the JVM, consider the explanation provided here:

When a .NET Core application is executed, the CLR loads the executable file and any referenced assemblies into memory. The executable file and the assemblies contain metadata that describes the types, members, and references in the code.

The CLR then locates the entry point of the application, which is usually the Main method in C#, and invokes it. The Main method may call other methods or create objects as part of the application logic.

The CLR uses a just-in-time (JIT) compiler to compile the intermediate language (IL) code into native machine code on the fly as the program runs, optimizing performance. The JIT compiler also performs various optimizations, such as inlining, loop hoisting, and dead code elimination.

The CLR also provides various services to the application, such as memory management, type safety, security, and exception handling. The CLR manages the allocation and deallocation of memory for objects and performs garbage collection to reclaim unused memory. The CLR also checks the type of compatibility and validity of the code and enforces security policies and permissions. The CLR also handles exceptions that occur during the execution of the application and provides debugging and profiling services.

C# is the language most closely associated with .NET; Programmers may also be aware that Unity (the game engine) also runs on .NET and is usually programmed in C#.

Further reading on Geeks for Geeks here

PowerShell benefits from all of the features of .NET, to include the ability to make use of all .NET assemblies available on the local machine.

.NET Terminology

An object is a struct or entity that may contain attributes/properties and/or methods.

Classes are loosely defined as blueprints for specific kinds of objects.

Namespaces are used to organize classes. The default root namespace is System. Each namespace may contain child namespaces (such as System.IO) or classes (such as System.IO.File).

Assemblies are the binary files - usually .dlls - that contain .NET class and namespace definitions.

Defining PowerShell

It is a shell, scripting language, and configuration management tool.

See Microsoft's description here.

A Shell

From Wikipedia: "A shell is a computer program that exposes an operating system's services to a human user or other programs... A shell manages the user–system interaction by prompting users for input, interpreting their input, and then handling output from the underlying operating system (much like a read–eval–print loop, REPL)"

e.g., PowerShell allows us to interact with the OS by making syscalls or calling other executable programs

A Scripting Language

As with most shell languages, PowerShell supports running combinations of saved PowerShell commands as scripts. It supports common programming language features like variables, loops, flow control, and more. As a .NET language, it is significantly more powerful than most shell languages - for example, it can directly manage memory, use pointer arithmetic, etc.

PowerShell scripts are saved with the .ps1 file extension. PowerShell modules (packages of related PowerShell cmdlets, functions, etc.) can include .psm1, .psd1, and .dll files.

A Configuration Management Tool

PowerShell is used extensively for administration of Windows, AD, AAD, and other Microsoft products. Modules can also (and have been) written for the administration of nearly any platform. PowerShell is cross-platform and may be used for the administration of Linux machines - and any other OS that supports .NET Core - though this is less common.

Desired State Configuration (DSC)

PowerShell DSC is a declarative/idempotent configuration management platform

See here for details.

Key Points:

  • If a DSC "resource" is available for a particular feature, a script doesn't have to go through all the steps of enabling it - the config just "declares" the desired end state
  • Regular scripts need extensive logic to handle possible states: the desired config is already in place, not in place, partially in places, etc.. DSC configs just state the desired end state.

These notes will not explore DSC in further detail.

Getting Started

Video intro from SANS: link

Interfaces

PowerShell can be opened in any terminal application, to include Windows Terminal (default on Windows 11 and available on all modern versions of Windows) or conhost (default on older versions of Windows). These are the same options as for cmd (Command Prompt). The system defaults (whether to open PowerShell and CMD in conhost or WT) are configurable. Regardless of configuration, either shell may be started in either terminal application with the following commands:

Start-Process conhost.exe -ArgumentList cmd.exe
Start-Process wt.exe -ArgumentList cmd.exe

Windows also comes with the PowerShell ISE ("Integrated Scripting Environment"). It can be useful for developing scripts if other editors cannot be installed.

On Linux, PowerShell can be opened in any standard terminal program.

Power User Menu

Modern Windows distributions support a "Power User" menu accessed with the Win+X keyboard shortcut. On newer systems, Powershell can be selected from this menu by default. On older systems, cmd is the default - but this can be changed.

Things to Know

ExecutionPolicy

PowerShell has an ExecutionPolicy that determines whether it will allow script execution. Scripts can be blocked entirely or require code signing. The ExecutionPolicy can be viewed and adjusted with [Get|Set]-ExecutionPolicy. The command supports scopes including CurrentUser (i.e., only change settings for the current user) and Process (i.e., change the policy for the current process only).

ErrorAction and WarningAction

Error handling behavior can be adjusted for PowerShell cmdlets by setting ErrorAction (usually accepted as a function parameter by the built-in cmdlets). SilentlyContinue is best when errors should be ignored. WarningAction has similar functionality for (you guessed it) warnings.

Object Orientation

Whereas Bash treats all command outputs and inputs as strings, almost everything in PowerShell is an object with properties and methods. Often, there are more properties and methods than will be displayed by default. (See Get-Member below.)

Specifically, every command, cmdlet, function, etc. in PowerShell that is written in pure PowerShell or based on .NET will produce .NET objects. External binaries (like ipconfig) will return strings.

Standard shell navigation applies:

  • Arrows can be used to scroll through previous commands or move left and right in the current line
  • Ctrl+arrow will move left or right one word at a time
  • Home will take you to the beginning of the current line
  • End will take you to the end of the current line
  • Delete will remove the character in front of your cursor
  • Backspace will remove the character behind your cursor
  • Ctrl+R can be used to search your history
  • 'tab' can be used to autocomplete the text under your cursor with known commands and filepaths

PowerShell's tab completion is excellent for both cmdlets and .NET namespaces, methods, etc. Try this by typing Get- in PowerShell and using the tab key to cycle through all the possible completions of your text.

Helpful Commands

Get-Command - searches all known cmdlets, functions, exe's for the provided pattern. Wildcards supported

Get-Help - get help information for a given command. Similar to manpages on Linux. Help files are not installed by default.

Get-Alias - PowerShell supports aliased commands, which is why ls and cd work - They're actually aliases for Get-ChildItem and Set-Location. Get-Alias displays all currently defined aliases.

Select-Object (alias: Select) - Can be used to select specific properties from an object in a pipeline, select only the first or last n objects, only unique objects... etc.

Where-Object (alias: Where) - Used to filter a set of objects in a pipeline. Syntax: <Command> | Where-Object <PropertyName> -[eq|ne|lt|gt|le|ge|match|notmatch]. There are dozens of comparison operators, see docs here

Get-Member (alias: gm) - Used to view the type and members (properties & methods) of an object. Useful in combination with Select and Where to determine what preoprties are available.

Most (probably all, though I do not know this for sure) .NET objects support the .GetType() method, which will return its type, e.g.:

PS C:\Users\nwm> $regex = [System.Text.RegularExpressions.Regex]::new('[a-zA-Z]{3}')
PS C:\Users\nwm> $regex.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    Regex                                    System.Object

Scripting Language Features

Variables

Variables are instantiated and referenced with a $. They are case-insensitive.

Every object has a type. Variables can be declared with or without a type (PowerShell will infer one if none are provided). Functions may choose to type arguments or allow any type to be passed in. There are restrictions on type conversions.

Types can be set with PowerShell's .NET syntax, e.g. [int]$newVar = 9. All .NET types are supported.

Practical Exercise

Convert an int to a string containing the variable's binary representation.

Answer:

PS C:\Users\nwm> $var = [int]9
PS C:\Users\nwm> [convert]::ToString($var, '2')
1001

Quotes

A backtick (`) is used to escape special characters.

A newline is represented with `n.

Strings with double-quotes will attempt to interpret any of PowerShell's special characters ($,`).

PS C:\Users\nwm> $var = "hello"
PS C:\Users\nwm> Write-Output "$var`n`nworld"
hello

world

Strings with single-quotes do not interpret variables or special characters.

PS C:\Users\nwm> Write-Output '$var`n`nworld'
$var`n`nworld

Multi-line strings can be created with "here-strings," which can use either single or double quotes.

PS C:\Users\nwm> $var = "hello"
PS C:\Users\nwm> $longString = @"
>> $var
>> world
>> "@
PS C:\Users\nwm> echo $longString
hello
world
PS C:\Users\nwm> $longString = @'
>> $var
>> world
>> '@
PS C:\Users\nwm> echo $longString
$var
world

Collection Data Types

The most common collection types in PowerShell are arrays and hashtables.

PS C:\Users\nwm> $array = @("a", "b", "c")
PS C:\Users\nwm> echo $array[0]
a
PS C:\Users\nwm> $hashTable = @{a="b";c="d"}
PS C:\Users\nwm> echo $hashTable["c"]
d

Arrays are immutable, meaning changes to arrays result in new arrays and the copying of existing data. This becomes hugely inefficient with large arrays. More flexible data types are available from .NET in the System.Collections namespace, such as System.Collections.ArrayList. An ArrayList can be created with .NET syntax ([System.Collections.ArrayList]::new()) or PowerShell syntax (New-Object System.Collections.ArrayList).

Loops

PowerShell supports most common loop types. See the following examples.

While

PS C:\Users\nwm> $idx = 0
PS C:\Users\nwm> while ($idx -lt 5) { echo $idx; $idx++ }
0
1
2
3
4
PS C:\Users\nwm> $idx = 0
PS C:\Users\nwm> while ($idx -lt 5) { $idx++; if ($idx -eq 2) { continue }; echo $idx; }
1
3
4
5
PS C:\Users\nwm> $idx = 0
PS C:\Users\nwm> while ($idx -lt 5) { $idx++; if ($idx -eq 2) { break }; echo $idx; }
1

For

PS C:\Users\nwm> for ($i=0; $i -lt 5; $i++) { echo $i }
0
1
2
3
4

ForEach

PS C:\Users\nwm> foreach ($proc in $(get-process | select -First 5)) { echo "Process is $($proc.name)" }
Process is AggregatorHost
Process is AppHelperCap
Process is ApplicationFrameHost
Process is armsvc
Process is audiodg

% is an alias for ForEach. This type of loop can be very useful in pipelines.

Do-While

PS C:\Users\nwm> $idx = 0; do { $idx++; echo $idx } while ($idx -lt 5)
1
2
3
4
5

Flow Control

PowerShell supports if trees and switch statements. E.g.:

PS C:\Users\nwm> $var = "hello"
PS C:\Users\nwm> if ($var -eq "world") {
>> echo 1
>> } elseif ($var -eq "hello") {
>> echo 2
>> } else {
>> echo 3
>> }
2
PS C:\Users\nwm> switch ($var) {
>> "world" { echo 1 }
>> "hello" { echo 2 }
>> Default { echo 3 }
>> }
2

Ranges

A range of numbers can be generated with the .. syntax

PS C:\Users\nwm> foreach ($num in 1..5) { echo $num }
1
2
3
4
5

Serialization

PowerShell has native cmdlets for converting between .NET objects and common filetypes like JSON and CSV:

PS C:\Users\nwm> $json = Get-Process | Select-Object -First 5 -Property Id,Name | ConvertTo-Json
PS C:\Users\nwm> $json
[
  {
    "Id": 9776,
    "Name": "AggregatorHost"
  },
  {
    "Id": 3432,
    "Name": "AppHelperCap"
  },
  {
    "Id": 16088,
    "Name": "ApplicationFrameHost"
  },
  {
    "Id": 12560,
    "Name": "armsvc"
  },
  {
    "Id": 18600,
    "Name": "audiodg"
  }
]
PS C:\Users\nwm> $json | ConvertFrom-Json | ConvertTo-Csv
"Id","Name"
"9776","AggregatorHost"
"3432","AppHelperCap"
"16088","ApplicationFrameHost"
"12560","armsvc"
"18600","audiodg"

Types of Commands

All commands available to the current PowerShell session can be listed with Get-Command -CommandType *.

From Microsoft:

Without parameters, Get-Command gets all of the cmdlets, functions, and aliases installed on the computer. Get-Command * gets all types of commands, including all of the non-PowerShell files in the Path environment variable ($env:Path), which it lists in the Application command type.

External Executables

Most shell languages exist primarily to make or script calls to external executables. For example, when you run ping - whether in cmd, PowerShell, or Bash - you are creating a new process from a completely separate ping executable which runs and returns its output to the calling process.

PowerShell can be used to invoke any external executables. Use Get-Command -CommandType Application to see all executables on your current session's $PATH.

PowerShell Cmdlets, Functions, Aliases, Scripts

Cmdlets, Functions, Aliases, and Scripts are all native PowerShell features that do not require external executables.

Cmdlets are "native PowerShell commands, not stand-alone executables. Cmdlets are collected into PowerShell modules that can be loaded on demand. Cmdlets can be written in any compiled .NET language or in the PowerShell scripting language itself." (source)

Cmdlets follow the Verb-Noun syntax (e.g., Get-Process).

Functions (according to this Stack Overflow post) are differentiated from cmdlets by virtue of being written in PowerShell and loaded via a script, module, or at the command line - as opposed to being contained in a .dll or binary module as expected for a cmdlet.

Aliases are "alternate names or nicknames for cmdlets or for command elements, such as a function, script, file, or executable file. You can use the alias instead of the command name in any PowerShell commands." (source)

Scripts are generally well-understood and I will not define them.

Get-Command (no arguments) will show all currently loaded aliases, functions, and cmdlets.

Modules

From Microsoft:

PowerShell is both a command shell and a scripting language. Commands in PowerShell are implemented as scripts, functions, or cmdlets. The language includes keywords, which provide the structure and logic of processing, and other resources, such as variables, providers, aliases.

A module is a self-contained, reusable unit that can include cmdlets, providers, functions, variables, and other resources that can be imported into a PowerShell session or any custom PowerShell program.

Before the functionality contained in a module is usable, the module must be loaded into the PowerShell session. By default, PowerShell automatically loads an installed module the first time you use a command from the module. You can configure automatic module loading behavior using the variable $PSModuleAutoloadingPreference. For more information, see about_Preference_Variables.

You can also manually unload or reload modules during a PowerShell session. To unload a module, use the Remove-Module cmdlet. To load or reload a module, use Import-Module.

PowerShell comes with a base set of modules. Anyone can create new PowerShell commands or other resources, and publish them as modules that users can install as needed.

You can write modules in C# as compiled .NET assemblies, known as native modules, or in plain PowerShell, known as script modules. This topic explains how to use PowerShell modules. For information about how to create PowerShell modules, see Writing a PowerShell Module.

In other words, modules can be used to access more cmdlets, functions, and aliases.

More information on managing modules will be contained in the Package Management section.

.NET Objects

Because PowerShell runs on .NET, it is natively capable of leveraging any and all .NET assemblies available on your system (assuming compatible .NET versions). Many are available by default in every PowerShell session; Others have to be intentionally loaded.

Viewing and Adding Assemblies

You can see what assemblies are loaded with [System.AppDomain]::CurrentDomain.GetAssemblies(). There is no single or authoritative PowerShell method for viewing what unloaded assemblies are available, but you can look in these locations:

  • C:\Windows\Microsoft.NET\assembly\
    • This is the GAC (Global Assembly Cache) - "Starting with the .NET Framework 4, the default location for the Global Assembly Cache is %windir%\Microsoft.NET\assembly. In earlier versions of the .NET Framework, the default location is %windir%\assembly"
  • HKCR:\Installer\Assemblies\Global

Assemblies can be imported with the Add-Type command.

Using .NET

Relevant Microsoft blog post

.NET classes can be referenced using "bracket notation," e.g. [System.IO.File]::ReadAllBytes(). You can retrieve (and then search) all currently available classes with the following:

$classes = [System.AppDomain]::CurrentDomain.GetAssemblies() | % { $_.GetTypes() } | ? { $_.IsClass -and $_.IsPublic }
# example search
$classes | ? Name -match "File"

Note: You really want to store the first command's results in a variable - there are usually thousands of classes.

.NET classes are themselves an object of type System.RuntimeType and have a variety of helpful properties and methods. To see all members of a RuntimeType, run gm -InputObject $([int]) (note that any valid .NET type may be used in the place of int. gm is an alias for Get-Member).

This technique can be used to retrieve the declared members and declared constructors for a given type. Constructor objects declare their required parameters, which can be very helpful when learning how to create an instance of a given class, e.g.:

PS C:\Users\nwm> [string].DeclaredConstructors.Length
8
PS C:\Users\nwm> [string].DeclaredConstructors[7].GetParameters()


ParameterType    : System.Char
Name             : c
HasDefaultValue  : False
DefaultValue     :
RawDefaultValue  :
MetadataToken    : 134220483
Position         : 0
Attributes       : None
Member           : Void .ctor(Char, Int32)
IsIn             : False
IsOut            : False
IsLcid           : False
IsRetval         : False
IsOptional       : False
CustomAttributes : {}

ParameterType    : System.Int32
Name             : count
HasDefaultValue  : False
DefaultValue     :
RawDefaultValue  :
MetadataToken    : 134220484
Position         : 1
Attributes       : None
Member           : Void .ctor(Char, Int32)
IsIn             : False
IsOut            : False
IsLcid           : False
IsRetval         : False
IsOptional       : False
CustomAttributes : {}

PS C:\Users\nwm> [string]::new([char]'c', 9)
ccccccccc

Enum-based types also expose the GetEnumNames and GetEnumValues methods. See the section on filesystem interaction for two practical use cases for these methods.

Note that tab autocomplete works for .NET classes the same as it does for PowerShell cmdlets.

Using C#

PowerShell also supports running C# code natively via the Add-Type command, which can perform on-the-fly C# compilation, making defined types available in the current PowerShell session.

COM Objects

The Component Object Model (COM) defines a standard for software APIs. Any software that implementing the COM standard can be interacted with from any process of programming language that understands the COM standard - including PowerShell.

An overview of COM from Microsoft:

The Microsoft Component Object Model (COM) is a platform-independent, distributed, object-oriented system for creating binary software components that can interact. COM is the foundation technology for Microsoft's OLE (compound documents), ActiveX (Internet-enabled components), as well as others.

To understand COM (and therefore all COM-based technologies), it is crucial to understand that it is not an object-oriented language but a standard. Nor does COM specify how an application should be structured; language, structure, and implementation details are left to the application developer. Rather, COM specifies an object model and programming requirements that enable COM objects (also called COM components, or sometimes simply objects) to interact with other objects. These objects can be within a single process, in other processes, and can even be on remote computers. They can be written in different languages, and they may be structurally quite dissimilar, which is why COM is referred to as a binary standard; a standard that applies after a program has been translated to binary machine code.

Creating COM Objects

COM Objects can be created in PowerShell with the New-Object cmdlet.

Example: Using Windows Script Host to create a shortcut

$WshShell = New-Object -ComObject WScript.Shell
$lnk = $WshShell.CreateShortcut("$HOME\Desktop\PSHome.lnk")

Reference

Viewing Available COM Objects

Use the following code to view all registered COM Objects available on your system:

# Source: https://powershellmagazine.com/2013/06/27/pstip-get-a-list-of-all-com-objects-available/
Get-ChildItem HKLM:\Software\Classes -ErrorAction SilentlyContinue | Where-Object {
	$_.PSChildName -match '^\w+\.\w+$' -and (Test-Path -Path "$($_.PSPath)\CLSID")
} | Select-Object -ExpandProperty PSChildName
# Note: there may be a small number of additional objects in HKCU

Office Files

The Microsoft Office applications expose functionality via COM Objects. PowerShell can make use of these objects to automate document creation or editing.

COM Object Names for Office Apps:

  • Excel.Application
  • PowerPoint.Application
  • Word.Application

It is difficult to find Microsoft documentation explicitly for these COM Objects, but the object model exposed by them appears to be identical to that documented for VBA.

See my article on automating PowerPoint here

Interacting with Windows

WMI and CIM

WMI and CIM are largely out of scope for these notes, but - since they will be used in some of the following examples - the following (very brief) overview is provided:

  1. *WMI* and *CIM* cmdlets provide access to a Windows "management interface" that is primarily used for querying system information, but may also be used to set system configurations.
  2. CIM is the successor to WMI; WMI is no longer actively developed.

FileSystem

Listing and Finding Directories and Files

Get-ChildItem can be used to list the contents of a directory. ls and gci are both aliases for this command. gci supports recursion, filtering, and forcing the display of hidden items. It's similar to the find command on Linux.

The current directory can be found with Get-Location, its alias (pwd), or the $PWDvariable.

The current directory can be changed with Set-Location, or its alias, cd.

As with most execution environments, files can be specified with relative or absolute filepaths, e.g.:

PS C:\Users\nwm> Get-FileHash .\dev\test.txt

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          F68E37DC9CABF2EE8B94D6A5D28AD04BE246CCC2E82911F8F1AC390DCF0EE364       C:\Users\nwm\dev\test.txt


PS C:\Users\nwm> Get-FileHash C:\users\nwm\dev\test.txt

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          F68E37DC9CABF2EE8B94D6A5D28AD04BE246CCC2E82911F8F1AC390DCF0EE364       C:\users\nwm\dev\test.txt

IO

Files can be read or written with a variety of commands. The primary PowerShell cmdlets for this are Get-Content and Set-Content. Linux-style aliases are supported (cat and >). There are many .NET methods available for IO in the [System.IO.File] class, e.g. $f = [io.file]::Open(".\Downloads\Auto.data", 'open', 'readwrite')

String Searching

Select-String can be used to search stdin (e.g., pipeline input) for a given simple or regex-based pattern. It can also search a file if provided a filepath. It is the PowerShell equivalent of grep.

The [System.Text.RegularExpressions.Regex] class may also be of use.

ACLs

The NTFS Access Control Lists (or ACLs) for a file can be retrieved with Get-Acl. They can be changed with Set-Acl. cacls, icacls, and other "legacy" binaries may also be used.

The objects returned by Get-Acl have some interesting properties. .GetType() can be used to provide information about the returned object types:

PS C:\Users\nwm> $acl = Get-Acl .\dev\test.txt
PS C:\Users\nwm> $acl.access


FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITY\SYSTEM
IsInherited       : True
InheritanceFlags  : None
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited       : True
InheritanceFlags  : None
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : <redacted>
IsInherited       : True
InheritanceFlags  : None
PropagationFlags  : None


PS C:\Users\nwm> $acl.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    FileSecurity                             System.Security.AccessControl.FileSystemSecurity


PS C:\Users\nwm> $acl.Access.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    AuthorizationRuleCollection              System.Collections.ReadOnlyCollectionBase


PS C:\Users\nwm> $acl.Access[0].GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    FileSystemAccessRule                     System.Security.AccessControl.AccessRule


PS C:\Users\nwm> $acl.Access[0]


FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITY\SYSTEM
IsInherited       : True
InheritanceFlags  : None
PropagationFlags  : None
FileSystemRights

FileSystemRights is an enum-based type - there is a fixed, finite set of acceptable values. The next script block demonstrates usage of the .NET methods exposed by enum-based types to list enum names and values. It also demonstrates the bitmask nature of the FileSystemRights class.

PS C:\Users\nwm> [System.Security.AccessControl.FileSystemRights].GetEnumNames()
ListDirectory
ReadData
WriteData
CreateFiles
CreateDirectories
AppendData
ReadExtendedAttributes
WriteExtendedAttributes
Traverse
ExecuteFile
DeleteSubdirectoriesAndFiles
ReadAttributes
WriteAttributes
Write
Delete
ReadPermissions
Read
ReadAndExecute
Modify
ChangePermissions
TakeOwnership
Synchronize
FullControl

PS C:\Users\nwm> [System.Security.AccessControl.FileSystemRights].GetEnumValues() | % { [int]$_ }
1
1
2
2
4
4
8
16
32
32
64
128
256
278
65536
131072
131209
131241
197055
262144
524288
1048576
2032127

PS C:\Users\nwm> [System.Security.AccessControl.FileSystemRights].GetEnumValues() | % { New-Object PSObject -Property @{Name=$_;Value=[convert]::ToString([int]$_, '2')} } | ft -Property @{n='name';e={$_.name};align='left'}, @{n='value';e={$_.value};align='right'}

name                                         value
----                                         -----
ListDirectory                                    1
ListDirectory                                    1
CreateFiles                                     10
CreateFiles                                     10
CreateDirectories                              100
CreateDirectories                              100
ReadExtendedAttributes                        1000
WriteExtendedAttributes                      10000
Traverse                                    100000
Traverse                                    100000
DeleteSubdirectoriesAndFiles               1000000
ReadAttributes                            10000000
WriteAttributes                          100000000
Write                                    100010110
Delete                           10000000000000000
ReadPermissions                 100000000000000000
Read                            100000000010001001
ReadAndExecute                  100000000010101001
Modify                          110000000110111111
ChangePermissions              1000000000000000000
TakeOwnership                 10000000000000000000
Synchronize                  100000000000000000000
FullControl                  111110000000111111111

See What is a bitmask?

File Attributes

NTFS (the Windows filesystem) supports extended file attributes, which may be edited with the attrib binary, or by directly editing the file attributes, which are implemented as a bitmask. See below:

PS C:\Users\nwm> [System.IO.FileAttributes].GetEnumValues() | % { New-Object PSObject -Property @{Name=$_;Value=[convert]::ToString([int]$_, '2')} } | ft -Property @{n='name';e={$_.name};align='left'}, @{n='value';e={$_.value};align='right'}

name                           value
----                           -----
None                               0
ReadOnly                           1
Hidden                            10
System                           100
Directory                      10000
Archive                       100000
Device                       1000000
Normal                      10000000
Temporary                  100000000
SparseFile                1000000000
ReparsePoint             10000000000
Compressed              100000000000
Offline                1000000000000
NotContentIndexed     10000000000000
Encrypted            100000000000000
IntegrityStream     1000000000000000
NoScrubData       100000000000000000
PS C:\Users\nwm> $item = Get-Item .\dev\test.txt; $item.Attributes = $item.Attributes -bor 1
PS C:\Users\nwm> echo 'test2' > .\dev\test.txt
Out-File: Access to the path 'C:\Users\nwm\dev\test.txt' is denied.
PS C:\Users\nwm> $item = Get-Item .\dev\test.txt; $item.Attributes = $item.Attributes -bxor 1
PS C:\Users\nwm> echo 'test2' > .\dev\test.txt
PS C:\Users\nwm>

It is interesting to note that Windows relies on file names (extensions) to know how to open a file, but does not rely on names to know whether a file is hidden; Linux does not rely on file names to know how to open a file, but does rely on names to know whether a file is hidden.

Alternate Data Streams (ADS)

ADS is/are a feature of the NTFS filesystem that, in effect, allows multiple data streams to be associated with a single file. The default or primary data stream associated with each file is :$DATA; A common second stream is :Zone.Identifier, which Windows uses to mark files that have been downloaded from the Internet. (Remember Microsoft Office asking if you want to open a downloaded file in edit mode? This is why/how.) ADS can be used to hide data. Modern security tools will detect this, but your average computer user will not.

ADS in PowerShell:

PS C:\Users\nwm> Get-ChildItem .\Downloads\ | % { Get-Item -Path $_.FullName -Stream * }

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\Users\nwm\Downloads\download.jpg::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\Users\nwm\Downloads
PSChildName   : download.jpg::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\Users\nwm\Downloads\download.jpg
Stream        : :$DATA
Length        : 6933

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\Users\nwm\Downloads\download.jpg:Zone.Identifier
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\Users\nwm\Downloads
PSChildName   : download.jpg:Zone.Identifier
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\Users\nwm\Downloads\download.jpg
Stream        : Zone.Identifier
Length        : 96

PS C:\Users\nwm> cat .\Downloads\download.jpg:Zone.Identifier
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://www.google.com/
HostUrl=https://www.google.com/

PS C:\Users\nwm> Get-Content .\Downloads\download.jpg -stream Zone.Identifier
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://www.google.com/
HostUrl=https://www.google.com/

PS C:\Users\nwm> Remove-Item .\Downloads\download.jpg -Stream Zone.Identifier
PS C:\Users\nwm> Get-ChildItem .\Downloads\ | % { Get-Item -Path $_.FullName -Stream * }

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\Users\nwm\Downloads\download.jpg::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\Users\nwm\Downloads
PSChildName   : download.jpg::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\Users\nwm\Downloads\download.jpg
Stream        : :$DATA
Length        : 6933

Base64

Conversions to and from base64 are a common task in both cybersecurity and development. In PowerShell, these are relatively simple tasks, though they do require the use of .NET classes.

PS C:\Users\nwm> [convert]::ToBase64String([char[]]"test")
dGVzdA==
PS C:\Users\nwm> [char[]][convert]::FromBase64String("dGVzdA==") -join ''
test
PS C:\Users\nwm> [string]::new([convert]::FromBase64String("dGVzdA=="))
test

Note that the conversion functions accept and return arrays of bytes. Strings must be converted to or from byte arrays or char arrays (which can be implicitly converted into byte arrays by PowerShell).

Practical Exercise(s)

Using PowerShell only:

Read-Only Files
  1. Create a text file
  2. Set the text file as read-only
  3. Try editing the file with notepad
  4. Remove the read-only attribute and add the hidden attribute
  5. Try listing the directory; how can you force hidden files to be listed by gci?
Base64 Images
  1. Download or select an image
  2. Read the file and convert all bytes into a Base64 string
  3. Send the string as a message to a classmate
  4. Convert the b64 string(s) you receive into image file(s)

Processes

There are several ways to interact with processes in PowerShell. There is the Get-Process cmdlet, the equivalent .NET syntax [System.Diagnostics.Process]::GetProcesses(), and the Get-WmiObject/Get-CimInstance cmdlets, which can be used to retrieve objects in the CIM class win32_process.

CIM/WMI cmdlets are particularly useful for processes because the .NET-based methods do not provide a way of finding a process's parent process. The CIM-based cmdlets return process objects with a ParentProcessID property.

Stop-Process and Start-Process can be used to control processes, or .NET equivalents can be used for more granular options and controls.

Practical Exercise(s)

  1. Find the command line arguments used to start each svchost.exe process on your device
  2. List all processes with an open GUI window
  3. Write a one-liner to list all processes with their PIDs, PPIDs, and parent process names

Services

Much like processes, services can be listed with Get-Service, [System.ServiceProcess.ServiceController]::GetServices(), Get-CimInstance -ClassName win32_service, or Get-WmiObject -Class win32_service.

For a list of PowerShell cmdlets used to interact with, create, or destroy services, run Get-Command "*service*".

Practical Exercise(s)

  1. Find the command issued by each service upon a service 'start'
  2. Find out which service has the most downstream dependent services
  3. Find out which service has the most upstream service dependencies

Network

Ping

An ICMP ping may be sent with the ping command, or with .NET:

PS C:\Users\nwm> $ping = [System.Net.NetworkInformation.Ping]::new()
PS C:\Users\nwm> $ping.Send("8.8.8.8")

Status        : Success
Address       : 8.8.8.8
RoundtripTime : 5
Options       : System.Net.NetworkInformation.PingOptions
Buffer        : {97, 98, 99, 100…}

View Connections

Active network connections and listening ports may be investigated with the classic netstat or with a series of PowerShell and .NET cmdlets and classes, such as Get-NetTCPConnection and Get-NetUDPEndpoint. The following scriptblock contains a one-liner for showing the process name associated with active TCP connections.

Get-NetTCPConnection | Where-Object {($_.State -eq "Listen")} | Select-Object LocalAddress,LocalPort,RemoteAddress,RemotePort,State,@{Name="Process";Expression={(Get-Process -Id $_.OwningProcess).ProcessName}} | ft

Make Connections

Test-NetConnection can be used to test TCP connections to a provided hostname and port.

The [System.Net.Sockets.TcpClient], [System.Net.Sockets.TcpListener], and [System.Net.Sockets.UdpClient] classes can be used to perform more complex network operations (i.e., fill in for netcat).

Configurations

Interface configurations and statuses may be investigated with the classic ipconfig binary, or with the Get-NetIPAddress and Get-DNSClient PowerShell cmdlets.

Web Requests

PowerShell's Invoke-WebRequest and Invoke-RestMethod cmdlets are powerful web clients that continue to receive new features. Of particular note is the ability to maintain a web session using the -WebSession variable.

PS C:\Users\nwm> $session = [Microsoft.PowerShell.Commands.WebRequestSession]::New()
PS C:\Users\nwm> $response = Invoke-WebRequest https://google.com -WebSession $session
PS C:\Users\nwm> $session.Cookies

Capacity Count MaxCookieSize PerDomainCapacity
-------- ----- ------------- -----------------
     300     3          4096                20

PS C:\Users\nwm> $session.Cookies.GetAllCookies()[0]

Comment    :
CommentUri :
HttpOnly   : False
Discard    : False
Domain     : .google.com
Expired    : False
Expires    : 4/7/2024 1:51:41 AM
Name       : 1P_JAR
Path       : /
Port       :
Secure     : True
TimeStamp  : 3/7/2024 7:51:40 PM
Value      : 2024-03-08-01
Version    : 0

Recent versions of Windows also ship with a curl binary.

Practical Exercise

Use the free iDigBio API (documented here) to determine how many specimens match the following search criteria:

  • Speciment belongs to genus macrarene
  • Found within 5km of coordinates (32.5, -117.1)
  • Collected in the year 1957

Who discovered/collected each of the relevant specimens?

# Example Solution
$searchTerms = @{ 
  geopoint = @{ 
    type = "geo_distance"; 
    distance = "5km"; 
    lat = 32.5; 
    lon = -117.1 }; 
  datecollected = @{ 
    type = "range"; 
    gte = "1957-01-01"; 
    lte = "1958-01-01" 
  }; 
  genus = "macrarene" 
}
$request = Invoke-WebRequest -Uri "https://search.idigbio.org/v2/search/records/?rq=$($searchTerms | ConvertTo-Json -Compress)"
$data = $request.content | ConvertFrom-Json

Event Logs

Windows Event Logs may be queried in several ways: PowerShell's Get-WinEvent, Get-EventLog (no longer actively developed), .NET's [System.Diagnostics.EventLog]::GetEventLogs(), or the wevtutil binary.

PS C:\Users\nwm> $seclog = [System.Diagnostics.EventLog]::GetEventLogs() | Where Log -eq Security
PS C:\Users\nwm> $seclog.Entries.Where({$_.EventID -eq 4624}) | Select -First 5 | ft -AutoSize

  Index Time         EntryType    Source                              InstanceID Message
  ----- ----         ---------    ------                              ---------- -------
2339745 Mar 04 20:25 SuccessAudit Microsoft-Windows-Security-Auditing       4624 An account was successfully logged on.2339747 Mar 04 20:25 SuccessAudit Microsoft-Windows-Security-Auditing       4624 An account was successfully logged on.2339798 Mar 04 20:25 SuccessAudit Microsoft-Windows-Security-Auditing       4624 An account was successfully logged on.2341381 Mar 04 20:57 SuccessAudit Microsoft-Windows-Security-Auditing       4624 An account was successfully logged on.2341383 Mar 04 20:57 SuccessAudit Microsoft-Windows-Security-Auditing       4624 An account was successfully logged on.
PS C:\Users\nwm> Get-WinEvent -LogName Security | ? Id -eq 4624 | select -First 5 | ft -AutoSize

   ProviderName: Microsoft-Windows-Security-Auditing

TimeCreated           Id LevelDisplayName Message
-----------           -- ---------------- -------
3/7/2024 7:55:18 PM 4624 Information      An account was successfully logged on.3/7/2024 7:51:56 PM 4624 Information      An account was successfully logged on.3/7/2024 7:50:14 PM 4624 Information      An account was successfully logged on.3/7/2024 7:50:05 PM 4624 Information      An account was successfully logged on.3/7/2024 7:45:54 PM 4624 Information      An account was successfully logged on.

Notice that the two demonstated methods list logs in the opposite order (oldest to newest and vice versa).

Misc

PSDrives

A PSDrive is a type of interface that allows PowerShell to treat a variety of data sources like local filesystems. PSDrive types include registry hives, the local certificate store, and mapped network drives. See here for more information.

Get-PSDrive, New-PSDrive, and Remove-PSDrive are used to interact with PSDrives.

PS C:\Users\nwm> Get-PSDrive

Name           Used (GB)     Free (GB) Provider      Root                                                                                                                                                                                                               CurrentLocation
----           ---------     --------- --------      ----                                                                                                                                                                                                               ---------------
Alias                                  Alias
C                1125.49        736.20 FileSystem    C:\                                                                                                                                                                                                                      Users\nwm
Cert                                   Certificate   \
Env                                    Environment
Function                               Function
HKCU                                   Registry      HKEY_CURRENT_USER
HKLM                                   Registry      HKEY_LOCAL_MACHINE
Temp             1125.49        736.20 FileSystem    C:\Users\nwm\AppData\Local\Temp\
V                 230.84        234.90 FileSystem    V:\
Variable                               Variable
WSMan                                  WSMan

PS C:\Users\nwm> ls alias: | select -first 5

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           ? -> Where-Object
Alias           % -> ForEach-Object
Alias           ac -> Add-Content
Alias           cat -> Get-Content
Alias           cd -> Set-Location

PS C:\Users\nwm> ls hkcu: | select -first 1

    Hive: HKEY_CURRENT_USER

Name                           Property
----                           --------
AppEvents

Amongst other things, PSDrives enable the use of Get-ChildItem for recursively searching registry hives for a given property name or value.

Windows (Kernel) APIs

PowerShell can be used to directly call Windows APIs. It is not a trivial task, but can enable some very interesting functionality. For more information, see the blog posts here and here.

GUI Apps

.NET is a popular platform for creating Windows applications because of the convenient frameworks it provides. Windows Forms are an older, but still frequently used framework. The following script demonstrates how easily applications can be made with PowerShell.

WinForms
<#
created with https://app.poshgui.com
#>

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()

$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = New-Object System.Drawing.Point(530,310)
$Form.text                       = "MyGUI"
$Form.TopMost                    = $false

$FormButton                   = New-Object system.Windows.Forms.Button
$FormButton.text              = "Show Local User Accounts"
$FormButton.width             = 280
$FormButton.height            = 50
$FormButton.location          = New-Object System.Drawing.Point(125,120)
$FormButton.Font              = New-Object System.Drawing.Font('Microsoft Sans Serif',10,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))
$FormButton.ForeColor         = [System.Drawing.ColorTranslator]::FromHtml("#ffffff")
$FormButton.BackColor         = [System.Drawing.ColorTranslator]::FromHtml("#4a90e2")

$Form.controls.AddRange(@($FormButton))

$FormButton.Add_Click({ MyFunction })

#region Logic 
function MyFunction { 
    Get-LocalUser | Out-GridView
}

#endregion

[void]$Form.ShowDialog()

Package Management

There are a number of package management tools available in PowerShell. Some are used for applications, others for PowerShell-specific modules, and others for .NET. If the variety and overlap of official tools appears confusing... well, I agree.

PowerShell Modules

Old Tool(s)

PowerShellGet - used to install PowerShell modules with Install-Module and related commands.

Current Tool(s)

PSResourceGet - successor to PowerShellGet for managing PowerShell modules with Install-PSResource and related commands

Commands

View related commands with:

Get-Command | ? Source -In @("Microsoft.PowerShell.PSResourceGet", "PowerShellGet")

.NET Libraries

NuGet is an official package manager for .NET libraries.

The dotnet command can be used to install new assemblies.

Other Resources

Official/Microsoft-Provided

Old Tool(s)

PackageManagement - A PowerShell module used by PowerShellGet to install all manner of packages using Install-Package and related commands.

Current Tool(s)

Winget is a PackageManagement successor that's mainly used for non-PS resources. Doesn't support .NET resources

Shortcomings

Note: I intend to expand on this section over time.

While I am a fan of PowerShell, it does have some shortcomings, particularly when being used as a general-purpose language:

  • Importing a PowerShell module does NOT import any classes defined in that module
    • Alternative methods: 'dot-sourcing' or using module <path>
      • Note that using module has its own problems, for example
  • The import system is generally more limited than other general-purpose languages
    • E.g., no from <module> import <method> or equivalent
  • Unsurprisingly, the function call syntax (excluding .NET static and instance methods) is more suitable for a command-line shell than a programming language
  • PowerShell does not support passing named parameters in .NET functions; it supports positional args only
  • Unlike other .NET languages, Event Handlers execute in a background job and cannot modify event args