Notes on Learn PowerShell in a Month of Lunches (4th Edition)
Install the .NET Framework:
# pacman -S dotnet-sdk
Install PowerShell as a .NET tool:
$ dotnet tool install --global powershell
Make sure to extend your path variable to find the pwsh
binary (e.g. in ~/.bashrc
):
export PATH="$PATH:~/.dotnet/tools"
Reload the environment variables:
$ source ~/.bashrc
Start PowerShell:
$ pwsh
Update the help files (takes a shitload of time):
Update-Help
Same, but in English (gives you a complete help):
Update-Help -UICulture en-US
Save the help to DIRECTORY
(a.k.a. take a huge XML dump):
Save-Help -DestinationPath DIRECTORY -UICulture en-US
Show help (not paged):
Get-Help TOPIC
Show help (paged):
Help TOPIC
Also accepts wildcards:
Help *foo*
Show full help:
Help -Full TOPIC
Show help online (i.e. in a browser):
Help -Online TOPIC
Show example section of help:
Help -Example TOPIC
List all commands:
Get-Command
List all commands for a noun/verb:
Get-Command -Noun NOUN
Get-Command -Verb VERB
There are different kinds of commands:
- Cmdlet ("command-let"): PowerShell command-line utilities written in a .Net-Language (e.g. C#)
- Function: piece of code written in PowerShell
- Application: executable written in any language
Scripts have the .ps1
extension.
Command names are all case insensitive and follow the convention [Verb]-[Noun]
. (See Get-Verb
for conventionally used "verbs".)
The invocation operator &
can run a command with a name stored in a variable:
$cmd = 'ping'
& $cmd google.com
The operator %-- [...]
prevents parsing of [...]
.
Policies (set machine-wide) protect the user from accidentally running harmful scripts:
Restricted
(default on Windows 10): no script execution allowedRemoteSigned
(default on Windows Server): all local script and signed remote script execution allowedAllSigned
(stricter): signed script execution allowedUnrestricted
: all script execution allowedBypass
: for embedded PowerShell use
Commands:
Get-ExecutionPolicy
Set-ExecutionPolicy
(only as Admin)
The PowerShell can be started with the -ExecutionPolicy POLICY
option.
The Execution Policy can also be configured by a Group Policy Object (GPO).
Aliases only emcompass the command, but no parameters. (There are parameter aliases, too: see Get-Help about_CommonParameters
.)
New-Alias
: create a new temporary alias (for the current session)Export-Alias
: export aliasesImport-Alias
: import aliases
Any parameter with its name surrounded in []
in the help text is a positional parameter:
Get-Foo [[-Param] ...]
A Provider (Noun: PSProvider
) is an adapter from a data storage to a virtual disk drive.
Get-PSProvider
: lists the providersGet-PSDrive
: list the virtual drives (mount points for data storage)
Providers (with drives) available by default are:
Registry
:HKLM
(forHKEY_LOCAL_MACHINE
),HKCU
(forHKEY_CURRENT_USER
) for the registryAlias
:Alias
for command alias namesEnvironment
:Env
for environment variablesFileSystem
:C
,Temp
for the file systemFunction
:Function
for PowerShell functionsVariable
:Variable
for PowerShell variables
Providers work with different kind of items (e.g. FileSystem
with files and directories, Environment
with environment variables).
Get-Command -Noun *item*
: lists all commands to deal with items- not all providers support all commands
- Verbs for
Item
commands (with different meanings depending on the provider):Clear
Copy
Get
Move
New
Remove
Rename
Set
- Nouns for
Item
commands:ItemProperty
(e.g. for files, but not for environment variables)ChildItem
(e.g. for directories, but not for files)
Wildcards (*
: matching 0..n
, ?
: matching 0..1
characters) are supported in -Path
parameters. Use -LiteralPath
if the wildcards should not be expanded.
Set-Location -Path DRIVE:PATH
cd PATH
New-Item FOLDER -ItemType Directory
mkdir FOLDER
Get-ChildItem
dir
Get-ChildItem Env:
env
Set-Item -Path Env:/FOO -Value 123
export FOO=123
Set-Location -Path HKCU:
move toHKEY_CURRENT_USER
in registry- no Unix equivalent
Set-ItemProperty -Path dwm -PSProperty EnableAeroPeek -Value 0
: setEnableAeroPeek
property ofdwm
registry key to0
The Pipe operator |
in PowerShell can be used like its Unix Shell equivalent, but it forwards objects rather than text.
Get-ChildItem | Where-Object -Property Name -Eq 'README.md'
ls | grep '^README.md$'
Get-ChildItem | Select-Object -Property Name
- think of a combination of
ls -l
andcut
- alternative notation:
(Get-ChildItem).Name
- think of a combination of
Get-ChildItem -Path .\labs\ | Select-Object -First 2
ls labs | head -2
Several formats are allowed for Export-*
and Import-*
commands:
Export-Csv
/Import-Csv
: Comma Separated Values-Delimiter ';'
: defines delimiter;
Export-Clixml
/Import-Clixml
: Serialized Objects as XML
Use the -IncludeTypeInformation
parameter to include type information, which allows for a nicely formatted output of an import later:
> Get-ChildItem -Path .\labs\ | Export-Csv -IncludeTypeInformation -Path labs.csv
> Import-Csv -Path labs.csv
More formats are supported via the ConvertTo
verb, e.g.:
ConvertTo-Csv
ConvertTo-Html
ConvertTo-Json
ConvertTo-Xml
Examples:
> Get-Item -Path README.md | ConvertTo-Json
> Get-Item -Path README.md | ConvertTo-Html > readme.html
Not the content is converted, but the Item as a PowerShell object, mind you!
The default output goes formatted to the screen:
> ... | Out-Default
> ... | Out-Host
Without colors:
> ... | Out-String
Discard output (equivalent to Unix: > /dev/null
):
> ... | Out-Null
Redirect to a file:
> Get-ChildItem > foo.txt
> Get-ChildItem | Out-File -Path foo.txt
Unlike the >
operator, the Out-File
Cmdlet supports various parameters (e.g. encoding).
Both the running PowerShell ($ConfirmPreference
) and each Cmdlet have their own impact level:
- If the Cmdlet's impact level is bigger/equal than the PowerShell's, the user is asked for confirmation.
- Otherwise, the Cmdlet is executed without further ado.
- Cmdlets support the
-Confirm
parameter to enforce confirmation. - The
-WhatIf
parameter let's the Cmdlet run in dry mode, just showing the impact the Cmdlet would cause.
Compare the PID (process id) of the running Notepad and Bash process:
> Compare-Object -ReferenceObject (Get-Process -Name notepad) -DifferenceObject (Get-Process -Name bash) -Property Id
<=
: only on the left side (-ReferenceObject
)=>
: only on the right side (-DifferenceObject
)
When the result from Left-Command
is piped into Right-Command
(such as in
Left-Command | Right-Command
), PowerShell needs to figure out which parameter
of Right-Command
can accept the object produced by Left-Command
, for which
there are two strategies to be tried in the following order:
ByValue
: The type of the output object fromLeft-Command
is matched to the parameter types ofRight-Command
.ByPropertyName
: The output object ofLeft-Command
has a property with the same name as a parameter ofRight-Command
that accepts pipeline input (see Accept pipeline input? in the parameter's documentation). If there are multiple matching properties, all of them are used to exchange data fromLeft-Command
toRight-Command
.
If neither works, use parenthetical commands instead:
> Left-Command | Right-Command
ERROR
> Right-Command (Left-Command)
OK
Use Select-Object -ExpandProperty PROPERTY
to extract the content of a
property (i.e. its value) from an output object.
Commands can be added by installing modules. The module PowerShellGet manages modules from online repositories ("Galleries"), e.g. PowerShellGallery. Make sure to check the compatibility under the section PSEditions (e.g. Core) for each module. Modules can not only add commands, but also providers.
Repositories can be registered using the Register-PSRepository
Cmdlet.
Modules have a prefix, e.g. Az
for the Azure
module. This prefix is used in the nouns of commands to avoid name conflicts. (Use fully qualified paths, such as Module\Cmdlet
in case of a conflict.)
Install a Module (e.g. Azure, without confirmation prompt):
> Install-Module -Name Az -Force
Check if the module was installed:
> Get-Module -Name Az -ListAvailable
See Update-Module
and Remove-Module
for updating and removing a module, respectively.
Import the module into the current PowerShell session, or do so with a custom prefix:
> Import-Module -Name Az
> Import-Module -Name Az -Prefix Cloud
List the available commands (with paging):
> Get-Command -Noun Az* | Out-Host -Paging
Connect to an Azure account (opens browser):
> Connect-AzAccount
If you have multiple subscriptions, use the Select-AzSubscription
Cmdlet with
the -SubscriptionName
parameter to pick one.
If you only have a single subscription, set it as the active context:
> Get-AzSubscription | Select-Object Property Id | Set-AzContext
Register a resource provider for storage:
> Register-AzResourceProvider -ProviderNamespace Microsoft.Storage
Get information about available locations:
> Get-AzLocation | Select-Object -Property Location,DisplayName,Type,PhysicalLocation
Query existing resource groups within a location:
> Get-AzResourceGroup -Location switzerlandnorth
Create a new resource group called test
, if none exists:
> New-AzResourceGroup -Location switzerlandnorth -Name test
Create a new storage account:
> New-AzStorageAccount -ResourceGroupName test -Name patrickbucher -SkuName Standard_ZRS -Location switzerlandnorth
Remove a storage account (without confirmation):
> Remove-AzStorageAccount -Name patrickbucher -ResourceGroupName test -Force
A Cmdlet like Get-Processes
shows a collection of processes as a table. Each
table row represents an object with various members—properties
(information about them) and methods (actions to run on them). Every property
has a value.
There are usually more properties than are being shown in the console output.
(Pipe the result through a ConvertTo-*
Cmdlet to see all properties.)
Inspect the members of an object, e.g. the current Directory
(works on
single and multiple items):
> Get-Item -Path . | Get-Member
> Get-Item -Path . | Get-Member -MemberType Property
Members of the type ScriptProperty are added dynamically by the running PowerShell process.
Collections of objects can be sorted by a property:
> Get-Process | Sort-Object -Property CPU
> Get-Process | Sort-Object -Property CPU,ID -Descending
Properties can be extracted from objects:
> Get-Process | Select-Object -Property Name,ID,CPU
Filter collections by the objects' properties:
> Get-Process | Where-Object -Property Name -Eq -Value dotnet
> Get-Process | Where-Object -Property Name -Like -Value pw*
> Get-Process | Where-Object -Property CPU -GT -Value 10
The original objects (e.g. processes) are converted into generic PSObject
instances by such commands, which come with their own rules for output.
Many Cmdlets (Batch Cmdlets) can deal with multiple inputs.
As the left command's output is consumed by the right command, it needs to be
explicitly passed through in order to become visible using the -PassThru
parameter:
> Get-ChildItem folder | Copy-Item -Destination backup -PassThru
Other Cmdlets can only deal with single inputs, and therefore the objects need
to be enumerated. This could either happen by the means of scripting, or by
using specialized Cmdlets. Consider a file containing patterns (patterns.txt
):
*.toml
*.rs
*.md
In order to filter the file names for all those patterns, the ForEach-Object
Cmdlet can be used to enumerate them, invoking the Get-ChildItem
Cmdlet in a
script block with every pattern, referred as $_
:
> Get-Content patterns.txt | ForEach-Object -Process { Get-ChildItem-Recurse ~/projects -Name $_ }
Instead of the (positional) -Process
parameter, use -Parallel
to execute the
script block concurrently:
> Get-Content patterns.txt | ForEach-Object -Parallel { Get-ChildItem-Recurse ~/projects -Name $_ }
To measure execution time, surround the entire line in a script block, to be
called with Measure-Command
:
> Measure-Command { Get-Content … }
By default, no more than 5 processes are run in parallel. Use -ThrottleLimit X
to set the upper limit to X
, for which the number of supported CPU threads is
a good setting.
Formatting options are configured in *.format.ps1xml
files within the
PowerShell installation folder $PSHome
. The PSReadLine.format.ps1xml
contains basic configuration of the shell (do not edit it, it's digitally
signed).
The Cmdlet Get-FormatData
shows mappings of TypeNames
to
FormatViewDefinition
. Use the Update-TypeData
Cmdlet to add definitions,
using the -Path
parameter to specify an XML file containing the custom
format rules.
Cmdlets with the Out
don't work with standard objects, but pass them to the
formatting system for conversion.
Objects with up to four properties are displayed as a table. If more properties are to be formatted, they are shown as a list of key-value pairs. (Note that the table headers may differ from property names.)
For better control of the output, use the Format-*
Cmdlets:
Format-Table
: tabular output-Property
: specify the output columnsGet-Process | Format-Table -Property Name,CPU,VM
-GroupBy
: show multiple tables grouped by a property (the input should already be sorted by that particular property)Get-Process | Sort-Object -Property PriorityClass | Format-Table -GroupBy PriorityClass
-Wrap
: wrap cell content instead of abridging it with an ellipsis (…
)
Format-List
: list outputFormat-Wide
: output list items with single property in multiple columns-Col
: number of columnsGet-Process | Format-Wide -Property Name -Col 5
Make sure the Format-
Cmdlet is the last one in the pipeline (except for an
additional Out-
Cmdlet); processing formatted input will cause trouble
otherwise.
Do not mix up multiple kinds of objects when formatting, unless respective formatting rules have been put in place.
Use hash tables to map custom headers to columns (e.g. the Id
property of the
process to the header PID
):
> Get-Process | Format-Table -Property Name,@{name='PID',expression={$_.Id}},CPU
The hash table accepts additional properties such as formatstring
and align
.
For graphical output, use the Out-GridView
Cmdlet from the
Microsoft.PowerShell.GraphicalTools
module.
Results can be filtered either left by instructing the Cmdlet to do so by
various properties (e.g. -Name
), or right by filtering the output of a
Cmdlet through an additional piped Cmdlet. Filter left or early filtering is
usually more efficient and therefore should be preferred to filter right.
The Where-Object
(aliased as where
) offers powerful filtering facilities
with its -FilterScript
(positional) parameter, which can be applied to the
piped-in object, referred to as $_
:
-Eq
: test for equality-Ne
: test for inequality-Gt
/-Lt
: greater/less than-Ge
/-Le
: greater/less than or equal to-Ceq
,-Cne
,-Cgt
,-Clt
,-Cge
,-Cle
: case-sensitive variants of above parameters for string comparisons-Like
/-NotLike
: pattern matching (*
,?
, etc.)-CLike
/-CNotLike
: pattern matching, but case-sensitive-Match
/-NotMatch
: matching against a regular expression-CMatch
/-CNotMatch
: case sensitive matching against a regular expression-And
/-Or
/-Not
: logical operations to combine or negate expressions
A PowerShell Cmdlet can be executed on a remote computer HOSTNAME
as follows:
Invoke-Command -ComputerName HOSTNAME
On Windows, remoting works over WSMan (Web Services for Management) via HTTP/HTTPS, run by Windows Remote Management (WinRM) for Windows.
On Linux and macOS, remoting works over SSH.
Objects are serialized/deserialized through XML over the network; therefore, methods/actions are not supported, but all data can be inspected on a remote computer.
TODO: continue with chapter 13 (13.2, p. 159)
- PSRP: PowerShell Remoting Protocol
PowerShell commands are executed synchronuously, i.e. the shell is blocked until a command has finished its execution. However, commands can be moved into the background for asynchronuous execution, allowing the user to interact with the shell while another command is executing. Only commands running synchronuously can be interactive; commands running asynchronuously must be provided with all required input as they are run.
Commands running the background are referred to as jobs.
Start a job (with a custom name):
> Start-Job -ScriptBlock { Get-Date | Out-File -Path date.txt }
> Start-Job -Name WriteDateJob -ScriptBlock { Get-Date | Out-File -Path date.txt }
Specify -FilePath
instead of -ScriptBlock
to run a script from a file. The
-WorkingDirectory
can also be specified.
A job is run in its own process. The Start-ThreadJob
Cmdlet runs a script in a
separate thread within the current process:
> Start-ThreadJob -ScriptBlock { Get-Date | Out-File -Path date.txt }
By default, no more thread jobs than 10 can be run. Pass the -ThrottleLimit
parameter to the Start-ThreadJob
Cmdlet to override this limit.
Thread jobs start up faster and have less overhead than regular jobs, but slow down the current shell process. Use thread jobs for short-lived, quick tasks; use regular jobs for long-running, heavy tasks.
To start jobs remotely, use the Invoke-Command
Cmdlet together with the
-AsJob
(and optionally the -JobName
) parameter. Many other Cmdlets, such as
New-AzVM
, support the -AsJob
parameter, too.
The current shell's jobs can be listed using the Get-Job
Cmdlet. Pass the
-Id
parameter to see the job details:
> Get-Job -Id 1 | Format-List
The output of a job can be received using the Receive-Job
Cmdlet. This output
will be consumed, unless the -Keep
parameter is specified.
> Start-Job -ScriptBlock { Write-Output "Hello, Job!" } | Select-Object -Property Id
12
> Receive-Job -Id 12 -Keep
Hello, Job!
> Receive-Job -Id 12
Hello, Job!
> Receive-Job -Id 12
[nothing]
A job that started other jobs refers to them in its ChildJobs
property. Those
jobs can be inspected by their name:
> Get-Job -Id 7 | Select-Object -Property ChildJobs
{Job8}
> Get-Job -Name Job8
Use the following Cmdlets to manage jobs:
Remove-Job
: delete a jobStop-Job
: terminate a stuck jobWait-Job
: move a running job back to the foreground
Remove all completed jobs:
> Get-Job | Where-Object -Property State -Eq -Value Completed | Remove-Job
Stop all blocked jobs:
> Get-Job | Where-Object -Property State -Eq -Value Blocked | Stop-Job
Await all pending jobs:
> Get-Job | Wait-Job
The Get-Member
Cmdlet shows the TypeName
of an object:
> Write-Output "hello" | Get-Member
TypeName: System.String
Values can be stored in variables, prefixed with a sigil ($
):
> $name = "Joe"
Variable names can constist of letters, numbers, underscores, and even—god forbid—spaces, for which the name has to be written in curly braces:
> ${full name} = "Joe Doe"
> Write-Output ${full name}
Joe Doe
Variables are expanded within double quotes, but not so within single quotes:
> $now = (Get-Date)
> Write-Output 'now is $now'
now is $now
> Write-Output "now is $now"
now is 05/29/2024 21:48:57
The backtick can be used as an escape character, e.g. to escape the sigil, and to encode characters such as tabs or newline:
> Write-Output "`$now`tis`n$now"
$now is
05/29/2024 21:48:57
Variables can store multiple items when assigned a comma-separated list, which are stored as arrays with a zero-based index (0 is the first, -1 the last element):
> Write-Output $names
Alice
Bob
Charlene
> Write-Output $names[0]
Alice
> Write-Output $names[-1]
Charlene
> Write-Output $names.Count
3
Use a subexpression $()
for advanced expansion within double quotes:
> Write-Output "The first name is $($names[0])"
The first name is Alice
Properties and methods can be accessed directly on the variable:
> $drink = 'Beer'
> $drink.Replace('ee', 'ie')
Bier
Arrays can be enumerated using the ForEach-Object
Cmdlet:
> $names | ForEach-Object { $_.ToUpper() }
ALICE
BOB
CHARLENE
If a member isn't found on the collection itself but on the individual items, PowerShell unrolls the access to the elements of the collection:
> $names.ToUpper()
ALICE
BOB
CHARLENE
Variables can be coerced into types, e.g. when reading input interactively from
the user using the Read-Host
Cmdlet:
> $name = Read-Host "Name"
Name: Joe
> [int]$age = Read-Host "Age"
Age: 13
Unlike the string $name
, $age
can be used for arithmetic:
> $name * 3
JoeJoeJoe
$ $age * 3
39
The variable $name
has the type System.String
, whereas $age
was coerced
into System.Int32
.
An error will be thrown if the coercion doesn't work:
> [int]$age = Read-Host "Weight"
Weight: average
MetadataError: Cannot convert value "average" to type "System.Int32"
Commonly used types are:
[int]
: integer numbers[single]
and[double]
: single and double precision floating point numbers[string]
: strings of characters[char]
: a single character[xml]
: an XML document
Variables can also be dealt with using the following Cmdlets:
New-Variable
Set-Variable
Remove-Variable
Get-Variable
Clear-Variable
$PSVersionTable
: version information$PSModulePath
: paths where modules are stored$_
: piped-in object$PSHome
: installation folder
Interactive input can be obtained using the Read-Host
Cmdlet:
> $input = Read-Host -Prompt "What's your name?"
Output can be produced using the Write-Host
Cmdlet:
> Write-Host -Object "Hello, World!"
Foreground and background colors can be specified:
> Write-Host -ForegroundColor red -BackgroundColor yellow -Object "Nice!"
Use the Write-Verbose
Cmdlet for messages that are not crucial for the user,
which is only displayed if the $VerbosePreference
variable is set to
"Continue"
:
> $VerbosePreference = "Continue"
> Write-Verbose -Message "ok, fine
VERBOSE: ok, fine
The same mechanism works for the Write-Warning
Cmdlet in accordance with the
$WarningPreference
variable.
Write-Host
sends objects to the output, whereas Write-Output
sends objects
to the pipe:
> Write-Host -InputObject "Hello"
Hello
> Write-Host -InputObject "Hello" | Select-Object -First 1
Hello
> Write-Host -InputObject "Hello" | Select-Object -First 0
[nothing]
> Write-Host -Object "Hello" | Select-Object -First 0
Hello
There are a couple of Write-*
Cmdlets with according variables (and default
values) that deal with different kinds of messages:
Write-Warning
with$WarningPreference
(default:Continue
)Write-Verbose
with$VerbosePreference
(default:SilentlyContinue
)Write-Debug
with$DebugPreference
(default:SilentlyContinue
)Write-Error
with$ErrorActionPreference
(default:Continue
)Write-Information
with$InformtionPreference
(default:SilentlyContinue
)
The value Continue
produces, whereas SilentlyContinue
suppresses output.
The Write-Progress
Cmdlet can display progress bars.
Define documentation for the script within <#
and #>
. Define parameters for
the script within a param
block (examples/Say-Hello.ps1
):
<#
.SYNOPSIS
Say-Hello greets the user.
.DESCRIPTION
The user is greeted with the string "Hello", followed by either the provided
name, or by the name "Anonymous.
.PARAMETER Name
The name of the user to be greeted. Default: Anonymous.
#>
Param (
$Name = 'Anonymouns'
)
Write-Host -Message "Hello, $Name."
Usage:
> Say-Hello
Hello, Anonymouns.
> Say-Hello -Name Joe
Hello, Joe.
Use the [CmdletBinding()]
attribute as the first line (after the
documentation) for the script to unlock various features, such as automatic
parameters like -Verbose
, which enable verbose output of the Write-Verbose
Cmdlet regardless of the $VerbosePreference
setting.
Use the [Parameter]
attribute for various purposes:
[Parameter(Mandatory=$True)]
: Prompts the user interactively for the respective parameter if it isn't provided on the command line.[Parameter(HelpMessage="…")]
: Provides a help message for the respective parameter.[Parameter(ValueFromPipeline=$True)]
: Accept values from piped-in objects for this parameter.[Parameter(ValueFromPipelineByPropertyName=$True)]
: Match piped-in object's property by name for this parameter.
The [Alias('Foo')]
attribute allows to use Foo
as an alternative name for
the respective parameter.
A set of valid values (e.g. 1
, 2
, and 3
) can be provided using the
[ValidateSet(1,2,3)]
attribute.
Example (examples/Do-AdvancedStuff.ps1
):
<#
.SYNOPSIS
Demonstrate advanced scripting features.
.DESCRIPTION
Prompts for the FirstName and a Choice of flavour, which will be printed.
.PARAMETER FirstName
The first name of the user picking a flavour.
.PARAMETER Choice
The flavour of the user's choice: 1 for chocolat, 2 for strawberry, and 3 for banana.
.EXAMPLE
Do-AdvancedStuff -Name Joe -Choice 1
#>
[CmdletBinding()]
Param (
[Alias('Name')]
[Parameter(Mandatory=$True, HelpMessage="The name of the subject at the keyboard.")]
[string]$FirstName,
[ValidateSet(1,2,3)]
[Parameter(Mandatory=$True, HelpMessage="1) chocolat, 2) strawberry, 3) banana")]
[int]$Choice
)
Write-Verbose -Message "Entering…"
Write-Host -Message "You are $FirstName and like option $Choice."
Write-Verbose -Message "Exiting…"
Usage:
> ./Do-AdvancedStuff.ps1 -FirstName Joe -Choice 1
You are Joe and like option 1.
> ./Do-AdvancedStuff.ps1 -Name Joe -Choice 1
> ./Do-AdvancedStuff.ps1
FirstName: Jane
Choice: 2
You are Jane and like option 2.
> ./Do-AdvancedStuff.ps1 -Name Jack -Choice 3 -Verbose
VERBOSE: Entering…
You are Jack and like option 3.
VERBOSE: Exiting…
Loop over a range of values:
> foreach ($i in 1..5) { Write-Host $i }
1
2
3
4
5
For use within a pipeline, use the Foreach-Object
Cmdlet (or its alias %
):
> 1..5 | ForEach-Object { Write-Host $_ }
1
2
3
4
5
Use the -Parallel
parameter to process the elements concurrently (output
non-deterministic):
> 1..5 | ForEach-Object -Parallel { Write-Host $_ }
1
3
2
4
5
The level of parallel processing (default: 5) can be modified using the
-ThrottleLimit
parameter.
Use while
to loop as long as a condition holds true:
> $i = 0
> while ($i -LT 5) { $i++; Write-Host $i }
1
2
3
4
5
Use do
/while
to loop with a condition at the end, running at least once:
> [int]$secret = 3
> do { [int]$guess = Read-Host "Guess" } while ($guess -NE $secret)
Guess: 4
Guess: 1
Guess: 2
Guess: 3
- Pick variable names similar to the parameters they are being used for. (E.g.
$path
to be used with-Path
.) - For command sequences connected by pipes, insert a newline after the pipe for better readability and shorter lines.
- Escape line breaks now following pipes with the backtick character.
- Write out the full command an parameter names instead of relying on aliases, abbreviations, and positional parameters. This will render the script easier readable.
- Prefer object output over pre-formatted output, for it makes the processing of the script's result easier downstream.
Use -Match
and its case-sensitive counterpart -CMatch
to test a value
against a regular expression (e.g. to check if could be an IP address):
> "127.0.0.1" -Match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
True
> "1.2.3.4" -Match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
True
> "localhost" -Match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
False
Provide the Select-String
Cmdlet with a regex -Pattern
:
$pattern = "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})"
$ip = "127.0.0.1"
> (Select-String -InputObject $ip -Pattern $pattern).Matches.Groups |
>> Select-Object -Property Value
Value
-----
127.0.0.1
127
0
0
1
Errors of the current session are stored in the $Error
array, with the most
recent error under index 0:
> 1 / 0
RuntimeException: Attempted to divide by zero.
> $Error[0]
RuntimeException: Attempted to divide by zero.
The $ErrorActionPreference
variable defines the behaviour when an error
occurs:
Continue
(default): display error and continueBreak
: enter the debuggerIgnore
: suppress the error (only applicable for single commands)Inquire
: displays the error and asks the user to continue or notSilentlyContinue
: continue without promptStop
: display the error and stop execution (enters exception handling)Suspend
: stop and resume after error inspection (only applicable for single commands)
There are two common parameters for error handling, which should be preferred
over setting the session- or script-wide $ErrorActionPreference
variable:
ErrorAction
: defines what should happen upon an error- see possible values above for
$ErrorActionPreference
- see possible values above for
ErrorVariable
: defines the variable to store the error in
Store the error in the variable $x
:
> Get-ChildItem -Path DoesNotExist -ErrorVariable x
Append the error to the variable $x
:
> Get-ChildItem -Path DoesNotExist -ErrorVariable +x
Inspect the error and continue:
> Get-ChildItem -Path DoesNotExist -ErrorAction Inquire
[shows prompt on how to continue]
Use try
/catch
with -ErrorAction Stop
(!) for exception handling:
> try { Get-ChildItem -Path DoesNotExist -ErrorAction Stop } catch { Write-Host "error" }
error
Exceptions can be dealt with individually based on their type:
try {
Some-Command -ErrorAction Stop
} catch [Type1] {
# ...
} catch [Type2] {
# ...
} catch {
# ...
}
- Powershell 5.1 is called "Windows PowerShell" and has the binary
powershell.exe
and a blue background by default. - Powershell 7.x is called "PowerShell" and has the binary
pwsh.exe
and a black background by default. - ISE: Integrated Scripting Environment (host application), outdated; use Visual Studio Code with the PowerShell extension instead
A profile script, which is automatically executed as PowerShell loads, can be
located under $PSHOME/profile.ps1
. Check out Help About_Profile
for
additional locations for a profile file. The currently loaded profile script is
referred to by the variable $PROFILE
.
Add a function called Prompt
to the profile script to modify the prompt:
function Prompt
{
'PS ' + (Get-Location).ToString().Split('/')[-1] +
$(if ($NestedPromptLevel -GE 1) { '>> ' } else { '> ' })
}
Convert data types using -as
:
> 10 / 3
3.33333333333333
> 10 / 3 -as [int]
3
Check if an expression is of a type with -is
:
> 10 / 3 -is [double]
True
> 10 + 3 -is [int]
True
Replace text with regexes and -replace
:
> "e32df0d9" -replace '[a-z]','_'
_32__0_9
Convert between arrays and text using -join
and -split
:
> "a,b,c,d" -split ','
a
b
c
d
> {1,2,3,4,5} -join ','
1,2,3,4,5
Perform wildcard string matching using -like
:
> "liability" -like '*ability*'
True
Check if an element is contained within a collection using -contains
and -in
(with opposite operand orders):
> 3 -in 1..5
True
> 1..5 -contains 3
True
The $PSDefaultParameterValues
variable is a hash map that stores parameter
names (keys) and default values for them (values). The keys follow the structure
[Cmdlet]:[Parameter]
, e.g. Get-ChildItem:Path
:
> $PSDefaultParameterValues.Add('Get-ChildItem:Path', '~/music')
> Get-ChildItem
[lists ~/music]
Wildcards can be used, too. The value can also be a script block.
Compressing and uncompressing archives:
> Compress-Archive -Path foo -DestinationPath foo.zip
> Expand-Archive -Path foo.zip -DestinationPath foo_copy
Measure execution time:
> Measure-Command { foobar.exe }
Wait a while:
> Start-Sleep -Seconds 3
Get the fully qualified type name of an object (e.g. a date):
> (Get-Date).GetType().FullName
Find the full command name of an alias (e.g. cd
):
> Get-Alias | Where-Object -Property Name -Like -Value cd*
Install a YAML module (ConvertFrom-Yaml
and ConvertTo-Yaml
Cmdlets):
> Install-Module -Name Powershell-YAML -Force
Convert a YAML file to JSON:
> Get-Content foo.yaml | ConvertFrom-YAML | ConvertTo-Json -Depth 100
> @{name='Alice', age=37}
Open PowerShell as Admin.
Load the Appx
module:
> Import-Module -Name Appx -UseWindowsPowerShell
List the names of installed Appx packages:
> Get-AppxPackage -AllUsers | Select-Object -Property Name
Switch to the registry (HKEY_CURRENT_USER
):
> Set-Location -Path hkcu:
Show input method entries:
> Get-ChildItem '.\Control Panel\Input Method\'
If EnableHexNumpad
is not yet set to 1
, set it accordingly (use -Type String
to get a REG_SZ
value):
> Set-ItemProperty -Path '.\Control Panel\Input Method\' -PSProperty EnableHexNumpad -Value 1 -Type String
Uninstall the worst offenders:
> Remove-AppxPackage -Name *Xbox* | Remove-AppxPackage
> Remove-AppxPackage -Name *Zune* | Remove-AppxPackage
> Remove-AppxPackage -Name *YourPhone* | Remove-AppxPackage
> Remove-AppxPackage -Name *Skype* | Remove-AppxPackage