About Powershell

Topic Summary
Office365AdminWithPowershell Managing Office365 from Powershell For all cases below you need to set the executionpolicy Once for every PC you need to run this and select A for ALL Set Execut...
PowerShell About Powershell * See also https://ss64.com/ps/ * See also these pages: %SEARCH{" pP ower sS hell" type="regex" scope="topic" nonoise="on" header=" Topi...
PowerShellManagingPCsRemotely Managing PCs With Remote PowerShell Powershell remoting (Traditional without SSH) This is perfect for windows domains but irritating and MAYBE unsecure for workg...
PowerShellScripts Usefull powershell scripts function to sum durations found in text and report total minutes function sum_durations_from_text($text) { # read multiline te...

PowerShell as a shell

A sensible configuration for your shell (for a good UX)

Download less because PowerShell's more is a pain to use: https://sourceforge.net/projects/gnuwin32/files/less/

Add a few options to your profile (for the curious read the github link):

if (Test-Path $Profile) {} else {New-Item –Path $Profile –Type File –Force}
echo "# Enable PSReadLine -- https://github.com/PowerShell/PSReadLine/blob/master/README.md" >> $profile
echo "Import-Module PSReadLine" >> $profile
echo "# Same shortcut keys with Linux" >> $profile
echo "Set-PSReadLineOption -EditMode Emacs" >> $profile

Linux geeks can enjoy less, grep and vim and even :grep inside vim by

1) installing git

2) setting up their $Profile like so:

# Enable PSReadLine
# https://github.com/PowerShell/PSReadLine/blob/master/README.md
Import-Module PSReadLine
# Same shortcut keys with Linux
Set-PSReadLineOption -EditMode Emacs

sal less "C:\Program Files\Git\usr\bin\less.exe"
sal grep "C:\Program Files\Git\usr\bin\grep.exe"
sal vim "C:\Program Files\Git\usr\bin\vim.exe"
sal vi "C:\Program Files\Git\usr\bin\vim.exe"

3) Finally, start vim, type :e $MYVIMRC to edit your vim config and add this line set shell=/bin/bash to enjoy :%!grep ...

Shell absolute basics

# executing programs outside the current dir
& 'c:\path\to\program\with spaces\program.exe'

# executing programs in the current dir
.\prog_name_wo_spaces.exe
&
'.\program name with spaces.exe'

help Get-Process # Help about a command
help *process* # List commands containing process in their name
Show-Command Some-Complex-Cmd # graphically prompts you for the command’s parameters.
Get-Process -name Notepad | Stop-Process # cmdlets with the same noun can often pass information among each other

Exploring the output of command and producing useful output from commands

Get-Process | gm # list properties and methods (gm=get-methods)

Get-Process | select * # display all properties and their values
Get-Process | ogv # view output on an Excel like GUI *very* handy for long lists or lists with large texts (ogv=out-GridView)

Get-Process | Format-Table -autoSize # force the shell to try to size each column to hold its contents and no more
Get-Service | Format-Table -autoSize -wrap # -wrap will never truncate text with ...
Get-Process | Format-Table -property ID,Name,Responding -autoSize # specific properties only

Get-Process | Format-List # like Format-Table but vertical (one line per property)
Get-Process | Select * # show all properties (by default some are hiden)
Get-Process | Format-List * # -->>--
Get-Process | Sort -Property VM | Select Name,ID,VM,PM # sort & display specific properties
Get-Process | Sort -Property VM | Select Name,ID,VM,PM -First 10 # only the first 10 objects
Get-Process | Format-List | Out-String | ForEach-Object { $_.Trim() } # after Out-String the output is a long string, we use ForEach-Object to remove empty lines
-OR-
... | Out-String -Stream | sls "word"
-OR- ... | Out-String -Stream | where {$_ -notmatch ': *$|: {}$'}

$FormatEnumerationLimit =-1 # makes format-table to print full property values instead of 16 characters with ...

Enumerating Arrays/Streams of objects

# ENUMERATING ONLY (all these do the same thing)
ForEach ($file in (Get-ChildItem)) {$file.fullname}
Get-ChildItem | ForEach-Object { $_.fullname }
Get-ChildItem | ForEach { $_.fullname }
Get-ChildItem | %{ $_.fullname }
Get-ChildItem | ForEach fullname
# FILTERING (all these do the same thing)
Get-ChildItem | Where-Object { $_.fullname -match '[.]docx$' }
Get-ChildItem | Where { $_.fullname -match '[.]docx$' }
Get-ChildItem | ? { $_.fullname -match '[.]docx$' }
# you can also use these operators -notmatch, -eq, -ne, -lt, -gt, -le, -ge, -like, -notlike
# ALL TOGETHER Enumerate -> filter -> map
Get-ChildItem | Where { $_.fullname -match '[.]docx$' } | ForEach { [PSCustomObject]@{FileName=$_.Name; FileSize=$_.Length} }
# How to split a text in lines
$array_of_lines = ($text -split "`r?`n") # '`n' does not work, and note the trick with `r?

More Advanced

Get-Service | Sort Status | Format-Table -groupBy Status # sorting first matters
Get-Process | Sort -Property VM | Select Name,ID,VM,PM | ConvertTo-HTML > processes.html # or json or csv
Get-Process | Format-Table Name, @{name='VM(MB)';expression={$_.VM / 1MB -as [int]}} -autosize # calculated field VM(MB)
Get-Process | Where { $_.Name -notlike 'powershell*' } | Measure-Object -property VM -sum # SUM of a column(property)

Even more Advanced: Parenthetical commands

