DEV Community

jeikabu
jeikabu

Posted on • Originally published at rendered-obsolete.github.io on

PowerShell Crash Course

Let’s be honest, cmd.exe sucks. I have fond memories of MS-DOS 6.0/6.22, but after getting over the learning curve of Linux/Unix it’s tough to go back. When PowerShell 1.0 was announced I was excited; finally a “real” CLI for Windows. But I couldn’t really be asked to learn it and kept installing cygwin or msys.

After discovering PowerShell Core is multi-platform, it’s back on my radar because of a few use cases:

This is more in the vain of cheat-sheet/quick reference/cookbook than tutorial. If you’re not comfortable picking up new (scripting) languages, this may be unhelpful.

Shell Keyboard Shortcuts

Display list of all shortcuts:



Get-PSReadlineKeyHandler


Enter fullscreen mode Exit fullscreen mode

On macOS/Linux it defaults to emacs “edit mode”. If you’ve used emacs (or bash’s emacs mode) you’ll feel right at home. On Windows, the default is Windows but you can:



Set-PSReadLineOption -EditMode Emacs


Enter fullscreen mode Exit fullscreen mode

If you’re new to PowerShell, I highly recommend switching to emacs mode. If for no other reason you’ll also familiarize yourself with Bash- should you ever find yourself at a Linux terminal.

Commands for every day use:



# Movement
Ctrl+a # Beginning of line
Ctrl+e # End of line
Ctrl+f # Forward one character
Ctrl+b # Back one character
Alt+f # Forward one word
Alt+b # Back one word

# Editing
Alt+. # Insert last argument of previous command
Ctrl+d # Delete character
Alt+d # Delete word
Ctrl+u # Delete to beginning of line
Ctrl+k # Delete to end of line

# Command History
Ctrl+p # Previous command
Ctrl+n # Next command
Ctrl+o # Execute command and advance to next
Ctrl+r <text> # Search command history for <text>


Enter fullscreen mode Exit fullscreen mode

Alt+. is one I wish I had learned the first day I installed Linux. Tip: you can press it repeatedly to cycle through the history of last arguments.

Same with Ctrl+o. If you need to redo a sequence of commands: Ctrl+p back to the start, Ctrl+o Ctrl+o… throw in Ctrl+n if you need to skip one, etc.

If you’re on macOS/OSX and using the default terminal Alt is Esc. Or, you can use Option (recommended) via Terminal > Preferences :

Basic Syntax

Literals and variables:



$boolean = $true # or `$false`
$string = "string"
$int = 42
$array = 1, 2, 3
$array2 = @(1, 2, 3)
$array[0] = $null # Remove first item
$hash = @{first = 1
    "second" = 2; third = 3
}
# Add to hashtable
$hash += @{4 = "fourth"}
$hash["fifth"] = 5 # Key has to be quoted here
$hash[4] = $null # Remove value (but not key) from hash

# String with `PATH` environment variable
"$env:PATH ${env:PATH}: safer"
# Multi-line "here string"
@"
"Here-string" with value $env:PATH
"@
# Escape character
"literal `$ or `" within double-quotes"
# Evaluate expression
"Hello $(echo world)"

# Casting locks variable type
[int[]]$ints = "1", "2", "3"
$ints = "string" # Throws exception
# Destructuring
$first, $rest = $ints # first = 1; $rest = 2,3


Enter fullscreen mode Exit fullscreen mode

Control-flow:



$value = 42
if ($value -eq 0) {
    # Code
} elseif ($value -gt 1) {
} else {
}

$value = "value"
# Match against each string/int/variable/expression case
switch ($value)
{
    "x" { echo "matched string" }
    1 { echo "matched int" }
    $var { echo "matched variable" }
    { $_ -gt 42 }{ echo "matched expression" }
    default { }
}
$collection = 1,2,3,4
# Matched against each element of collection. `$_` is current item. `Break` applies to entire collection
switch ($collection)
{
    1 { echo $_ 1 }
    { $_ -gt 1 } { echo "$_ Greater than 1" }
    3 { echo $_ 3; break }
}
# Output is (NB: there's no 4):
#1 1
#2 Greater than 1
#3 Greater than 1
#3 3

