PowerShell Scripting Cheatsheet¶
This is the companion to PowerShell Cheatsheet
, which focuses on writing PowerShell scripts.
According to Microsoft PowerShell
Is a cross-platform task automation and configuration management framework, consisting of a command-line shell and
scripting language that is built on top of the .NET Common Language Runtime
(CLR), accepts and returns .NET objects
.
This brings entirely new tools and methods for automation.
This means learning new skills and thinking differently which can be frustrating while learning.
To simplify maintenance I write PowerShell scripts as standalone utilities deployed in a single file, this means I have to copy-and-paste my favourite frequently used functions, such as dumpArrayList, dumpHashTable because there is no mechanism to textually include your favourite functions into the source when writing and testing.
It is possible to split your script into multiple files, create libraries of your favoutite utilities etc.
I do not cover this topic, the example script shows where/how to source
you library files, and if you wish to create your
own modules, see How to Write a PowerShell Script Module.
Introduction¶
Unfortunately because PowerShell
is very powerful scripting language, often used to automate routine tasks, makes it an ideal
target for would-be hackers. To mitigate this Microsoft limits if/when PowerShell scripts can be executed, although
individual cmdlets
can always be executed.
Windows Pro/Home usually disallows
PowerShell scripts
but permitscmdlets
to be executed;Windows Server usually allows
RemoteSigned
scripts to be run on theLocalMachine
;
The execution policy governs whether a PowerShell
script can be executed, get-executionpolicy
displays this for
the current PowerShell
, and get-executionpolicy -list
shows all the policies in highest to lowest priority (scope) order.
In the example below only the LocalMachine
policy is defined, and this is set to restricted
so PowerShell
scripts cannot be executed, but
indiviual commands, cmdlets
can.
PS> Get-ExecutionPolicy
Restricted
PS> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined # highest priority
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine Restricted # lowest priority
If your ExecutionPolicy is as above, a quick fix is to start a PowerShell as Administrator and set it to RemoteSigned as shown, but you should still read the PowerShell Exection Policies section.
# Set *ONE* of: 'LocalMachine RemoteSigned' or 'CurrentUser RemoteSigned' not both
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
PS C:\WINDOWS\system32> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined # highest priority
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine RemoteSigned # lowest priority
Language¶
The language makes use of .Net Framework and is built on top of the .NET Common Language Runtime (CLR) , and manipulates .NET objects. If the language itself does not provide what you need, there may be a Popular PowerShell Module you can download or you can access the .Net APIs directly, a good example being ArrayLists which are dynamic in size unlike a PowerShell Array.
In common with other object oriented languages, PowerShell
has features such inheritance, subclasses, getters, setters, modules etc.
Functions support both named
and positional
arguments, which can be mixed, this can be confusing, so in
most cases it is clearer to use splatting rather
than individual name or positional parameters.
Useful starting points when learning about the language:
Unlike most texts on programming languages this starts with a simple but realistic PowerShell example, with many of the language details being covered in subsequent sections.
Example Script¶
This is a contrived but realistic PowerShell script to illustrate several important points. It is based on a gist template from 9to5IT, which is extremely useful, but is augmented to force the syntax version and to be more strict on the use of uninitialized variables.
#requires -version 4
<#
.SYNOPSIS
9to5IT Template for PowerShell scripts.
.DESCRIPTION
Displays the names and ages of the flintstones.
.PARAMETER names
List the names only
.PARAMETER ages
List the ages only
.PARAMETER person <name>
List person's age
.INPUTS
None
.OUTPUTS
The Requested text.
.NOTES
Version: 1.0
Author: sjfke
Creation Date: 2021.01.03
Purpose/Change: Initial script development
.EXAMPLE
families.ps1 -names
.EXAMPLE
families.ps1 -person fred
#>
param(
[switch]$names = $false,
[switch]$ages = $false,
[string]$person = $null,
[switch]$stackTrace = $false
)
Set-StrictMode -Version 2
#---------------------------------------------------------[Initialisations]--------------------------------------------------------
# Set Error Action to Silently Continue
# $ErrorActionPreference = "SilentlyContinue"
# Dot Source required Function Libraries
# . "C:\Scripts\Functions\Logging_Functions.ps1"
#----------------------------------------------------------[Declarations]----------------------------------------------------------
$scriptName = "flintstones.ps1"
$scriptVersion = "1.0"
#Log File Info
# $sLogPath = "C:\Windows\Temp"
# $sLogName = "<script_name>.log"
# $sLogFile = Join-Path -Path $sLogPath -ChildPath $sLogName
$hash = $null
#-----------------------------------------------------------[Functions]------------------------------------------------------------
function initializeHash {
return @{ Fred = 30; Wilma = 25; Pebbles = 1; Dino = 5 }
}
function getNames {
return $hash.keys
}
function getAges {
return $hash.values
}
function getPerson {
param(
[string]$name = ''
)
return $hash[$name]
}
#-----------------------------------------------------------[Execution]------------------------------------------------------------
$hash = initializeHash
if ($names) {
getNames
}
elseif ($ages) {
getAges
}
elseif (($person -ne '') -and ($person -ne $null)) {
$arguments = @{
name = $person
}
getPerson @arguments
}
else {
if ($stackTrace) {
write-error("invalid or missing argument") # stack-trace like error message
}
else {
write-warning("{0} v{1}: invalid or missing argument" -f $scriptName, $scriptVersion)
exit(1)
}
}
Things to note:
The #requires -version 4 PowerShell version 4 syntax, (use version 2, if windows is very old);
Initial comment block
.SYNOPSIS...
provides theget-help
text, note line-spacing is important;The param() block must be the first non-comment line for command-line arguments;
The Set-StrictMode -Version 2 checks the usage of uninitialized variables;
Variables¶
Powershell variables can be any of the Basic DataTypes such as integers, characters, strings, arrays, and hash-tables, but also .Net
objects that represent such things as
processes, services, event-logs, and even computers.
PS> $age = 5 # System.Int32
PS> [int]$age = "5" # System.Int32, cast System.String + System.Int32
PS> $name = "Dino" # System.String
PS> $name + $age # Fails; System.String + System.Int32
PS> $name + [string]$age # Dino5; System.String + System.String
PS> $a = (5, 30, 25, 1) # array of System.Int32
PS> $a = (5, "Dino") # array of (System.Int32, System.String)
PS> $h = @{ Fred = 30; Wilma = 25; Pebbles = 1; Dino = 5 } # hash table
PS> $d = Get-ChildItem C:\Windows # directory listing, FileInfo and DirectoryInfo types,
PS> $d | get-member # FileInfo, DirectoryInfo Properties and Methods
PS> $p = Get-Process # System.Diagnostics.Process type
PS> set-variable -name age 5 # same as $age = 5
PS> set-variable -name name Dino # same as $name = "Dino" (variable's name is *name*)
PS> clear-variable -name age # clear $age; $age = $null
PS> clear-variable -name name # clear $name; $name = $null
PS> remove-variable -name age # delete variable $age
PS> remove-item -path variable:\name # delete variable $name
PS> set-variable -name pi -option Constant 3.14159 # constant variable
PS> $pi = 42 # Fails $pi is a constant
Basic DataTypes¶
Data Type |
Definition |
---|---|
Boolean |
True or False Condition |
Byte |
An 8-bit unsigned whole number from 0 to 255 |
Char |
A 16-bit unsigned whole number from 0 to 65,535 |
Date |
A calendar date |
Decimal |
A 128-bit decimal value, such as 3.14159 |
Double |
A double-precision 64-bit floating point number, narrower range than Decimal |
Integer |
A 32-bit signed whole number from -2,147,483,648 to 2,147,483,647 |
Long |
A 64-bit signed whole number, very big integer, 9,233,372,036,854,775,807 |
Object |
|
Short |
A 16-bit unsigned whole number, -32,768 to 32,767 |
Single |
A single-precision 32-bit floating point number |
String |
Text, a character string |
Array Variables¶
Array variables are a fixed size, can have mixed values and can be multi-dimensional.
PS> $a = 1, 2, 3 # array of integers
PS> $a = (1, 2, 3) # array of integers (my personal preference)
PS> $a = ('a','b','c')
PS> $a = (1, 2, 3, 'x') # array of System.Int32's, System.String
PS> [int[]]$a = (1, 2, 3, 'x') # will fail 'x', array of System.Int32 only
PS> $a = ('fred','wilma','pebbles')
PS> $a[0] # fred
PS> $[2] # pebbles
PS> $a.length # 3
PS> $a[0] = 'freddie' # fred becomes freddie
PS> $a[3] = 'dino' # Error: Index was outside the bounds of the array.
PS> $a += 'dino' # correct way to add 'dino' (note does an array copy)
PS> $a[1,3,2] # wilma, dino, pebbles
PS> $a[1..3] # wilma, pebbles, dino
PS> $a = $a[0..2] # dino ran away (note does an array copy)
PS> $b = ('barbey', 'betty', 'bamm-bamm')
PS> $a = ($a, $b) # [0]:fred [1]:wilma [2]:pebbles [3]:barney [4]:betty [5]:bamm-bamm
PS> $a.length # 6
PS> $a = ($a, ($b)) # [0]:fred [1]:wilma [2]:pebbles [3][0]:barney [3][1]:betty [3][2]:bamm-bamm
PS> $a.length # 4
PS> $ages = (30, 25, 1, 5) # flintstones ages
PS> $names = ('fred','wilma','pebbles', 'dino') # flintstones names
PS> $a = ($names),($ages)) # multi-dimensional array example
PS> $a.length # 4
PS> $a[0] # fred wilma pebbles dino
PS> $a[1] # 30 25 1 5
PS> $a[0][0] # fred
PS> $a[0][1] # 30
Useful references:
HashTables¶
A HashTable is an unordered collection of key:value pairs, synonymous with an object and its properties.
Later versions support known/fixed order hash elements, $hash = [ordered]@{}
.
PS> $h = @{} # empty hash
PS> $key = 'Fred' # set key name
PS> $value = 30 # set key value
PS> $h.add($key, $value) # add key:value ('fred':30) to the hash-table
PS> $h.add('Wilma', 25 ) # add 'Wilma':25
PS> $h['Pebbles'] = 1 # add 'Pebbles':1
PS> $h.Dino = 5 # add 'Dino':5
PS> $h # actual hash-table, printed if on command-line
PS> $h['Fred'] # how old is Fred? 30
PS> $h[$key] # how old is Fred? 30
PS> $h.fred # how old is Fred? 30
# creating a populated hash, multi-line.
PS> $h = @{
Fred = 30
Wilma = 25
Pebbles = 1
Dino = 5
}
# creating the same populated hash, on single-line
PS> $h = @{ Fred = 30; Wilma = 25; Pebbles = 1; Dino = 5 }
PS> $h.keys # unordered: Dino, Pebbles, Fred, Wilma
PS> $h.values # unordered: 5, 1, 30, 25 (but same as $h.keys order)
# later PowerShell versions allow the order to be fixed.
PS> $h = [ordered]@{ Fred = 30; Wilma = 25; Pebbles = 1; Dino = 5 }
PS> $h.keys # ordered: Fred, Wilma, Pebbles, Dino
PS> $h.values # ordered: 30, 25, 1, 5
# key order is random, unless [ordered] was used in the declaration
PS> foreach ($key in $h.keys) {
write-output ('{0} Flintstone is {1:D} years old' -f $key, $h[$key])
}
# ascending alphabetic order (Dino, Fred, Pebbles, Wilma)
PS> foreach ($key in $h.keys | sort) {
write-output ('{0} Flintstone is {1:D} years old' -f $key, $h[$key])
}
# descending alphabetic order (Wilma, Pebbles, Fred, Dino)
PS> foreach ($key in $h.keys | sort -descending) {
write-output ('{0} Flintstone is {1:D} years old' -f $key, $h[$key])
}
# specfific order (Fred, Wilma, Pebbles, Dino)
PS> $keys = ('fred', 'wilma', 'pebbles', 'dino')
for ($i = 0; $i -lt $keys.length; $i++) {
write-output ('{0} Flintstone is {1:D} years old' -f $keys[$i], $h[$keys[$i]])
}
PS> if ($h.ContainsKey('fred')) { ... } # true
PS> if ($h.ContainsKey('barney')) { ... } # false
PS> if ($h.fred) { ... } # avoid, works most of the time.
PS> if ($h['barney']) { ... } # avoid, works most of the time.
PS> $h.remove('Dino') # remove Dino, because he ran away :-)
PS> $h.clear() # flintstone family deceased
For more details read the excellent review by Kevin Marquette:
Objects¶
If you cannot create what you need from Arrays, HashTables, ArrayLists, Queues, Stacks etc., then it is possible to create custom PowerShell objects, but to date I have never needed to do this. For more details, read:
Functions¶
Function arguments and responses are passed by reference, so an arugment can be changed inside the function and remains unchanged outside the function, but this is considered “bad programming practice”, so better to avoid doing this. Functions return references to objects, as illustrated in the Example Script where references to HashTable and Array objects are returned.
While each function call returns a reference to a new (different) object, be careful about the scope of the variable you assign this reference too, it is easy to create multiple references to the same object.
While mixing named (order indepedent) and positional (order dependent) arguments is permitted it can cause strange errors, so unless you are only
supplying one or two arguments, a better approach is to use splatting.
The following contrived example illustrates the basics but the param ( ... )
section has many options not shown here.
#requires -version 4
Set-StrictMode -Version 2
function createPerson {
param (
[string]$name = '',
[int]$age = 0,
[switch]$verbose = $false,
[switch]$debug = $false
)
if (($name -eq $null) -or ($name.length -eq 0)) {
if ($verbose) {
write-warning("createPerson - name is missing")
return $null
}
elseif ($debug) {
write-error("createPerson - name is missing")
exit(1)
}
else {
return $null
}
}
if (($age -le 0) -or ($age -gt 130)) {
if ($verbose) {
write-warning("createPerson - age, {0:D}, is incorrect" -f $age)
return $null
}
elseif ($debug) {
write-error("createPerson - age, {0:D}, is incorrect" -f $age)
exit(1)
}
else {
return $null
}
}
$hash = @{}
$hash[$name] = $age
return $hash
}
createPerson 'fred' 30 -verbose # positional arguments
createPerson 30 'fred' -verbose # positional arguments, breaks name=30
createPerson -name 'fred' -age 30 -verbose # named arguments
createPerson -age 30 'fred' -verbose # mixed arguments, be careful, no-named taken param order
$arguments = @{ # splatting
name = 'fred'
age = 30
verbose = $true
}
createPerson @arguments
$arguments = @{name = 'wilma'; age = 25; verbose = $true} # splatting one-line
createPerson @arguments
$arguments = @{
name = 'fred'
verbose = $true
debug = $false
}
createPerson @arguments # fails, age default is 0
$arguments = @{
age = 21
verbose = $true
debug = $false
}
createPerson @arguments # fails, name default is an empty string
Further reading:
Microsoft Docs, Chapter 9 - Functions
Microsoft Docs, About Functions Advanced Parameters.
ArrayList¶
PS> $names = New-Object -TypeName System.Collections.ArrayList
PS> $names = [System.Collections.ArrayList]::new()
PS> $names.gettype() # ArrayList
PS> $index = $names.Add('fred') # returns array-list index: i.e. 0
PS> [void]$names.Add('wilma') # discard array-list index
PS> [void]$names.Add('pebbles')
PS> [void]$names.Add('dino')
# one-line creation, empty or populated
PS> [System.Collections.ArrayList]$names = @()
PS> [System.Collections.ArrayList]$names = @('fred','wilma','pebbles', 'dino')
PS> $names.Count # returns 4
PS> $names[1] # wilma
PS> $names.remove(3) # dino ran away or did he?
PS> $names.Count # 4, no dino is still there
PS> $names.[3] # dino
PS> $names.RemoveAt(3) # dino, has really gone this time
PS> [void]$names.Add('dino') # dino found
PS> $names.Remove('dino') # dino, escaped again
PS> [void]$names.Add('dino') # dino found ... again
PS> [void]$names.Insert(3,'fido')
PS> $names # 0:fred, 1:wilma, 2:pebbles, 3:fido, 4:dino
PS> $names.remove('fido')
PS> $names # 0:fred, 1:wilma, 2:pebbles, 3:dino
# Generic List are ArrayList's of a fixed type
PS> [System.Collections.Generic.List[string]]$names = @()
PS> [System.Collections.Generic.List[string]]$names = @('fred','wilma','pebbles', 'dino')
PS> [System.Collections.Generic.List[int]]$ages = @()
PS> [System.Collections.Generic.List[int]]$ages = (30, 25, 1, 5)
$names.add(30) # 0:fred, 1:wilma, 2:pebbles, 3:dino, 4:30
$ages.add('fred') # fails, throws conversion exception
Further reading:
IF/Switch commands¶
The conditions that can be tested in an if
statement are very extensive:
Equality/inequality:
-eq|-ieq|-ceq / -ne|-ine|-cne
;Greater/less than:
-gt|-igt|-cgt|-ge|-ige / -lt|-ilt|-clt|-le|-ile|-cle
;Wildcard:
-like|-ilike|-clike|-notlike|-inotlike|-cnotlike
;Regular Expressions:
-match|-imatch|-cmatch|-notmatch|-inotmatch|-cnotmatch
;Object type check:
-is|-isnot
;Array <op> value:
-contains|-icontains|-ccontains|-notcontains|-inotcontains|-cnotcontains
;Value <op> array:
-in|-iin|-cin|-notin|-inotin|-cnotin
Logical operators:
-not|!|-and|-or|-xor
Bitwise operators:
-band|-bor|-bxor|-bnot|-shl|-shr
;PowerShell expressions:
Test-Path|Get-Process
;PowerShell pipeline:
(Get-Process | Where Name -eq Notepad)
;Null checking:
($null -eq $value)
;
There is also a switch
statement for comparing against multiple values.
#requires -version 2
Set-StrictMode -Version 2
$apple = 10
$pear = 20
if ( $apple -gt $pear ) {
write-host('apple is higher than pear')
}
elseif ( $apple -lt $pear ) {
write-host('apple is lower than pear')
}
else {
write-host('apple and pear are equal')
}
$path = 'file.txt'
$alternatePath = 'folder1'
if ( Test-Path -Path $path -PathType Leaf ) {
Move-Item -Path $path -Destination $alternatePath
}
elseif ( Test-Path -Path $path ) {
Write-Warning "A file is required but a folder was given."
}
else {
Write-Warning "$path could not be found."
}
$fruit = 10
switch ( $fruit ) {
10 {
write-host('fruit is an apple')
}
20 {
write-host('fruit is an apple')
}
Default {
write-host('unknown fruit')
}
}
Further reading:
Try/Catch¶
Exception handling uses Try/Catch, but the Catch block is only invoked on terminating errors.
#requires -version 4
Set-StrictMode -Version 2
$error.clear()
# $Error is an array of recent errors, index 0 being the latest
# $Error[0] | get-member # what does an error return
# $Error[0].tostring() # error text message
# $Error[0].Exception | get-member # method, properties of the exception
# $Error[0].Exception.GetType().FullName # how to catch-it :-)
$cwd = get-childitem variable:pwd
$filename = 'cannot-readme.txt'
$path = Join-Path -path $cwd.value -childpath $filename
try {
$content = get-content -path $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
write-warning $Error[0].ToString()
exit(1)
}
catch {
write-warning $Error[0].ToString()
write-warning $Error[0].Exception.GetType().FullName # exception message type
exit(1)
}
finally {
write-warning("Resetting the Error Array")
$error.clear()
}
write-host("Fetched the content of {0}" -f $path)
exit(0)
Note the following two points in the example:
Addition of
-ErrorAction Stop
toget-content
to make it a terminating error;The
finally
block is always executed, whether an exception is thrown or not!
Further reading:
Loops¶
There are several loop constructirs for
, foreach
, while
and do .. while
.
#requires -version 4
Set-StrictMode -Version 2
$names = ('Fred', 'Wilma', 'Pebbles', 'Dino')
for ($index = 0; $index -lt $names.length; $index++) {
write-host ('{0} Flintstone' -f $names[$index])
}
# Index often written as $i, $j, $k
for ($i = 0; $i -lt $names.length; $i++) {
write-host ('{0} Flintstone' -f $names[$i])
}
foreach ($name in $names) {
write-host ('{0} Flintstone' -f $name)
}
$hash = @{ Fred = 30; Wilma = 25; Pebbles = 1; Dino = 5 }
foreach ($key in $hash.keys) {
write-host ('{0} Flintstone is {1:D} years old' -f $key, $hash[$key])
}
$index = 0;
while ($index -lt $names.length){
write-host ('{0} Flintstone' -f $names[$index])
$index += 1
}
$index = 0;
do {
write-host ('{0} Flintstone' -f $names[$index])
$index += 1
} while($index -lt $names.length)
Operators¶
PowerShell
supports the almost all the common programming language operators, with parenthesis to alter operator precedence.
#requires -version 4
Set-StrictMode -Version 2
$a = 20
$b = 10
$c = 2
# Arithmetic
$a + $b + $c # addition = 32
$a - $b - $c # subtraction = 8
$a - $b + $c # subtraction, addition = 12
$a + $b - $c # addition, subtraction = 28
$a * $b * $c # multiplication = 400
$a + $b * $c # addition, multiplication = 40
$a * $b + $c # multiplication, addition = 202
$a * ($b + $c) # multiplication, addition = 240
$a / $b / $c # division = 1
$a + $b / $c # addition, division = 15
$a / $b + $c # division, addition = 4
$a / ($b + $c) # division, addition = 1.66666666666667
$a % $b # modulus = 0
$b % $a # modulus = 10
$c % $b # modulus = 2
# Comparison
$a -eq $b # equals = False
$a -ne $b # not equals = True
$a -gt $b # greater than = True
$a -ge $a # greater than or equal = True
$a -lt $b # less than = False
$a -le $a # less than or equal = True
# Assignment
$d = $a + $b # assignment = 30
$d += $c # addition, assignment = 32
$d -= $c # subtraction, assiginment = 30
$a = $true
$b = $false
# Logical
$a -and $b # and = False
$a -or $b # or = True
-not $a # not = False
-not $a -and $b # not, and = False
$a -and -not $b # and, not = True
Backtick Operator¶
The `
is used for line continuation and to identify a “tab” and “new line” character.
Word-wrap operator
`
Newline
`n
Tab
`t
Regular Expressions¶
PowerShell supports regular expressions in much the same was as Perl
or Python
.
Table taken from TutorialsPoint.com - Regular Expression
Subquery |
Match description |
---|---|
^ |
The beginning of the line. |
$ |
The end of the line. |
. |
Any single character except newline. Using m option it to matches the newline as well. |
[…] |
Any single character in brackets. |
[^…] |
Any single character not in brackets. |
\A |
Beginning of the entire string. |
\z |
End of the entire string. |
\Z |
End of the entire string except allowable final line terminator. |
re* |
0 or more occurrences of the preceding expression. |
re+ |
1 or more of the previous thing. |
re? |
0 or 1 occurrence of the preceding expression. |
re{ n} |
Exactly n number of occurrences of the preceding expression. |
re{ n,} |
n or more occurrences of the preceding expression. |
re{ n, m} |
At least n and at most m occurrences of the preceding expression. |
a¦b |
Either a or b. |
(re) |
Groups regular expressions and remembers the matched text. |
(?: re) |
Groups regular expressions without remembering the matched text. |
(?> re) |
Matches the independent pattern without backtracking. |
\w |
The word characters. |
\W |
The nonword characters. |
\s |
The whitespace. Equivalent to [tnrf]. |
\S |
The nonwhitespace. |
\d |
The digits. Equivalent to [0-9]. |
\D |
The nondigits. |
\A |
The beginning of the string. |
\Z |
The end of the string. If a newline exists, it matches just before newline. |
\z |
The end of the string. |
\G |
The point where the last match finished. |
\n |
Back-reference to capture group number “n”. |
\b |
The word boundaries. Matches the backspace (0x08) when inside the brackets. |
\B |
The nonword boundaries. |
\n,\t,\r |
Newlines, carriage returns, tabs, etc. |
\Q |
Escape (quote) all characters up to E. |
\E |
Ends quoting begun with Q. |
Examples:
#requires -version 4
Set-StrictMode -Version 2
"fred" -match "f..d" # True (same as imatch)
"fred" -imatch "F..d" # True
"fred" -cmatch "F..d" # False
"fred" -notmatch "W..ma" # True
"fred" -match "re" # (match 're') True
"dog" -match "d[iou]g" # (dig, dug) True
"ant" -match "[a-e]nt" # (bnt, cnt, dnt, ent) True
"ant" -match "[^brt]nt" # True
"fred" -match "^fr" # (starts with 'fr') True
"fred" -match "ed$" # (ends with 'ed') True
"doggy" -match "g*" # True
"doggy" -match "g?" # True
"Fred Flintstone" -match "\w+" # (matches word Fred) True
"FredFlintstone" -match "\w+" # (matches word Fred) True
"Fred Flintstone" -match "\W+" # (matches >= 1 non-word) True
"FredFlintstone" -match "\W+" # (matches >= 1 non-word) False
"Fred Flintstone" -match "\s+" # (matches >= 1 white-space) True
"FredFlintstone" -match "\s+" # (matches >= 1 white-space) False
"Fred Flintstone" -match "\S+" # (matches >= 1 non white-space) True
"FredFlintstone" -match "\S+" # (matches >= 1 non white-space) True
"Fred Flintstone" -match "\d+" # (matches >= 1 digit 0..9) False
"Fred is 30" -match "\d+" # (matches >= 1 digit 0..9) True
"Fred Flintstone" -match "\D+" # (matches >= 1 non-digit 0..9) True
"Fred is 30" -match "\D+" # (matches >= 1 non-digit 0..9) True
"Fred Flintstone" -match "\w?" # (match >= 0 preceding pattern) True
"Fred Flintstone" -match "\w{2}" # (match 2 preceding pattern) True
"Fred Flintstone" -match "\W{2}" # (match 2 preceding pattern) False
"Fred Flintstone" -match "\w{2,}" # (match >2 preceding pattern) True
"Fred Flintstone" -match "\W{2,}" # (match >2 preceding pattern) False
"Fred Flintstone" -match "\w{2,3}" # (match >2 <=3 preceding pattern) True
"Fred Flintstone" -match "\W{2,3}" # (match >2 <=3 preceding pattern) False
'Fred Flinstone' -replace '(\w+) (\w+)', 'Wilma $2' # Wilma Flinstone
'fred Flinstone' -ireplace 'Fred (\w+)', 'Wilma $1' # Wilma Flinstone
'fred Flinstone' -replace 'Fred (\w+)', 'Wilma $1' # Wilma Flinstone
'fred Flinstone' -creplace 'Fred (\w+)', 'Wilma $1' # fred Flinstone
Entire technical books are dedicated to Regular Expressions, the above is very brief. For more details see:
Reading Files¶
#requires -version 4
Set-StrictMode -Version 2
$filename = 'file.txt'
$addCWD = $false
$path = $filename
if ($addCWD) {
$path = Join-Path -path $cwd.value -childpath $filename
}
write-host("if...then...else")
if (-not (Test-Path -path $path -pathtype leaf) ) {
write-warning("Filename, {0}, does not exist" -f $path)
exit(1)
}
else {
$count = 1
foreach ($line in get-content $path) {
write-host("{0:D3}:{1}" -f $count, $line)
$count += 1
}
$fh = get-childitem $path # get file attributes
}
write-host("try...catch")
try {
$count = 1
foreach ($line in get-content $path -ErrorAction Stop) {
write-host("{0:D3}:{1}" -f $count, $line)
$count += 1
}
$fh = get-childitem $path # get file attributes
}
catch {
write-warning $Error[0].ToString()
write-warning $Error[0].Exception.GetType().FullName # exception message type
exit(1)
}
exit(0)
Writing Files¶
Simplest approach is to use set-content, add-content and clear-content cmd-lets, which have many options not covered here.
#requires -version 4
Set-StrictMode -Version 2
$h = @{ Fred = 30; Wilma = 25; Pebbles = 1; Dino = 5 }
set-content -path "file.obj" -value $h # writes hash-table object
$path = "file.txt"
# add one line at a time, note no need to close the file
set-content -path $path -value $null # creates and closes an empty file
foreach ($key in $h.keys) {
add-content -path $path -value ("{0}:{1:D}" -f $key, $h[$key]) # adds content and closes
# ("{0}:{1:D}" -f $key, $h[$key]) | add-content -path $path # same, less intuative
}
clear-content -path $path # clear the file contents
# string with line continuation characters.
$text = "Fred:30`
Wilma:25`
Pebbles:1`
Dino:5"
$text | set-content -path $path
clear-content -path $path # clear the file contents
# string containing new-line characters.
$text = "Fred:30`nWilma:25`nPebbles:1`nDino:5"
$text | set-content -path $path
clear-content -path $path # clear the file contents
# string containing new-line characters using out-file
$text | Out-File -FilePath $path
See also:
CSV Files¶
Powershell provides cmdlets
for handling these which avoid importing into Excel
and MS Access
.
The out-gridview
renders the output the data in an interactive table.
PS> import-csv -Path file.csv -Delimeter "`t" | out-gridview # load and display a <TAB> separated file.
PS> import-csv -Path file.csv -Delimeter ";" | out-gridview # load and display a ';' separated file.
PS> get-content file.csv
Name;Age
Fred;30
Wilma;25
Pebbles;1
Dino;5
PS> $f = import-csv -delimiter ';' file.csv
PS> $f.Name # Fred Wilma Pebbles Dino
PS> $f[1].Name # Wilma
PS> $f.Age # 30 25 1 5
PS> $f[3].Age # 5
PS> for ($i =0; $i -lt $f.length; $i++) {
write-output("{0,-7} is {1:D} years" -f $f[$i].Name, $f[$i].Age)
}
PS> import-csv -delimiter ';' file.csv | out-gridview
JSON files¶
PowerShell requires that ConvertTo-Json
and ConvertFrom-Json
modules are installed.
PS> get-content file2.json
{
"family":"flintstone",
"members":
[
{"Name":"Fred", "Age":"30"},
{"Name":"Wilma", "Age":"25"},
{"Name":"Pebbles", "Age":"1"},
{"Name":"Dino", "Age":"5"}
]
}
PS> get-content file2.json | ConvertFrom-Json
family members
------ -------
flintstone {@{Name=Fred; Age=30}, @{Name=Wilma; Age=25}, @{Name=Pebbles; Age=1}, @{Name=Dino; Age=5}}
PS> $obj = get-content file2.json | convertfrom-json
PS> $obj
family members
------ -------
flintstone {@{Name=Fred; Age=30}, @{Name=Wilma; Age=25}, @{Name=Pebbles; Age=1}, @{Name=Dino; Age=5}}
PS> $obj.family # returns flintstone
PS> $obj.members[0].name # returns Fred
PS> $obj.members[0].age # returns 30
PS> $obj.members[0].age = 35 # set Fred's age to 35
PS> $obj.members[0].age # now returns 35
PS> $obj | convertto-json | add-content newfile.json # save as JSON
PS> $obj.members.name # returns: Fred Wilma Pebbles Dino
PS> $obj.members.age # returns: 35 25 1 5
PS> $obj.members.age[0] # returns 35
PS> $obj.members.age[0] = 37 # immutable, silently fails, no error
PS> $obj.members.age[0] # returns 35
PS> remove-variable -name obj # cleanup
PS> get-content newfile.json
{
"family": "flintstone",
"members": [
{
"Name": "Fred",
"Age": 35
},
{
"Name": "Wilma",
"Age": "25"
},
{
"Name": "Pebbles",
"Age": "1"
},
{
"Name": "Dino",
"Age": "5"
}
]
}
Further reading:
Reading XML files¶
Powershell
supports full manipulation of the XML DOM, read the Introduction to XML
and .NET XmlDocument Class for more detailed information. The examples shown
are very redimentary, and only show a few of the manipulations you can perform on XML objects.
Note, cmdlets Export-Clixml and
Import-Clixml provide a simplified way to save
and reload your PowerShell
objects and are Microsoft
specific.
PS> get-content .\file2.xml
<?xml version="1.0" encoding="UTF-8"?>
<family surname = "Flintstone">
<member>
<name>Fred</name>
<age>30</age>
</member>
<member>
<name>Wilma</name>
<age>25</age>
</member>
<member>
<name>Pebbles</name>
<age>1</age>
</member>
<member>
<name>Dino</name>
<age>5</age>
</member>
</family>
PS> $obj = [XML] (get-content .\file2.xml) # returns a System.Xml.XmlDocument object
PS> $obj.childnodes # returns all the child nodes
PS> $obj.xml # returns version="1.0" encoding="UTF-8"
PS> $obj.childnodes.surname # Flintstone
PS> $obj.childnodes.member.name # returns Fred Wilma Pebbles Dino
PS> $obj.childnodes.member.age # returns 30 25 1 5
PS> $obj.ChildNodes[0].NextSibling
surname member
------- ------
Flintstone {Fred, Wilma, Pebbles, Dino}
PS> $obj.GetElementsByTagName("member");
name age
---- ---
Fred 30
Wilma 25
Pebbles 1
Dino 5
PS> $obj.GetElementsByTagName("member")[0].name # returns Fred
PS> $obj.GetElementsByTagName("member")[0].age # returns 30
PS> $obj.GetElementsByTagName("member")[0].age = 35 # Errors, only strings can be used.
PS> $obj.GetElementsByTagName("member")[0].age = "35" # Fred is now older
PS> $obj.GetElementsByTagName("member")[0].age # returns 35
PS> $obj.Save("$PWD\newfile.xml") # needs a full pathname
PS> get-content newfile.xml
<?xml version="1.0" encoding="UTF-8"?>
<family surname="Flintstone">
<member>
<name>Fred</name>
<age>35</age>
</member>
<member>
<name>Wilma</name>
<age>25</age>
</member>
<member>
<name>Pebbles</name>
<age>1</age>
</member>
<member>
<name>Dino</name>
<age>5</age>
</member>
</family>
Writing XML files¶
To generate an XML file, use the XmlTextWriter Class
Note, cmdlets Export-Clixml and
Import-Clixml provide a simplified way to save
and reload your PowerShell
objects and are Microsoft
specific.
$settings = New-Object System.Xml.XmlWriterSettings # to update XmlWriterSettings
$settings.Indent = $true # indented XML
$settings.IndentChars = "`t" # <TAB> indents
$settings.Encoding = [System.Text.Encoding]::UTF8 # force the default UTF8 encoding; others ASCII, Unicode...
$obj = [System.XML.XmlWriter]::Create("C:\users\geoff\bedrock.xml", $settings) # note full-pathname
# Simpler approach but no encoding is specified in XML header and again note full-pathname
# $obj = New-Object System.XMl.XmlTextWriter('C:\users\geoff\bedrock.xml', $null)
# $obj.Formatting = 'Indented'
# $obj.Indentation = 1
# $obj.IndentChar = "`t"
$obj.WriteStartDocument() # start xml document, <?xml version="1.0"?>
$obj.WriteComment('Bedrock Families') # add a comment, <!-- Bedrock Families -->
$obj.WriteStartElement('family') # start element <family>
$obj.WriteAttributeString('surname', 'Flintstone') # add surname attribute
$obj.WriteStartElement('member') # start element <member>
$obj.WriteElementString('name','Fred') # add <name>Fred</name>
$obj.WriteElementString('age','30') # add <age>30</age>
$obj.WriteEndElement() # end element </member>
$obj.WriteStartElement('member') # start element <member>
$obj.WriteElementString('name','Wilma') # add <name>Wilma</name>
$obj.WriteElementString('age','25') # add <age>25</age>
$obj.WriteEndElement() # end element </member>
$obj.WriteStartElement('member') # start element <member>
$obj.WriteElementString('name','Pebbles') # add <name>Pebbles</name>
$obj.WriteElementString('age','1') # add <age>1</age>
$obj.WriteEndElement() # end element </member>
$obj.WriteStartElement('member') # start element <member>
$obj.WriteElementString('name','Dino') # add <name>Dino</name>
$obj.WriteElementString('age','5') # add <age>5</age>
$obj.WriteEndElement() # end element </member>
$obj.WriteEndElement() # end element <family>
$obj.WriteEndDocument() # end document
$obj.Flush() # flush
$obj.Close() # close, writes the file
PS> get-content C:\users\geoff\bedrock.xml
<?xml version="1.0" encoding="utf-8"?>
<!--Bedrock Families-->
<family surname="Flintstone">
<member>
<name>Fred</name>
<age>30</age>
</member>
<member>
<name>Wilma</name>
<age>25</age>
</member>
<member>
<name>Pebbles</name>
<age>1</age>
</member>
<member>
<name>Dino</name>
<age>5</age>
</member>
</family>
PS> remove-variable -name settings
PS> remove-variable -name obj
PS> remove-item C:\users\geoff\bedrock.xml
Log files¶
# tailing a log file
PS> get-content -wait -last 10 "application.log"
PS> get-content -wait "application.log" | out-host -paging
# writing a time-stamped log message
PS> $LogFile = "application.log"
PS> $DateTime = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) # [03/22/21 21:07:06]
PS> $LogMessage = "$Datetime: $LogString"
PS> add-content $LogFile -value $LogMessage
Formatting Variables¶
Very similar to Python -f
operator, examples use write-host
but can be used with other cmdlets, such as assigment.
Specified as {<index>, <alignment><width>:<format_spec>}
PS> $shortText = "Align me"
PS> $longerText = "Please Align me, but I am very wide"
PS> write-host("{0,-20}" -f $shortText) # Left-align; no overflow.
PS> write-host("{0,20}" -f $shortText) # Right-align; no overflow.
PS> write-host("{0,-20}" -f $longerText) # Left-align; data overflows width.
PS> write-host("Room: {0:D}" -f 232) # Room: 232
PS> write-host("Invoice No.: {0:D8}" -f 17) # Invoice No.: 00000017
PS> $invoice = "{0}-{1}" -f 00017, 007 # (integers) so invoice = 17-7
PS> $invoice = "{0}-{1}" -f '00017', '007' # (strings) so invoice = 00017-007
PS> write-host("Temp: {0:F}°C" -f 18.456) # Temp: 18.46°C
PS> write-host("Grade: {0:p}" -f 0.875) # Grade: 87.50%
PS> write-host('Grade: {0:p0}' -f 0.875) # Grade: 88%
PS> write-host('{1}: {0:p0}' -f 0.875, 'Maths') # Maths: 88%
# Custom formats
PS> write-output('{1:00000}' -f 'x', 1234) # 01234
PS> write-output('{0:0.000}' -f [Math]::Pi) # 3.142
PS> write-output('{0:00.0000}' -f 1.23) # 01.2300
PS> write-host('{0:####}' -f 1234.567) # 1235
PS> write-host('{0:####.##}' -f 1234.567) # 1234.57
PS> write-host('{0:#,#}' -f 1234567) # 1,234,567
PS> write-host('{0:#,#.##}' -f 1234567.891) # 1,234,567.89
PS> write-host('{0:000}:{1}' -f 7, 'Bond') # 007:Bond
PS> get-date -Format 'yyyy-MM-dd:hh:mm:ss' # 2020-04-27T07:19:05
PS> get-date -Format 'yyyy-MM-dd:HH:mm:ss' # 2020-04-27T19:19:05
PS> get-date -UFormat "%A %m/%d/%Y %R %Z" # Monday 04/27/2020 19:19 +02
More detailed formatting examples:
Ouput methods:
Running PowerShell scripts¶
PowerShell is an often abused hackers attack vector, so modern versions of Windows prevent PowerShell scripts from
being executed out-of-the-box, although the cmd-lets
can be run.
Many articles suggest the disabling this security feature… DO NOT DO THIS
Furthermore most companies harden their Windows laptop and server installations, so disabling may not work anyway.
Ways to work with this restriction, are not intuitive… it took me some time to figure it out, and I am still be no means an expert, hopefully this will get you started, and you are always welcome to contact me to improve this section.
The execution-policy, controls the execution of PowerShell scripts, good references to read are:
If you start PowerShell
as administrator, then you can change the ‘execution-policy’, and you should
change the ‘CurrentUser’, which is your execution-policy rights, see Get-ExecutionPolicy link.
A default install will most likely look as shown.
PS> Get-ExecutionPolicy -list
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Restricted
LocalMachine Restricted
# Permit yourself to run PowerShell scripts
PS> Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope CurrentUser # Must be Signed
PS> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser # Must be RemotelySigned
PS> Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser # Disable
Choosing Unrestricted means that any PowerShell script, even ones inadvertently or unknowingly downloaded from the Internet will run as you, and with your privileges, so Avoid Doing This.
When developing your scripts you can try using the following to avoid having certificates installed and updating the signature each time you change the script.
PS> powershell.exe -noprofile -executionpolicy bypass -file .\script.ps1
This may not be permitted on Corporate laptops which usually have additional security restrictions.
PowerShell Exection Policies¶
See: About Execution Policies for more details.
PowerShell’s execution policies:
Restricted
does not permit any scripts to run (.ps1xml, .psm1, .ps1);AllSigned
, prevents running scripts that do not have a digital signature;RemoteSigned
prevents running downloaded scripts that do not have a digital signature;Unrestricted
runs scripts without a digital signature, warning about non-local intranet zone scripts;Bypass
allows running of scripts without any digital signature, and without any warnings;Undefined
no execution policy is defined;
PowerShell’s execution policy scope:
MachinePolicy
set by a Group Policy for all users of the computer;UserPolicy
set by a Group Policy for the current user of the computer;Process
current PowerShell session, environment variable$env:PSExecutionPolicyPreference
;CurrentUser
affects only the current user,HKEY_CURRENT_USER
registry subkey;LocalMachine
all users on the current computer,HKEY_LOCAL_MACHINE
registry subkey;
By default on a Windows Server the execution policy is, LocalMachine RemoteSigned
, but for your Windows Laptop or Desktop it will be LocalMachine Restricted
.
To change the execution policy, you must start a PowerShell as Administrator and use Set-ExecutionPolicy
as shown, you will be prompted to confirm this action.
In a commercial or industrial environment ask your Windows Adminstrator, but company policy may be AllSigned.
# Stops running of downloaded scripts
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned # sets: LocalMachine RemoteSigned
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy Restricted # sets: LocalMachine Restricted
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy Undefined # sets: LocalMachine Undefined
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser # just me
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy AllSigned # mandate code-signing
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy Default # restore: LocalMachine defaults
Generating and Installing Certificates¶
This section will show how to use openssl
and WLS2
to generate self-signed certificates
To come shortly.
How to sign scripts for your own use.¶
Draft and not completely finished.
To add a digital signature to a script you must sign it with a code signing certificate:
Purchased from a certification authority, which allows executing your script on other computers;
A free self-signed certificate which will only work on your computer;
Typically, a self-signed certificate is only used to sign your own scripts and to sign scripts that you get from other sources that you have verified to be safe, and should be used in an industrial or commercial enviroment.
Microsoft’s official guide:
Add an Authenticode signature to a PowerShell script or other file.
Generate and export certificates for Point-to-Site using PowerShell
How to get around signed scripts¶
Some proposals to avoid signing PowerShell scripts.
Some internet posts recommend disabling the execution policy, but I would advise against.
### DO NOT DO THE FOLLOWING, UNLESS YOU KNOW WHAT YOU ARE DOING ###
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
PS C:\WINDOWS\system32> Set-ExecutionPolicy -ExecutionPolicy Unrestricted