# -computername gets a list of computer names from c:\names.txt filtered 
Get-Service -computername (Get-Content c:\names.txt | Where-Object -filter { $_ -notlike '*dc' })
# It's the same as these two commands:
$list = (Get-Content c:\names.txt | Where-Object -filter { $_ -notlike '*dc' })
Get-Service -computername $list

Dealing with Greek (Character encodings)

chcp 1252 # change character encoding to Windows-Greek (ISE is by default set to UTF8)
Get-Content -Raw -Encoding default file.log > temp.txt; sls 'ελληνικά' temp.txt # how to use sls on a file with ANSI(cp1253) encoding

Powershell <--> Linux cheat-sheet

cat log.txt | sls '^[a-z]'                     # grep
cat sourcefile.txt | ?{$_ -notmatch 'NotThis'} # grep -v gc log.txt | select -first 10 # head gc log.txt | select -last 10 # tail gc log.txt | more # or less if you have it installed echo test | %{ $_ -replace 'e', 'o' } # sed ls 2> $null # /dev/null is $null in P.S. gc -TotalCount 10 log.txt # also head gc -Tail 10 log.txt # also tail -- much faster than above option
(cat $file.FullName | Measure-Object -line).lines # wc -l
cat lala | group | sort # cat lala | sort | uniq -c
(Get-Process | Measure-Object).count # almost what wc -l would do -- it counts how many objects Get-Process emmits
(get-date) - (gcim Win32_OperatingSystem).LastBootUpTime # uptime (also see systeminfo | sls "Boot"
Set-PSDebug -Trace 1; script.ps1 # bash -x script.sh

Scripting help

Things to keep in mind

ALWAYS and I mean ALWAYS start your scripts with this line that raises an error if you use an undeclared/unassigned variable:

Set-strictmode -version latest

If you want execution of a script to stop on the first error (which you want if you are not very thoughtful about every line of code you write):

$ErrorActionPreference="Stop"

Note that the above will catch most errors but may miss errors when calling external programs (or DOS commands). See https://stackoverflow.com/a/11450852/1011025

How to debug

Set-PSBreakpoint  "script.ps1" 123 # commands: Step into, step oVer, step Out, Continue, quit
Get-PSBreakpoint| Remove-PSBreakpoint

Set-PSBreakpoint -Variable StackTrace -Mode Write #Break on any error
Get-PSBreakpoint | Remove-PSBreakpoint

Common Gotchas

THIS IS WRONG!          THIS IS CORRECT!
Start-Sleet 60*60 Start-Sleep (60*60)
echo "$_.name" echo "$($_.name)"

PowerShell Reference

# Single line comments start with a number symbol.

<#
  Multi-line comments
  like so
#>


####################################################
## 1. Primitive Datatypes and Operators
####################################################

# Numbers
3 # => 3

# Math
1 + 1   # => 2
8 - 1   # => 7
10 * 2  # => 20
35 / 5  # => 7.0

# Powershell uses banker's rounding,
# meaning [int]1.5 would round to 2 but so would [int]2.5
# Division always returns a float. 
# You must cast result to [int] to round.
[int]5 / [int]3       # => 1.66666666666667
[int]-5 / [int]3      # => -1.66666666666667
5.0 / 3.0   # => 1.66666666666667
-5.0 / 3.0  # => -1.66666666666667
[int]$result = 5 / 3 
$result # => 2

# Modulo operation
7 % 3  # => 1

# Exponentiation requires longform or the built-in [Math] class.
[Math]::Pow(2,3)  # => 8

# Enforce order of operations with parentheses.
1 + 3 * 2  # => 7
(1 + 3) * 2  # => 8

# Boolean values are primitives (Note: the $)
$True  # => True
$False  # => False

# negate with !
!$True   # => False
!$False  # => True

# Boolean Operators
# Note "-and" and "-or" usage
$True -and $False  # => False
$False -or $True   # => True

# True and False are actually 1 and 0 but only support limited arithmetic.
# However, casting the bool to int resolves this.
$True + $True # => 2
$True * 8    # => '[System.Boolean] * [System.Int32]' is undefined
[int]$True * 8 # => 8
$False - 5   # => -5

# Comparison operators look at the numerical value of True and False.
0 -eq $False  # => True
1 -eq $True   # => True
2 -eq $True   # => False
-5 -ne $False # => True

# Using boolean logical operators on ints casts to booleans for evaluation.
# but their non-cast value is returned
# Don't mix up with bool(ints) and bitwise -band/-bor
[bool](0)     # => False
[bool](4)     # => True
[bool](-6)    # => True
0 -band 2     # => 0
-5 -bor 0     # => -5

# Equality is -eq (equals)
1 -eq 1  # => True
2 -eq 1  # => False

# Inequality is -ne (notequals)
1 -ne 1  # => False
2 -ne 1  # => True

# More comparisons
1 -lt 10  # => True
1 -gt 10  # => False
2 -le 2  # => True
2 -ge 2  # => True

# Seeing whether a value is in a range
1 -lt 2 -and 2 -lt 3  # => True
2 -lt 3 -and 3 -lt 2  # => False

# (-is vs. -eq) -is checks if two objects are the same type.
# -eq checks if the objects have the same values.
# Note: we called '[Math]' from .NET previously without the preceeding
# namespaces. We can do the same with [Collections.ArrayList] if preferred.
[System.Collections.ArrayList]$a = @()  # Point a at a new list
$a = (1,2,3,4)
$b = $a                                 # => Point b at what a is pointing to
$b -is $a.GetType()                     # => True, a and b equal same type
$b -eq $a                               # => True, a and b values are equal
[System.Collections.Hashtable]$b = @{}  # => Point a at a new hash table
$b = @{'one' = 1 
       'two' = 2}