foreach ($val in $collection) {
}

while ($value -gt 0) {
    $value--
}


Enter fullscreen mode Exit fullscreen mode
  • Assignment : += -= *= /= ++ -- (e.g. ++$int or $int++ or $int += 1)
  • Equality : -eq -ne -gt -ge -lt -le
  • Matching : -like -notlike (wildcard), -match -notmatch (regex; $matches contains matching strings)
  • Containment : -contains -notcontains -in -notin
  • Type : -is -isnot
  • Logic : -and -or -xor -not or ! (e.g. $a -and $b or -not $a or !$a)
  • Replacement : -replace (replaces a string pattern)
  • Other than the last, all return $true or $false
  • All are case-insensitive. For case-sensitive prefix with c (e.g. -clike)
  • If input is collection, output is a collection of matches

  • Comparison operators

Essentials



# List commands containing "Path"
Get-Command -Name *path*

# Get help for `Get-Command`
Get-Help Get-Command

# List properties/methods of object
Get-Command | Get-Member

cd output/Debug
Set-Location output/Debug

# Current file/module's directory
$PSScriptRoot

ls
dir # also works which is freaky/helpful for migration
Get-ChildItem
# Pattern glob
ls *.jpg
Get-ChildItem *.jpg
# Just files
Get-ChildItem -File
# Just directories
Get-ChildItem -Directory
Get-ChildItem | ForEach-Object { $_.Name }
Get-ChildItem | Where-Object {$_.Length -gt 1024}

md tmp/
New-Item -ItemType Directory -Name tmp/ -Force | Out-Null

# Add to PATH
$env:PATH += ";$(env:USERPROFILE)" # `;` for Windows, `:` for *nix
$env:PATH += [IO.Path]::PathSeparator + $(pwd) # Any platform

# Check environment variable `GITHUB_TOKEN` is set
Test-Path Env:\GITHUB_TOKEN
# Test for file/directory
Test-Path subdir/child -PathType Leaf # `Container` for directory

pushd tmp/
popd
Push-Location tmp/
Pop-Location
cd - # Go back to previous directory

# Write to stdout, redirect stderr to stdout, send stdout to /dev/null
Write-Output "echo" 2>&1 > $null
&{
    Write-Warning "warning"
    Write-Output "stdout"
# Append warnings to tmp.txt, rest to /dev/null
} 3>> ./tmp.txt | Out-Null
# Write to stderr, redirect all, append to file
Write-Warning "oops" *>> ./tmp.txt

# Execute string
$ls = "ls"
& $ls
& $ls -l # with args
# Execute string with args
$ls_l = "ls -l"
Invoke-Expression $ls_l
# Execute file `script.ps1`
& ./script
$file = "./script.ps1"
& $file

# Execute command looking for failure text
$res = Invoke-Expression "& $cmd 2>&1"
if ($LASTEXITCODE -and ($res -match "0x800700C1")) {
    # Do something
}


Enter fullscreen mode Exit fullscreen mode

Error Handling

Powershell has terminating (i.e. exceptions) and non-terminating errors.



# Delete PathToDelete/ folder recursively ignoring all errors
Remove-Item -Force -Recurse -ErrorAction Ignore PathToDelete

# Make terminating error
Write-Error "fail" -ErrorAction Stop
throw "fail"

# Non-terminating errors are terminating
$ErrorActionPreference = "Stop"

# Handle terminating error
try {
    throw "fail"
} catch [System.Management.Automation.RuntimeException] {
    Write-Output "Throw'd: $_"
] catch [Microsoft.PowerShell.Commands.WriteErrorException] {
    Write-Output "Write-Error'd"
} catch {
    # Any error
} finally {
    # Always executes
}

