learnxinyminutes-docs/powershell.html.markdown
Andrew Ryan Davis 11db56b81c Making some minor fixes
Adjusting some inconsistent names. 
Changing Remove-Array to Format-Array since Remove is not an approved Posh verb.
Adding Kevin Marquette's blog because it's awesome
Adding a simpler array reversal example
2020-08-20 12:30:32 -07:00

21 KiB

category tool contributors filename
tool powershell
Wouter Van Schandevijl
https://github.com/laoujin
Andrew Ryan Davis
https://github.com/AndrewDavis1191
LearnPowershell.ps1

PowerShell is the Windows scripting language and configuration management framework from Microsoft built on the .NET Framework. Windows 7 and up ship with PowerShell.
Nearly all examples below can be a part of a shell script or executed directly in the shell.

A key difference with Bash is that it is mostly objects that you manipulate rather than plain text. After years of evolving, it resembles Python a bit.

Read more here.

Powershell as a Language:


# 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

# 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 # => 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 them to booleans for evaluation
# but their non-cast value is returned
# Don't mix up with bool(ints) and bitwise and/or (&,|)
[bool](0)     # => False
[bool](4)     # => True
[bool](-6)    # => True
0 -and 2     # => 0
-5 -or 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.
[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."

# $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) {
  if ($value) {Write-Output 'True'}
    else {Write-Output 'False'}
}
test ($null) # => False
test (0)   # => False
test ("")  # => False
test []  # => True
test ({})  # => True
test @()  # => False

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

# Powershell uses the "Write-Output" function to print
Write-Output "I'm Powershell. Nice to meet you!"  # => I'm Powershell. 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 immutable array
$defaultArray = "thing","thing2","thing3"
# you are unable to add or remove objects
$defaultArray.Add("thing4") # => Exception "Collection was of a fixed size."
# To have a mutable array, you will need to use the .NET ArrayList class

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

# Add stuff to the end of a list with add (Note: it produces output, so 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 the end with index of count of objects-1 as arrays are indexed starting 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 the length with "Count" (Note: Length method 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, run again and it 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 }
  { 'x' -contains 'x'}    { "FALSE! -contains is for lists!"; 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-Verbose 'Creating new website(s)' }
    PROCESS { Write-Output "name: $siteName, port: $port" }
    END { Write-Verbose '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 us to do things like High-Order Functions

# Group Object is a handy command that does incredible things for us
# It works much like a GROUP BY in SQL would

<#
 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

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

# This function is commonly 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

<#
 This is a silly one
 You may one day be asked to create a func that could take $start and $end
 and reverse anything in an array within the given range
 based on an arbitrary array
 Let's see one way to do that and introduce another data structure
#>

$targetArray = 'a','b','c','d','e','f','g','h','i','j','k','l','m','n'

function Format-Range ($start, $end) {
[System.Collections.ArrayList]$firstSectionArray = @()
[System.Collections.ArrayList]$secondSectionArray = @()
[System.Collections.Stack]$stack = @()
    for ($index = 0; $index -lt $targetArray.Count; $index++) {
        if ($index -lt $start) {
            $firstSectionArray.Add($targetArray[$index]) > $null
        }
        elseif ($index -ge $start -and $index -le $end) {
            $stack.Push($targetArray[$index])
        }
        elseif ($index -gt $end) {
            $secondSectionArray.Add($targetArray[$index]) > $null
        }
    }
    $returnArray = $firstSectionArray + $stack.ToArray() + $secondSectionArray
    Write-Output $returnArray
}

# The previous method works, but it uses extra memory by allocating new arrays
# It's also kind of lengthy
# Let's see how we can do this without allocating a new array
# This is slightly faster as well

function Format-Range ($start, $end) {
  while ($start -lt $end)
  {
      $temp = $targetArray[$start]
      $targetArray[$start] = $targetArray[$end]
      $targetArray[$end] = $temp
      $start++
      $end--
  }
  return $targetArray
}

Powershell as a Tool:

Getting Help:

# Find commands
Get-Command about_* # alias: gcm
Get-Command -Verb Add
Get-Alias ps
Get-Alias -Definition Get-Process

Get-Help ps | less # alias: help
ps | Get-Member # alias: gm

Show-Command Get-EventLog # Display GUI to fill in the parameters

Update-Help # Run as admin

If you are uncertain about your environment:

Get-ExecutionPolicy -List
Set-ExecutionPolicy AllSigned
# Execution policies include:
# - Restricted: Scripts won't run.
# - RemoteSigned: Downloaded scripts run only if signed by a trusted publisher. 
# - AllSigned: Scripts need to be signed by a trusted publisher.
# - Unrestricted: Run all scripts.
help about_Execution_Policies # for more info

# Current PowerShell version:
$PSVersionTable
# Remoting into computers is easy
Enter-PSSession -ComputerName RemoteComputer
# Once remoted in, you can run commands as if you're local
RemoteComputer\PS> Get-Process powershell
<#
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName                                             
-------  ------    -----      -----     ------     --  -- -----------                                             
   1096      44   156324     179068      29.92  11772   1 powershell                                              
    545      25    49512      49852             25348   0 powershell 
#>
RemoteComputer\PS> Exit-PSSession

<#
 Powershell is an incredible tool for Windows management and Automation
 Let's take the following scenario
 You have 10 servers
 You need to check whether a service is running on all of them
 You can RDP and log in, or PSSession to all of them, but why?
 Check out the following
#>

$serverList = @(
    'server1',
    'server2',
    'server3',
    'server4',
    'server5',
    'server6',
    'server7',
    'server8',
    'server9',
    'server10'
)

[scriptblock]$Script = {
    Get-Service -DisplayName 'Task Scheduler'
}

foreach ($server in $serverList) {
    $CmdSplat = @{
        ComputerName  = $Server
        JobName       = 'checkService'
        ScriptBlock   = $Script
        AsJob         = $true
        ErrorAction   = 'SilentlyContinue'
    }
    Invoke-Command @CmdSplat | Out-Null
}

<#
 Here we've invoked jobs across many servers
 We can now Receive-Job and see if they're all running
 Now scale this up 100x as many servers :)
#>

Interesting Projects