$b -is $a.GetType()                     # => False, a and b types not equal

# Strings are created with " or ' but " is required for string interpolation
"This is a string."
'This is also a string.'

# Strings can be added too! But try not to do this.
"Hello " + "world!"  # => "Hello world!"

# A string can be treated like a list of characters
"Hello world!"[0]  # => 'H'

# You can find the length of a string
("This is a string").Length  # => 16

# You can also format using f-strings or formatted string literals.
$name = "Steve"
$age = 22
"He said his name is $name." 
# => "He said his name is Steve"
"{0} said he is {1} years old." -f $name, $age 
# => "Steve said he is 22 years old"
"$name's name is $($name.Length) characters long." 
# => "Steve's name is 5 characters long."

# Escape Characters in Powershell
# Many languages use the '\', but Windows uses this character for 
# file paths. Powershell thus uses '`' to escape characters
# Take caution when working with files, as '`' is a
# valid character in NTFS filenames.
"Showing`nEscape Chars" # => new line between Showing and Escape
"Making`tTables`tWith`tTabs" # => Format things with tabs

# Negate pound sign to prevent comment
# Note that the function of '#' is removed, but '#' is still present
`#Get-Process # => Fail: not a recognized cmdlet

# $null is not an object
$null  # => None

# $null, 0, and empty strings and arrays all evaluate to False.
# All other values are True
function Test-Value ($value) {
  if ($value) {
    Write-Output 'True'
  }
  else {
    Write-Output 'False'
  }
}

Test-Value ($null) # => False
Test-Value (0)     # => False
Test-Value ("")    # => False
Test-Value []      # => True 
# *[] calls .NET class; creates '[]' string when passed to function
Test-Value ({})    # => True
Test-Value @()     # => False


####################################################
## 2. Variables and Collections
####################################################

# Powershell uses the "Write-Output" function to print
Write-Output "I'm Posh. Nice to meet you!"  # => I'm Posh. Nice to meet you!

# Simple way to get input data from console
$userInput = Read-Host "Enter some data: " # Returns the data as a string

# There are no declarations, only assignments.
# Convention is to use camelCase or PascalCase, whatever your team uses.
$someVariable = 5
$someVariable  # => 5

# Accessing a previously unassigned variable does not throw exception.
# The value is $null by default

# Ternary Operators exist in Powershell 7 and up
0 ? 'yes' : 'no'  # => no


# The default array object in Powershell is an fixed length array.
$defaultArray = "thing","thing2","thing3"
# you can add objects with '+=', but cannot remove objects.
$defaultArray.Add("thing4") # => Exception "Collection was of a fixed size."
# To have a more workable array, you'll want the .NET [ArrayList] class
# It is also worth noting that ArrayLists are significantly faster

# ArrayLists store sequences
[System.Collections.ArrayList]$array = @()
# You can start with a prefilled ArrayList
[System.Collections.ArrayList]$otherArray = @(4, 5, 6)

# Add to the end of a list with 'Add' (Note: produces output, append to $null)
$array.Add(1) > $null    # $array is now [1]
$array.Add(2) > $null    # $array is now [1, 2]
$array.Add(4) > $null    # $array is now [1, 2, 4]
$array.Add(3) > $null    # $array is now [1, 2, 4, 3]
# Remove from end with index of count of objects-1; array index starts at 0
$array.RemoveAt($array.Count-1) # => 3 and array is now [1, 2, 4]
# Let's put it back
$array.Add(3) > $null   # array is now [1, 2, 4, 3] again.

# Access a list like you would any array
$array[0]   # => 1
# Look at the last element
$array[-1]  # => 3

# Looking out of bounds returns nothing
$array[4]  # blank line returned

# You can look at ranges with slice syntax.
# The start index is included, the end index is not
# (It's a closed/open range for you mathy types.)
$array[1..3]   # Return array from index 1 to 3 => [2, 4]
$array[2..-1]    # Return array starting from index 2 => [4, 3]
$array[0..3]    # Return array from beginning until index 3  => [1, 2, 4]
$array[0..2]   # Return array selecting every second entry => [1, 4]
$array.Reverse()  # mutates array to reverse order => [3, 4, 2, 1]
# Use any combination of these to make advanced slices

# Remove arbitrary elements from a array with "del"
$array.Remove($array[2])  # $array is now [1, 2, 3]

# Insert an element at a specific index
$array.Insert(1, 2)  # $array is now [1, 2, 3] again

# Get the index of the first item found matching the argument
$array.IndexOf(2)  # => 1
$array.IndexOf(6)  # Returns -1 as "outside array" 

# You can add arrays
# Note: values for $array and for $otherArray are not modified.
$array + $otherArray  # => [1, 2, 3, 4, 5, 6]

# Concatenate arrays with "AddRange()"
$array.AddRange($otherArray)  # Now $array is [1, 2, 3, 4, 5, 6]

# Check for existence in a array with "in"
1 -in $array  # => True

# Examine length with "Count" (Note: "Length" on arrayList = each items length)
$array.Count  # => 6


# Tuples are like arrays but are immutable.
# To use Tuples in powershell, you must use the .NET tuple class.
$tuple = [System.Tuple]::Create(1, 2, 3)
$tuple.Item(0)      # => 1
$tuple.Item(0) = 3  # Raises a TypeError

# You can do some of the array methods on tuples, but they are limited.
$tuple.Length       # => 3
$tuple + (4, 5, 6)  # => Exception
$tuple[0..2]        # => $null
2 -in $tuple        # => False


# Hashtables store mappings from keys to values, similar to Dictionaries.
$emptyHash = @{}
# Here is a prefilled dictionary
$filledHash = @{"one"= 1 
                "two"= 2 
                "three"= 3}

# Look up values with []
$filledHash["one"]  # => 1

# Get all keys as an iterable with ".Keys".
# items maintain the order at which they are inserted into the dictionary.
$filledHash.Keys  # => ["one", "two", "three"]

# Get all values as an iterable with ".Values".
$filledHash.Values  # => [1, 2, 3]

# Check for existence of keys or values in a hash with "-in"
"one" -in $filledHash.Keys  # => True
1 -in $filledHash.Values    # => False

# Looking up a non-existing key returns $null
$filledHash["four"]  # $null

# Adding to a dictionary
$filledHash.Add("five",5)  # $filledHash["five"] is set to 5
$filledHash.Add("five",6)  # exception "Item with key "five" has already been added"
$filledHash["four"] = 4 # $filledHash["four"] is set to 4, running again does nothing

# Remove keys from a dictionary with del
$filledHash.Remove("one") # Removes the key "one" from filled dict


####################################################
## 3. Control Flow and Iterables
####################################################

# Let's just make a variable
$someVar = 5

# Here is an if statement.
# This prints "$someVar is smaller than 10"
if ($someVar -gt 10) {
    Write-Output "$someVar is bigger than 10."
}
elseif ($someVar -lt 10) {    # This elseif clause is optional.
    Write-Output "$someVar is smaller than 10."
}
else {                        # This is optional too.
    Write-Output "$someVar is indeed 10."
}


<#
Foreach loops iterate over arrays
prints:
    dog is a mammal
    cat is a mammal
    mouse is a mammal
#>
foreach ($animal in ("dog", "cat", "mouse")) {
    # You can use -f to interpolate formatted strings
    "{0} is a mammal" -f $animal
}

<#
For loops iterate over arrays and you can specify indices
prints:
   0 a
   1 b
   2 c
   3 d
   4 e
   5 f
   6 g
   7 h
#>
$letters = ('a','b','c','d','e','f','g','h')
for($i=0; $i -le $letters.Count-1; $i++){
    Write-Host $i, $letters[$i]
}

<#
While loops go until a condition is no longer met.
prints:
    0
    1
    2
    3
#>
$x = 0
while ($x -lt 4) {
    Write-Output $x
    $x += 1  # Shorthand for x = x + 1
}

# Switch statements are more powerful compared to most languages
$val = "20"
switch($val) {
  { $_ -eq 42 }           { "The answer equals 42"; break }
  '20'                    { "Exactly 20"; break }
  { $_ -like 's*' }       { "Case insensitive"; break }
  { $_ -clike 's*'}       { "clike, ceq, cne for case sensitive"; break }
  { $_ -notmatch '^.*$'}  { "Regex matching. cnotmatch, cnotlike, ..."; break }
  default                 { "Others" }
}

# Handle exceptions with a try/catch block
try {
    # Use "throw" to raise an error
    throw "This is an error"
}
catch {
    Write-Output $Error.ExceptionMessage
}
finally {
    Write-Output "We can clean up resources here"
}


# Writing to a file
$contents = @{"aa"= 12 
             "bb"= 21}
$contents | Export-CSV "$env:HOMEDRIVE\file.csv" # writes to a file

$contents = "test string here"
$contents | Out-File "$env:HOMEDRIVE\file.txt" # writes to another file

# Read file contents and convert to json
Get-Content "$env:HOMEDRIVE\file.csv" | ConvertTo-Json


####################################################
## 4. Functions
####################################################

# Use "function" to create new functions
# Keep the Verb-Noun naming convention for functions
function Add-Numbers {
 $args[0] + $args[1]
}

Add-Numbers 1 2 # => 3

# Calling functions with parameters
function Add-ParamNumbers {
 param( [int]$firstNumber, [int]$secondNumber )
 $firstNumber + $secondNumber
}

Add-ParamNumbers -FirstNumber 1 -SecondNumber 2 # => 3 

# Functions with named parameters, parameter attributes, parsable documentation
<#
.SYNOPSIS
Setup a new website
.DESCRIPTION
Creates everything your new website needs for much win
.PARAMETER siteName
The name for the new website
.EXAMPLE
New-Website -Name FancySite -Po 5000
New-Website SiteWithDefaultPort
New-Website siteName 2000 # ERROR! Port argument could not be validated
('name1','name2') | New-Website -Verbose
#>
function New-Website() {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [Alias('name')]
        [string]$siteName,
        [ValidateSet(3000,5000,8000)]
        [int]$port = 3000
    )
    BEGIN { Write-Output 'Creating new website(s)' }
    PROCESS { Write-Output "name: $siteName, port: $port" }
    END { Write-Output 'Website(s) created' }
}


####################################################
## 5. Modules
####################################################

# You can import modules and install modules
# The Install-Module is similar to pip or npm, pulls from Powershell Gallery
Install-Module dbaTools
Import-Module dbaTools

$query = "SELECT * FROM dbo.sometable"
$queryParams = @{
    SqlInstance = 'testInstance'
    Database    = 'testDatabase'
    Query       = $query
}
Invoke-DbaQuery @queryParams

# You can get specific functions from a module
Import-Module -Function Invoke-DbaQuery


# Powershell modules are just ordinary Posh files. You
# can write your own, and import them. The name of the
# module is the same as the name of the file.

# You can find out which functions and attributes
# are defined in a module.
Get-Command -module dbaTools
Get-Help dbaTools -Full


####################################################
## 6. Classes
####################################################

# We use the "class" statement to create a class
class Instrument {
    [string]$Type
    [string]$Family
}

$instrument = [Instrument]::new()
$instrument.Type = "String Instrument"
$instrument.Family = "Plucked String"

$instrument

<# Output:
Type              Family        
----              ------        
String Instrument Plucked String
#>


####################################################
## 6.1 Inheritance
####################################################

# Inheritance allows new child classes to be defined that inherit 
# methods and variables from their parent class.

class Guitar : Instrument
{
    [string]$Brand
    [string]$SubType
    [string]$ModelType
    [string]$ModelNumber
}

$myGuitar = [Guitar]::new()
$myGuitar.Brand       = "Taylor"
$myGuitar.SubType     = "Acoustic"
$myGuitar.ModelType   = "Presentation"
$myGuitar.ModelNumber = "PS14ce Blackwood"

$myGuitar.GetType()

<#
IsPublic IsSerial Name                                     BaseType                                               
-------- -------- ----                                     --------                                               
True     False    Guitar                                   Instrument  
#>


####################################################
## 7. Advanced
####################################################

# The powershell pipeline allows things like High-Order Functions.

# Group-Object is a handy cmdlet that does incredible things.
# It works much like a GROUP BY in SQL.

<#
 The following will get all the running processes,
 group them by Name,
 and tell us how many instances of each process we have running.
 Tip: Chrome and svcHost are usually big numbers in this regard.
#>
Get-Process | Foreach-Object ProcessName | Group-Object

# Useful pipeline examples are iteration and filtering.
1..10 | ForEach-Object { "Loop number $PSITEM" }
1..10 | Where-Object { $PSITEM -gt 5 } | ConvertTo-Json

# A notable pitfall of the pipeline is it's performance when
# compared with other options.
# Additionally, raw bytes are not passed through the pipeline,
# so passing an image causes some issues.
# See more on that in the link at the bottom.

<#
 Asynchronous functions exist in the form of jobs.
 Typically a procedural language,
 Powershell can operate non-blocking functions when invoked as Jobs.
#>

# This function is known to be non-optimized, and therefore slow.
$installedApps = Get-CimInstance -ClassName Win32_Product

# If we had a script, it would hang at this func for a period of time.
$scriptBlock = {Get-CimInstance -ClassName Win32_Product}
Start-Job -ScriptBlock $scriptBlock

# This will start a background job that runs the command.
# You can then obtain the status of jobs and their returned results.
$allJobs = Get-Job
$jobResponse = Get-Job | Receive-Job


# Math is built in to powershell and has many functions.
$r=2
$pi=[math]::pi
$r2=[math]::pow( $r, 2 )
$area = $pi*$r2
$area

# To see all possibilities, check the members.
[System.Math] | Get-Member -Static -MemberType All

Comments, Escape Characters

#Comment Comment
<# A comment spanning
more than one
lines #>
$x = "Bruce `"The Boss`" Springsteen" # to include " inside " we use `
`t Tab
`n New line
` at end of line means the command continues to the next line

Flow Control

If(){} Elseif(){ } Else{ }
while(){}
For($i=0; $i -lt 10; $i++){}
Foreach($file in dir C:\){$file.name}
1..10 | foreach{$_}

Operators

-and -or -not (you can use ! instead of -not)
-eq -ne Equal, not equal
-match -notmatch Regular expression match
-like -notlike Wildcard matching
-contains,-notcontains Check if value is in array
-in -notin Reverse of contains,notcontains.
-gt -ge Greater than, greater than or equal
-lt -le Less than, less than or equal
$string -replace 'this','that' --OR-- $string -replace '(.*), (.*)','$2,$1'
You can use + -= *= /= %= ++ --=

Variables

$var = "string" Assign variable
$a,$b = 0 or $a,$b = 'a','b' Assign multiple variables
$a,$b = $b,$a Flip variables
$var=[int]5 Strongly typed variable

[string]::IsNullOrEmpty($Service.Description) # Check if variable is null or empty
[string]::IsNullOrWhiteSpace($var) # Check if variable is null or contains whitespace only
# test if a variable exists
Test-Path variable:global:var1
Test-Path variable:script:var1
Test-Path variable:var1

Arrays

@(Get-Process) Forces the result to an array using the array subexpression operator
$arr = "a", "b", "c" Array of strings
$arr.length # length of array
$arr = 1,2,3 # Array of integers
$arr = @() #Empty array
$arr = @(2) # Array of one element
$arr = $arr + @("new") # append one element to array
$arr = 1,(2,3),4 Array within array
$arr[0] First array element
$arr[-1] last array element
$arr[0..2] first 3 elements
$arr[-3..-1] last three elements
$arr[1,4+6..9] Displays the elements at index positions 1,4, and 6 through 9
$arr=1..10
$arr[($arr.length-1)..0] Reverses an array
$arr[1] += 200 Adds to an existing value of the second array item (increases the value of the element)
$z = $arr + $b Combines two arrays into a single array

Associative Arrays (Hash tables)

$hash = @{}   # Creates empty hash table
$hash = @{foo=1; bar='value2'}   # Creates and initialize a hash table

[ordered]@{a=1; b=2; c=3} # Creates an ordered dictionary
$hash.key1 = 1; $hash["key1"] = 1 # Assigns 1 to key key1
$hash.key1 ; $hash['key1'] # Returns value of key1
$hash.GetEnumerator() | sort Key # Sorts a hash table by the Key property

Create Custome Objects on the fly

[PSCustomObject]@{x=1; y=2}            # converts dict to PSCustomObject
$obj=@{x=1; y=2}; [PSCustomObject]@obj # -->>--
# example Get-ChildItem | %{ [PSCustomObject]@{FileName=$_.Name; FileSize=$_.Length} }

Object Properties

(Get-Date).Date Date property of object
$a = Get-Date
$a | Get-Member –MemberType Property
$a.Date
$a.TimeOfDay.Hours
$a | Get-Member -MemberType Property –Static
Static properties can be referenced with the "::" operator.
[DateTime]::Now

Methods

Methods can be called on objects.
$a = "This is a string"
$a | Get-Member –MemberType Method
$a.ToUpper(), $a.ToLower() # convert to upper case, lower case
$a.Substring(0,3)
$a | Get-Member -MemberType Method -Static
Static methods are callable with the "::" operator.
[DateTime]::IsLeapYear(2012)

Strings

"This is a string, this $variable is expanded as is $($var.property[4]+2) and ${variable}"

‘This is a string, this $variable is not expanded"

@"
This is a here-string which can contain anything including carriage returns and quotes. Expressions are evaluated: $(2+2*5). Note that the end marker of the here-string must be at the beginning of a line!
"@

@'
Here-strings with single quotes do not evaluate expressions:
$(2+2*5)
'@

Functions

PS C:\> function test1($x,$y) {echo "$x $y"}
PS C:\> function test2([int]$x, [int]$y) {echo "$x $y"}
PS C:\> test1 1, 2
1 2
PS C:\> test2 -x 1 -y 2
1 2
PS C:\> $v=(test2 -x 1 -y 2 )
PS C:\> $v
1 2
PS C:\> $v=(test1 1 2 )
PS C:\> $v
1 2

Convert if to a function (inline if/iif)

Enclose the if statement in (&{ ... }) like so:

echo "Condition is " + (&{If($Condition) {"True"} Else {"False"}})

Variables

Format: $[scope:]name or ${anyname} or ${any path}
$path = "C:\Windows\System32"
Get-ChildItem ${env:ProgramFiles(x86)} $processes = Get-Process
$global:a 1 # visible everywhere
$local:a = 1 # defined in this scope and visible to children
$private:a = 1 # same as local but invisible to child scopes
$script:a = 1 # visible to everything is this script # Using scope indicates a local variable in remote commands and with Start-Job $localVar = Read-Host "Directory, please" Invoke-Command -ComputerName localhost -ScriptBlock {
dir $using:localVar } Start-Job { dir $using:localVar -Recurse}
$env:Path +
";D:\Scripts"
Get-Command -Noun Variable # the Variable Cmdlets Get-ChildItem variable: # listing all variables using the variable drive
# strongly-typed variable (can contain only integers) [int]$number=8
# attributes can be used on variables
[ValidateRange(1,10)][int]$number = 1
$number = 11 #returns an error
# flip variables
$a=1;$b=2
$a,$b = $b,$a
# multi assignment $a,$b,$c = 0
$a,$b,$c = 'a','b','c'
$a,$b,$c = 'a b c'.split()
# create read only variable (can be overwritten with -Force)
Set-Variable -Name ReadOnlyVar -Value 3 -Option ReadOnly
# create Constant variable (cannot be overwritten)
Set-Variable -Name Pi -Value 3.14 -Option Constant
PowerShellAutomatic Variables (not exhaustive)
$$ Last token of the previous command line
$? Boolean status of last command
$^ First token of the previous command line
$_, $PSItem Current pipeline object
$Args Arguments to a script or function
$Error Array of errors from previous commands (use $error.clear() to clear it)
$ForEach Reference to the enumerator in a foreach loop
$Home The user’s home directory
$Host Reference to the application hosting the POWERSHELL language
$Input Enumerator of objects piped to a script
$LastExitCode Exit code of last program or script
$Matches Exit code of last program or script
$MyInvocation An object with information about the current command
$PSHome The installation location of Windows PowerShell
$profile The standard profile (may not be present)
$Switch Enumerator in a switch statement
$True Boolean value for TRUE
$False Boolean value for FALSE
$PSCulture Current culture
$PSUICulture Current UI culture
$PsVersionTable Details about the version of Windows PowerShell
$Pwd The full path of the current directory

Preference Variables

$ConfirmPreference Determines whether PowerShellautomatically prompts you for confirmation before running a cmdlet or function
$DebugPreference Determines how PowerShellresponds to debugging
$ErrorActionPreference Determines how PowerShellresponds to a nonterminating error
$ErrorView Determines the display format of error messages
$FormatEnumerationLimitDetermines how many enumerated items are included in a display
$MaximumHistoryCount Determines how many commands are saved in the command history for the current session
$OFS Output Field Separator. Specifies the character that separates the elements of an array when the array is converted to a string. The default value is: Space.
$OutputEncoding Determines the character encoding method that PowerShelluses when it sends text to other applications
$PSDefaultParameterValues Specifies default values for the parameters of cmdlets and advanced functions
$PSEmailServer Specifies the default e-mail server that is used to send e-mail messages
$PSModuleAutoLoadingPreference Enables and disables
automatic importing of modules in the session. "All" is the default.
$PSSessionApplicationName Specifies the default application name for a remote command that uses WS-Management technology
$PSSessionConfigurationName Specifies the default session configuration that is used for PSSessions created in the current session
$PSSessionOption Establishes the default values for advanced user options in a remote session
$VerbosePreference Determines how PowerShellresponds to verbose messages generated by a script, cmdlet or provider. Example:

$VerbosePreference = "Continue"; Write-Verbose -Message "Verbose message test." ; $VerbosePreference = "SilentlyContinue"; Write-Verbose -Message "Verbose message test."


$WarningPreference Determines how PowerShellresponds to warning messages generated by a script, cmdlet or provider
$WhatIfPreference Determines whether WhatIf is automatically enabled for every command that supports it

Other Tips

# access environment variable windir (same as %WINDIR% in DOS and $PATH in linux)
echo $env:USERPROFILE $env:USERNAME $env:USERDOMAIN $env:COMPUTERNAME $Env:WINDIR $temp_file = "$Env:TEMP\$(Get-Random).tmp" # a temp file in appdata\local\temp
Invoke-WebRequest http://www.foo.com/test.zip -OutFile test1.zip # download file
Expand-Archive .\test1.zip # extract files from zip file
# converting between different types
[char]65 # --> "A" converts int to character
[int][char]"A" # --> 65 converts string to char and then the char to int
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm") # convert date to formatted string
[DateTime]"2020-7-16" # this will work [datetime]::parseexact('01-Jul-16', 'dd-MMM-yy', $null) # but prefere this to avoid surprises (e.g. 7/5/2020 is 7 a day or a month?)

Useful commands in scripts

Write-Host "foo"     # writes to console 
Write-output "foo"   # writes to std-out (which may be the console and maybe not)
start-sleep -s 10    # sleep for 10 seconds

About branches/loops/functions
if (x -eq "foo") {...} else {...} # or -ne -gt -lt -match -notmatch
ForEach ($item in ($command)) {$item ... [break] ...}   # loop over the output objects of command. You can use break if you want
command | ForEach-Object {$_ ...} # loop over the output objects of command, $_ is the object CAUTION: you can't and shouldn't use break in it
For ($i=1; $i -le 20; $i++) { ... } # loop 20 times (from $i=1 to $i=20)
Do { ... } While ($i -gt 10)
Do { ... } Until ($i -eq 10)
function some_name { ... }

Useful tips
$out = (ls c:\ | select-string "foo")
echo "$out"               # This gives one line
echo $out                 # BUT this gives MULTIPLE lines
echo "$x$y" # almost like string concatanation but doesn't preserve any new lines in $x,$y
echo ($x + $y) # True string concatanation (preserves new lines in $x,$y)
$out = %{ $out -replace 'foo', 'bar' }"# printing
Get-Date -format yyyy-MM-dd` HH:mm:ss # a nicely formated timestamp
Get-Date -format yyyy-MM-dd_HH.mm.ss # a timestamp formated for filenames
# string formating
$msg = "{0,10} {1}" -f $var0, $var1 # $var0 is left-padded at 10chars
# get a list of all files like Auto*.txt that have been written during the last 8 days
$list = Get-ChildItem -Filter "Auto*.txt" -Force -File -Path 'G:\Soft1 Backup' -ErrorAction SilentlyContinue | ? { $_.LastWriteTime -gt (Get-Date).AddDays(-8) }
# to delete them
$list | del
# to keep only their Full Name:
$list | Select-Object FullName
# to execute commands for each one of the files
foreach ($file in $list) {
...
}
if ($list.length -lt 7) {echo "WARNING! less than 7 backup reports"}

