Intro to PowerShell
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.)
PowerShell in Popular Knowledge
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.
PowerShell Beyond Popular Knowledge
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 .dll
s - 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.
Navigation
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")
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:
*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.- 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 $PWD
variable.
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
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
- Create a text file
- Set the text file as read-only
- Try editing the file with notepad
- Remove the read-only attribute and add the hidden attribute
- Try listing the directory; how can you force hidden files to be listed by
gci
?
Base64 Images
- Download or select an image
- Read the file and convert all bytes into a Base64 string
- Send the string as a message to a classmate
- 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)
- Find the command line arguments used to start each svchost.exe process on your device
- List all processes with an open GUI window
- 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)
- Find the command issued by each service upon a service 'start'
- Find out which service has the most downstream dependent services
- 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.
<#
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
Unofficial (But Popular)
- chocolatey (choco)
- scoop
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
- Note that
- Alternative methods: 'dot-sourcing' or
- The import system is generally more limited than other general-purpose languages
- E.g., no
from <module> import <method>
or equivalent
- E.g., no
- 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