# Handling non-terminating errors
if ($LastExitCode > 0) {
    # Exit code of last program >0, which might mean it failed
}
if ($?) {
    # Last operation succeeded
} else {
    # Last operation failed
}


Enter fullscreen mode Exit fullscreen mode

Jobs



# Start job in background (sleeps for 200 seconds)
$job = Start-Job { param($secs) Start-Sleep $secs } -ArgumentList 200
# Or
$job = Start-Sleep 200 &
# Wait for it with a timeout
Wait-Job $job -Timeout 4

# Jobs run in their own session, use -ArgumentList
$value = "hi"
Start-Job { Write-Output "value=$value" } | Wait-Job | Receive-Job
# Output: value=
Start-Job { Write-Output "value=$args" } -ArgumentList $value | Wait-Job | Receive-Job
# Output: value=hi

# Start a bunch of work in parallel
Get-Job | Remove-Job # Remove existing jobs
$MaxThreads = 2 # Limit concurrency
foreach ($_ in 0..10) {
    # Wait for one of the jobs to finish
    while ($(Get-Job -State Running).count -ge $MaxThreads) {
        Start-Sleep 1
    }
    Start-Job -ScriptBlock { Start-Sleep 2 } # Random work
}
# Wait for them all to finish
while ($(Get-Job -State Running)){
    Start-Sleep 1
}


Enter fullscreen mode Exit fullscreen mode
  • Jobs
  • Be careful if you use relative paths: jobs start from $HOME on macOS/Linux and Documents/ on Windows
  • Need Receive-Job to see stdout/stderr
  • Parallel work snippet from this SO

Parameters and Functions

Command-line arguments to a script are handled as param() placed at the top of file.



function Hello { echo Hi }
# Call the function
Hello
# Output: Hi

# Function with two named params. First with type and default (both optional)
function HelloWithParams {
    param([string]$name = "<unknown>", $greeting)
    echo "hello $name! $greeting"
}
HelloWithParams 1 2
# Output: hello 1! 2
HelloWithParams -greeting 40
# Output: hello <unknown>! 40

# Function with switch and positional parameters
function Greeting {
    param([switch]$flag)
    echo "hello $flag $args"
}
Greeting more stuff
# Output: hello False more stuff
Greeting -flag more stuff
# Output: hello True more stuff
Greeting -flag:$false more stuff
# Output: hello False more stuff


function PositionalParams {
    param(
        [parameter(Position=0)]
        $greeting,
        [string]$name,
        [parameter(Position=1)]
        $tail
        )
    echo "$greeting $name$tail"
}
PositionalParams hi "!"
# Output: hi !
PositionalParams hi -name jake "!"
# Output: hi jake!
PositionalParams hi "!" -name jake
# Output: hi jake!

function FormalHello {
    param(
        # Params default to optional
        [parameter(Mandatory=$true, HelpMessage="Initial greeting")]
        [string]$greeting,
        # If have multiple values have to use @()
        [string[]]$name = @("Sir", "<unknown>")
        )
    echo "$greeting $name $suffix"
}
FormalHello "Greetings"
# Output: Greetings Sir <unknown>
FormalHello -name sir,jake,3rd -greeting welcome
# Output: welcome sir jake 3rd


Enter fullscreen mode Exit fullscreen mode

Misc

Visual Studio Code :

  • Use the extension.
  • On Windows, install PowerShell Core and in Visual Studio Code click the “PowerShell Session Menu”:

From command pallete that appears pick Switch to PowerShell Core 6 (x64).

Jenkins :

  • Inline powershell or call a script:


  powershell '''
      & ./script.ps1
      '''


Enter fullscreen mode Exit fullscreen mode
  • Call script and handle exit code:


  def res = powershell returnStatus: true, script: '''
      & ./script.ps1
      '''
  if (res != 0) {
      currentBuild.result = 'UNSTABLE'
  }


Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
alanmbarr profile image
Alan Barr

The emacs edit mode tip is awesome! I'm looking forward to using more powershell on linux/osx