To capture the output of a block of commands
Start-Transcript -Path 'c:\output.txt'
...commands...
Stop-Transcript

Error handling (try catch)

try {
command -ErrorAction Stop
} catch {
echo "command raised error", $error[0]
}

# for non terminating error (MOST ERRORS) you use 2>$null to silence them
# and then iterate over the $error items and to do what you want.
# e.g. here we just print a one line terse error
$error.clear()
remove-item "c:\test1" 2>$null
$error | %{ write-host -foregroundColor red $_.ToString()}

How to catch and handle SPECIFIC errors
# first reproduce the error and print its FullName
try { Get-ChildItem C:\ThisFileDoesNotExist -ErrorAction Stop} catch { $Error[0].Exception.GetType().FullName }
System.Management.Automation.ItemNotFoundExceptio
# and now you can catch this specific error
try {
Get-ChildItem C:\ThisFileDoesNotExist -ErrorAction Stop
} catch [System.Management.Automation.ItemNotFoundException] {
Write-host "Item not found"
}

Tips

How to suppress progress bar for various commands

$global:progressPreference = 'silentlyContinue'
(tnc $server_ip).PingSucceeded
$global:progressPreference = 'Continue'

How to run a script bypassing the Execution Policy of the user

powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File <script_name>

How to start a PS1 script from scheduled tasks

If you are running the scipt with another account and not the logged in user or don't care about a window flashing for a while:

04df0a3882bd3f1943c2fde9a6977217.png

The optional arguments are these:

-ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File __<script_name>__

If you don't want to have a powershell window flashing for a while

1fc711c139db259bf458aae60e756aa7.png

The optional arguments are these (change the path and the script filename):
vbscript:Execute("CreateObject(""Wscript.Shell"").Run ""powershell -NoLogo -Command """"& 'c:\some dir\script.ps1'"""""", 0 : window.close")

Creating a scheduled task from powershell

In this example we are creating a daily task that runs a powershell script without any window popping up

$script = "C:\Users\user\bin\cleanup-screenshots.ps1"
$time = '5:15 AM'
$user = 'user'
$executable = "mshta" 
$arguments = @'
vbscript:Execute("CreateObject(""Wscript.Shell"").Run ""powershell -NoProfile -ExecutionPolicy bypass -NoLogo -Command """"& '{script}'"""""", 0 : window.close")
'@ 
$arguments = $arguments -replace "{script}", $script
$actions = (New-ScheduledTaskAction -Execute $executable -Argument $arguments)
$trigger = New-ScheduledTaskTrigger -Daily -At $time
$principal = New-ScheduledTaskPrincipal -UserId $user 
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable 
$task = New-ScheduledTask -Action $actions -Principal $principal -Trigger $trigger -Settings $settings
Register-ScheduledTask 'cleanup-screenshots' -InputObject $task

If you want the powershell windows to be visible use this alternative

$script = "C:\Users\user\bin\cleanup-screenshots.ps1"
$time = '5:15 AM'
$user = 'user'
$executable = "C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe"
$arguments = "-NoProfile -ExecutionPolicy bypass -File `"$script`""
$actions = (New-ScheduledTaskAction -Execute $executable -Argument $arguments)
$trigger = New-ScheduledTaskTrigger -Daily -At $time
$principal = New-ScheduledTaskPrincipal -UserId $user
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable 
$task = New-ScheduledTask -Action $actions -Principal $principal -Trigger $trigger -Settings $settings
Register-ScheduledTask 'cleanup-screenshots' -InputObject $task

If you want to run something besides powershell scripts just change $exectuable and $arguments above

Finding files

A complex find operation

Everything after -Path is optional (it's also optional to use $FindDate= which helps to filter by last write time).

You can omit -File if you want to look both for files and directories (or replace it with -Directory)

The where-object expression can be as complex as necessary to narrow the files returned.

Get-ChildItem -File -Recurse -ErrorAction SilentlyContinue `
    -Path C:\,D:\test `
    -Include *.doc,*.docx `
    -Exclude test* `
    | Where-Object { $_.LastWriteTime -ge (Get-Date).AddDays(-8)}

# ALTERNATIVE WAYS TO DEFINE THE LastWriteTime:
# Get-Date -Year 2019 -Month 06 -Day 24

Find the most recent file in dir (recursively)

gci -recurse C:\some\dir | sort LastWriteTime | select -last 1

How to check if the session is elavated (started from Admin PowerShell)

function StopIfNotAnAdmin(){
    # Am I running with admin rights?
    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    $admin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (! $admin) {
        Write-Host "Please run me with administrator priviledges" -ForegroundColor Yellow
        exit
    }
}

How to execute powershell code in the context of the currently logged-in user from within a PS process that runs in the context of the SYSTEM user

# -------------------------------------
# An example that shows how to execute powershell code 
# in the context of the currently logged-in user 
# from within a process that runs in the context of the SYSTEM user 
# (like code that is downloaded and executed by emar2_dl_task)
# -------------------------------------

function process_running_as_user($param1, $param2) {
   $ScriptBlock={
      param($param1, $param2)
      write-verbose "running as user with params $param1, param2=$param2"
      #vvvvvvvvvvvvvvvvvv
      # YOUR CODE HERE!
      #^^^^^^^^^^^^^^^^^^
   }

   # find the logged-in user 
   # The following command gives you the user but burried inside irelevant characters:
   # Get-WmiObject Win32_LoggedOnUser | Select Antecedent -Unique | %{$_.Antecedent.ToString()} 
   $loggedin_user = (Get-WmiObject Win32_LoggedOnUser | Select Antecedent -Unique `
      | %{"{0}\{1}" -f $_.Antecedent.ToString().Split('"')[1],$_.Antecedent.ToString().Split('"')[3]} `
      | ?{ $_ -notlike '*\SYSTEM' -and $_ -notlike '*\LOCAL SERVICE' -and $_ -notlike '*\NETWORK SERVICE' `
      -and $_ -notmatch '.*\\DWM-[0-9]' -and $_ -notmatch '.*\\UMFD-[0-9]'})

   write-verbose  "calling process_running_as_user with user $loggedin_user"
   Invoke-CommandAs -ScriptBlock $ScriptBlock -AsInteractive $loggedin_user -args $param1, $param2
}

How to download and unzip an archive

New-TemporaryFile | %{rm $_; mkdir $_ >$null; cd $_} # create a temporary directory and cd into it (temp dir)
wget -Uri "http://foo.com/bar.zip" -OutFile t.zip
Expand-Archive t.zip

How to configure PS to execute commands on startup (like .bashrc / autoexec.bat)

# create a text file for powershell commands if it doesn't exist
if (Test-Path $Profile) {} else {New-Item –Path $Profile –Type File –Force; echo "# startup commands">> $profile}

# edit your $profile 
notepad $profile

Resources/Help

Troubleshooting

If installing modules fails try this command before:

[Net.ServicePointManager]::SecurityProtocol = "tls12"

Huge list of PowerShell commands

Experts from Learning PowerShell

490b183fa82edae9163a5a6d0eaea263.png

117be7276899c90d5f786eb7e6540931.png

ad5acc2daaa072dacad16232ceb1aa9e.png

b1d197bee3446d5de6e8227d03ea36bb.png

cbf485cdd1b5a4f241c94a7232f0acf3.png

1e09c4099efe7e037a81723c2375569f.png

24eab691a57aa04494b41f54732103c3.png

48b9a6ed794320f5f41f9e7fdeec1e0c.png

c69af9c25344e66ba17f8db76311b248.png

9c07a5e41bd4e6562bbe1944cd272c6e.png

c0aefa50c1ae534e1217234a20d82369.png

a89312a132409e60909dc5c8f73922f2.png

12666edfbf88d22e3e4ce94dcd561895.png

3f595e6940bbe574d229a2bbf53d6343.png

311ff630fa54d5f7b1648777d088d69f.png

c9598b8eba447afe728b91417d4ebe18.png

dbcaa972066bafe42b97433bac2186ae.png

6e50d2c6cadf7ca94b91bb476147252d.png

3252c04f2cde9779c9865bc7e576125d.png

4392a62125453630239c3c9d715a3ca0.png

a0e0a8f9901d627f48afca49895731f0.png

9c98f971ade0ffa0d84171bf1c5adaae.png

416da7ef84f243ee23e433a9030fe57e.png
Topic revision: r66 - 04 Feb 2025, KonstatntinosVanRiet
Copyright © enLogic