Skip to content

PowerShell

Functions are declared with the function keyword and the body in braces.

function Hello-World
{
    Write-Output "Hello, World!"
}

Positional parameters can be referenced using the $args array, which contains all arguments passed to the function on invocation. PowerShell appears to automatically handle arrays passed in as the first argument and interpolates spaces between each item this way.

function Hello-World
{
    Write-Output "Hello, $($args[0])!"
}

In order to set a default value, apparently a named parameter has to be used. There are two ways to do this, with or without the param keyword. These parameters are still usable as positional parameters, determined by the order in which they are defined (?), but note that the case of the identifier is preserved to define the option (i.e. "$name" becomes "-name", etc).

function Hello-World($Name='World')
{
    Write-Output "Hello, $($Name)!"
}

Using param, multiple parameters can be defined in a param block, which would probably make more sense for complicated functions using multiple parameters. Here the single parameter is also typed.

function Hello-World
{
    param (
        [string]$Name='World'
    )
    Write-Output "Hello, $($Name)!"
}

<!-- Documentation or comment-based help is incorporated by means of specially formatted comments within the body of the function. Even without comments, PowerShell will attempt to provide help to a user from the command-line using Get-Help.

``` -->

## Control flow

```powershell
if ($condition) { <# ... #> }

switch ($reference) 
{
    $value1 { ... }
    $value2 { ... }
}

while ($true) 
{
    (++$Tick)
    if ($Tick -gt 2) 
    { 
        break 
    } 
} # => @(1,2,3)

do 
{ 
    'Hello, world!' 
} while ($false) # => @('Hello, world!')

Loops are implemented with ForEach-Object.

1..5 | ForEach-Object {$_ + 2} # => @(3,4,5,6,7)

When values are stored in a variable at the end of a pipeline, it will create an array. while and do while loops are available, as well as until and do until loops which operate so long as their condition is false.

Variables

Variables are accessed by prefixing the identifier with $.

  • [Automatic variables][Automatic variable] ($$, $_, $PSVersionTable, $IsLinux, etc) are PowerShell-specific.
  • Windows environment variables are actually accessed through the Env virtual drive using syntax like $Env:APPDATA, $Env:USERNAME, etc.

Typing

Typing
[double]$Price
[int]$Quantity
[string]$Description
Casting
$Number = [int]'04'
$FailedCast = [int]'Hello'

Hash tables

Hash tables
$fruit = @{
    Apple = 'red'
    Orange = 'orange'
    Eggplant = 'purple'
}

# Inline
$fruit = @{ Apple = 'red'; Orange = 'orange'; Eggplant = 'purple' }
Hashtable methods
$fruit = @{}
$fruit.Add('Apple','red')
$fruit.Add('Orange','orange')
$fruit.Add('Kiwi','green')

# Deep copy or "clone" of a hashtable.
$fruitclone = $fruit.Clone()

$fruit.Keys # => @('Apple','Orange','Kiwi')
$fruit.Values # => @('red','orange','green')
$fruit.Count
$fruit.Remove('Apple')

Unlike Python, a hash table can be made ordered, changing its data type:

$fruit = [ordered]@{ Apple = 'red'; Orange = 'orange'; Eggplant = 'purple' }
$fruit.GetType().Name # => OrderedDictionary

Documentation

Documentation
<#
.SYNOPSIS
This script coordinates the process of creating new employees

.DESCRIPTION
This script creates new users in Active Directory...

.PARAMETER UserName
The official logon name for the user...

.PARAMETER HomeServer
The server name where the user's home folder will live...
#>

<#
.EXAMPLE
New-CorpEmployee -UserName John-Doe -HomeServer HOMESERVER1
This example creates a single new employee...
#>

Functions

Named parameters can be declared in one of two ways.

  • Within the function body using the param keyword, followed by the name of the variable representing the parameter's value, enclosed in $(...):
  • Directly after the function name in parentheses, with each parameter separated by a comma.

The name of the variable becomes the named parameter used when invoking the function. Default values for parameters can be specified by placing them within the parentheses.

Parameters can be made mandatory by preceding the parameter name with [Parameter(Mandatory=$true)]. Parameters can be static typed by preceding the parameter identifier with the data type in brackets.

function Get-LargeFiles 
{
    Get-ChildItem C:\Users\Michael\Documents |
        where {$_.Length -gt $args[0] and !$_PSIscontainer} |
        Sort-Object Length -Descending
}
function Get-LargeFiles($Size) 
{
    # param ($Size)
    Get-ChildItem C:\Users\Michael\Documents |
        where {$_.Length -gt $Size -and !$_.PSIsContainer} |
        Sort-Object Length -Descending
}
function Get-LargeFiles 
{
    param ($Size)
    Get-ChildItem C:\Users\Michael\Documents |
        where {$_.Length -gt $Size -and !$_.PSIsContainer} |
        Sort-Object Length -Descending
}
function Get-LargeFiles 
{
    param ($Size=2000)
    Get-ChildItem C:\Users\Michael\Documents |
        where {$_.Length -gt $Size -and !$_.PSIsContainer} |
        Sort-Object Length -Descending
}
function Get-LargeFiles 
{
    param ([int]$Size=2000)
    Get-ChildItem C:\Users\Michael\Documents |
        where {$_.Length -gt $Size -and !$_.PSIsContainer} |
        Sort-Object Length -Descending
}
Get-LargeFiles -Size 2000

Switch parameters are typed as a switch data type. Boolean values can be explicitly set upon invocation using this syntax:

function Switch-Item 
{
    param ([switch]$on)
    if ($on) { "Switch on" }
    else { "Switch off" }
}
Switch-Item             # => Switch off
Switch-Item -On         # => Switch on
Switch-Item -On:$false  # => Switch off

Attach common parameters to a custom function by placing the [CmdletBinding()] within the body of a function. This allows use of options like -Verbose or -Debug with custom functions. Now, using Write-Verbose and Write-Debug within the function body serve the dual purpose of outputting additional information at the time of execution, when needed, as well as documentation.

Remote administration

Powershell remoting can be done explicitly or implicitly. Remoting relies on WinRM, which is Microsoft's implementation of WSMAN.

  • Explicit remoting is also 1-to-1 remoting, where an interactive Powershell prompt is brought up on a remote computer.
  • One-to-many or fan-out remoting is possible with implicit remoting, where a command is transmitted to many computers.

Unit testing

Pester tests are organized in a hierarchy of blocks and run with Invoke-Pester:

Describe
{
  Context # optional
  {
    It
    {
      Should # assertion statements accept a value passed in via pipe and **must** be called within a `Describe` block
    }
  }
}
New-Fixture deploy Foo

function Foo 
{
  # ...
}

Describe 'Foo' 
{
    $true | Should -Be $true 
}

The block in braces is actually an argument pass to the -Fixture parameter.

Describe "Best airports in the USA" -Fixture 
{
    It -Name "RDU is one of the best airports" -Test 
    {
        $Output = Get-Airport -City "Raleigh"
        $Output | Should -BeOfType System.Collections.Hashtable
    }
}

Tasks

Display hostname
Get-ComputerInfo -Property CsName # gin.CsName
$Env:computername
Generate password
Add-Type -AssemblyName 'System.Web'
[System.Web.Security.Membership]::GeneratePassword(20, 3)
Save credential in variable
$cred = Get-Credential

$pw = ConvertTo-SecureString "Password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("FullerP", $pw)

Profile

Similar to a bashrc file, PowerShell has various profiles which can contain custom variable and function definitions, accessible through automatic variables like $PROFILE, etc. These are loaded using syntax similar to that of bash:
Reload profile
. $PROFILE

Filtering

Filtering results can be done with 5 commands:

  • Where-Object (aliased to where and ?): the most commonly used such command
  • Select-Object (aliased to sced to specify specific columns of information to be displayed
  • Select-String (aliased to sls)
  • ForEach-Object (aliased to foreach and %) There are two different ways to construct a ForEach-Object statement:
    1. Script block, within which the variable $_ represents the current object
    2. Operation statement, more naturalistic, where you specify a property value or call a method.

Loop examples

Download files
1..24 | ForEach-Object {
    Invoke-WebRequest -OutFile ("TGC_3466_Lect{0:d2}_FallPagansOriginsMedievalChristianity.m4v" -f $_) ("https://securedownloads.teach12.com/anon.eastbaymedia-drm/courses/3466/m4v/TGC_3466_Lect{0:d2}_FallPagansOriginsMedievalChristianity.m4v?userid=$USERID&orderid=$ORDERID&courseid=$COURSEID&FName=TGC_3466_Lect{0:d2}_FallPagansOriginsMedievalChristianity" -f $_)}
Processing multiple files
Get-ChildItem . | ForEach-Object { ffmpeg -i $_.Name $_.Name.Replace('m4a','mp3') }
Alert when connection re-established
while ($true) {
    if ((Test-NetConnection 8.8.8.8 -WarningAction SilentlyContinue).PingSucceeded -eq $true) {
        [System.Console]::Beep(1000,100)
        break
    }
}

New domain controller

[Jones][Jones]

Install-WindowsFeature AD-Domain-Services,DHCP -IncludeManagementTools
Install-ADDSForest -DomainName corp.packtlab.com
Add-DhcpServerv4Scope -Name "PacktLabNet" -StartRange 10.0.0.50 -EndRange 10.0.0.100 -SubnetMask 255.255.255.0
Set-DhcpServerv4OptionValue -DnsDomain corp.packtlab.com
Add-DhcpServerInDC -DnsName dc.corp.packtlab.com
New-AdUser -SamAccountName SysAdmin -AccountPassword (Read-Host "Set user password" -AsSecureString) -Name "SysAdmin" -Enabled $true -PasswordNeverExpires $true -ChangePasswordAtLogon $false
Add-ADPrincipalGroupMembership -Identity "CN=SysAdmin,CN=Users,DC=corp,DC=packtlab,DC=com","CN=Domain Admins,CN=Users,DC=corp,DC=packtlab,DC=com"
Get-ADPrincipalGroupMembership sysadmin

Text to speech

Text to speech
Add-Type –AssemblyName System.Speech # (1)
$tts = New-Object –TypeName System.Speech.Synthesis.SpeechSynthesizer
$tts.Speak('Hello, World!')

# List available voices
Foreach ($voice in $SpeechSynthesizer.GetInstalledVoices()){
    $Voice.VoiceInfo | Select-Object Gender, Name, Culture, Description
}

# Change voice
$tts.SelectVoice("Microsoft Zira Desktop")
$tts.Speak('Hello, World!')

# Save output
$WavFileOut = Join-Path -Path $env:USERPROFILE -ChildPath "Desktop\thinkpowershell-demo.wav" # (2)
$SpeechSynthesizer.SetOutputToWaveFile($WavFileOut)
  1. Powershell: Text To Speech in 3 lines of code
  2. Create Cortana Audio Files From Text Using PowerShell

VHDX file

Create a new 256 GB dynamic VHDX file, mount it, initialize it, and create and format the partition [Zacker][Zacker]: 91

New-VHD -Path C:\Data\disk1.vhdx -SizeBytes 256GB -Dynamic | 
Mount-VHD -Passthru |
Initialize-Disk -PassThru |
New-Partition -DriveLetter X -UseMaximumSize | 
Format-Volume -Filesystem ntfs -FileSystemLabel data1 -Confirm:$False -Force

Restart Wi-Fi adapter

$adaptor = Get-WmiObject -Class Win32_NetworkAdapter | Where-Object {$_.Name -like "*Wireless*"}
$adaptor.Disable()
$adaptor.Enable()

Add a member to a group

Add-ADGroupMember -Identity $group -Members $user1,$user2

Add a new local admin

nlu ansible
Add-LocalGroupMember Administrators ansible

Create a virtual switch with SET enabled

Create a virtual switch with SET enabled. [Zacker][Zacker]: 254

New-VMSwitch -Name SETSwitch -NetAdapterName "nic1","nic2" -EnableEmbeddedTeaming $true
Add new virtual network adapters to VMs
Add-VMNetworkAdapter -VMName server1 -SwitchName setswitch -Name set1
Enable RDMA with [Get-][Get-NetAdapterRdma] and [Enable-NetAdapterRdma][Enable-NetAdapterRdma].

Implement nested virtualization

Both the physical host and the nested virtual host must be running Windows Server 2016, but before installing Hyper-V on the nested host, the following configurations must be made. [Zacker][Zacker]: 181

Provide nested host's processor with access to virtualization technology on the physical host

Set-VMProcessor -VMName server1 -ExposeVirtualizationExtensions $true
Disable dynamic memory
Set-VMMemory -VMName SRV01 -DynamicMemoryEnabled $false
Configure 2 virtual processors
Set-VMProcessor -VMName SVR01 -Count 2
Turn on MAC address spoofing
Set-VMNetworkAdapter -VMName SVR01 -Name "NetworkAdapter" -MACAddressSpoofing On

Enable CredSSP

On the remote (managed) server [Zacker][Zacker]: 176

Enable-PSRemoting
Enable-WSManCredSSP
Add the fully-qualified domain name of the Hyper-V server to be managed to the local system's WSMan trusted hosts list
Set-Item WSMan:\localhost\client\trustedhosts -Value "hypervserver.domain.com"
Enable the use of CredSSP on the client
Enable-WSManCredSSP -Role client -DelegateComputer "hypervserver.domain.com"

Configure Server Core

Manually configure network interface, if a DHCP server is unavailable [Zacker][Zacker]: 19

New-NetIPAddress 10.0.0.3 -InterfaceAlias "Ethernet' -PrefixLength 24
Configure the DNS server addresses for the adapter
Set-DnsClientServerAddress -InterfaceIndex 6 -ServerAddresses ("192.168.0.1","192.168.0.2")
Rename the computer and join it to a domain
Add-Computer -DomainName adatum.com -NewName Server8 -Credential adatum\administrator

Update Server Core image

Mount-WindowsImage -ImagePath .\CoreServer.vhdx -Path .\MountDir -Index 1
Add-WindowsPackage -Path .\MountDir -PackagePath C:\ServicingPackages_cabs
Dismount-WindowsImage -Path .\MountDir -Save

Implement DDA

Discrete Device Assignment (DDA) begins with finding the Instance ID of the device needed to be passed through. [Zacker][Zacker]: 212

Get-PnpDevice -PresentOnly
Disable-PnpDevice -InstanceId                 # Remove host-installed drivers
Get-PnpDeviceProperty                         # Provide `InstanceId` and `KeyName` values in order to get value for `LocationPath` parameter in next command
Dismount-VmHostAssignableDevice -LocationPath # Remove the device from host control
Add-VMAssignableDevice -VM -LocationPath      # Attach the device to a guest

Configure live migration

Live migration is possible between Hyper-V hosts that are not clustered, but they must be within the same (or trusted) domains. [Zacker][Zacker]: 306

Enable-VMMigration
Set-VMMigrationNetwork 192.168.4.0
Set-VMHost -VirtualMachineMigrationAuthenticationType Kerberos
Set-VMHost -VirtualMachineMigrationPerformanceOption smbtransport

Configure S2D cluster

New-Cluster -Name cluster1 -node server1,server2,server3,server4 -NoStorage
Enable-ClusterStorageSpacesDirect

Install Docker Enterprise

[Zacker][Zacker]: 266

Install-Module -Name dockermsftprovider -repository psgallery -force
Install-Package -Name docker -ProviderName dockermsftprovider

Handle XML files

Find a sample XML file here

Assign the output of [gc][Get-Content] to a variable

[xml]$xdoc = gc $xmlfile
The XML tree can be viewed in VS Code using the XML Tools extension. The object itself can be treated as a first-class Powershell object using dot notation. red-gate.com
$xdoc.catalog.book | Format-Table -Autosize
Arrays of elements can be accessed by their index
$xdoc.catalog.book[0]
Nodes in the XML object can also be navigated using XPath notation with the SelectNodes and SelectSingleNode methods.
$xdoc.SelectNodes('//author')
This produces the same output as the command above (in XPath nodes are 1-indexed).
```powershell
$xdoc.SelectSingleNode('//book[1]')
[Select-Xml][Select-Xml] wraps the returned XML node with additional metadata, including the pattern searched. However, it can accept piped input.
(Select-Xml -Xml $xdoc -Xpath '//book[1]').Node
($xml | Select-Xml -Xpath '//book[1]').Node

Update Server Core images

[MeasureUp Lab][pl:70-740]

Mount-WindowsImage -ImagePath .\CoreServer.vhdx -Path .\MountDir -Index 1
Add-WindowsPackage -Path .\MountDir PackagePath C:\ServicingPackages_cabs
Dismount-WindowsImage -Path .\MountDir -Save

Pass-through disk

[Zacker][Zacker]: 226

Set-Disk -Number 2 -IsOffline $true
Add-VMHardDiskDrive -VMName server1 -ControllerType scsi -DiskNumber 2

Site-aware failover cluster

Configure failover clusters for two offices [Zacker][Zacker]: 366

New-ClusterFaultDomain -Name ny -Type site -Description "Primary" -Location "New York, NY"
New-ClusterFaultDomain -Name sf -Type site -Description "Secondary" -Location "San Francisco, CA"
Set-ClusterFaultDomain -Name node1 -Parent ny
Set-ClusterFaultDomain -Name node2 -Parent ny
Set-ClusterFaultDomain -Name node3 -Parent sf
Set-ClusterFaultDomain -Name node4 -Parent sf

Filter AD account information

Get-aduser -filter {(SamAccountName -like "*CA0*")} -properties Displayname, SaMaccountName, Enabled, EmailAddress, proxyaddresses | 
Where {($_.EmailAddress -notlike "*@*")} | 
Where {($_.Enabled -eq $True)} | 
Select Displayname, SaMaccountName, Enabled, EmailAddress, @{L=’ProxyAddress_1'; E={$_.proxyaddresses[0]}}, @{L=’ProxyAddress_2'; E={$_.ProxyAddresses[1]}} | 
Export-csv .\usersnoemail2.csv -notypeinformation

Create VM with installation media

[Practice Lab][pl:70-740]

New-VM PLABWIN102 1536mb 1 -SwitchName 'Private network 1' -NewVHDPath 'C:\Users\Public\Documents\Hyper-V\Virtual hard disks\PLABWIN102.vhdx' -NewVHDSizeBytes 127gb
Set-VMDvdDrive -VMName PLABWIN102 -Path C:\Users\Administrator.PRACTICELABS\Documents\Eval81.iso

Registry

Description Affected key
Fix Windows Search bar docs.microsoft.com HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search
Remove 3D Objects howtogeek.com HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace
Display seconds in system clock howtogeek.com HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
Disable Aero Shake howtogeek.com HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced

Remove 3D Objects from This PC howtogeek.com

Remove-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{0DB7E03F-FC29-4DC6-9020-FF41B59E513A}'
Add seconds to clock howtogeek.com
New-Item -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced -Name ShowSecondsInSystemClock -Value 1
Restart-Computer
New-Item -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search -Name BingSearchEnabled -Value 0
New-Item -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search -Name CortanaConsent -Value 0
Safely combine related registry modifications using [Start-Transaction][Start-Transaction] and [Complete-Transaction][Complete-Transaction] [Holmes][Holmes]: 604
Start-Transaction
New-Item TempKey -UseTransaction
Complete-Transaction
Remove User UAC for local users. ref
Set-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System -Name EnableLUA -Value 0

WinForms

Pastebin

# Load required assemblies
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

# Drawing form and controls
$Form_HelloWorld = New-Object System.Windows.Forms.Form
  $Form_HelloWorld.Text = "Hello World"
  $Form_HelloWorld.Size = New-Object System.Drawing.Size(272,160)
  $Form_HelloWorld.FormBorderStyle = "FixedDialog"
  $Form_HelloWorld.TopMost = $true
  $Form_HelloWorld.MaximizeBox = $false
  $Form_HelloWorld.MinimizeBox = $false
  $Form_HelloWorld.ControlBox = $true
  $Form_HelloWorld.StartPosition = "CenterScreen"
  $Form_HelloWorld.Font = "Segoe UI"

# adding a label to my form
$label_HelloWorld = New-Object System.Windows.Forms.Label
  $label_HelloWorld.Location = New-Object System.Drawing.Size(8,8)
  $label_HelloWorld.Size = New-Object System.Drawing.Size(240,32)
  $label_HelloWorld.TextAlign = "MiddleCenter"
  $label_HelloWorld.Text = "Hello World"
  $Form_HelloWorld.Controls.Add($label_HelloWorld)

# add a button
$button_ClickMe = New-Object System.Windows.Forms.Button
  $button_ClickMe.Location = New-Object System.Drawing.Size(8,80)
  $button_ClickMe.Size = New-Object System.Drawing.Size(240,32)
  $button_ClickMe.TextAlign = "MiddleCenter"
  $button_ClickMe.Text = "Click Me!"
  $button_ClickMe.Add_Click({
    $button_ClickMe.Text = "You did click me!"
    Start-Process calc.exe
  })
  $Form_HelloWorld.Controls.Add($button_ClickMe)

# show form
$Form_HelloWorld.Add_Shown({$Form_HelloWorld.Activate()})
[void] $Form_HelloWorld.ShowDialog()

Modules

Create a new module by placing a .psm1 file in a directory of the same name

.\Starship\Starship.psm1
Functions defined within the module can be loaded with [Import-Module][Import-Module] (execution policy must allow this).
ipmo .\Starship
To import classes, a different syntax must be used source
Using module .\Starship

Sample enumeration

PowerShellMagazine

Add-Type -AssemblyName System.Drawing
$count = [Enum]::GetValues([System.Drawing.KnownColor]).Count
[System.Drawing.KnownColor](Get-Random -Minimum 1 -Maximum $count)

Migrate a VM

Enable-VMMigration
Set-VMMigrationNetwork 192.168.10.1
Set-VMHost -VirtualMachineMigrationAuthenticationType Kerberos
Set-VMHost -VirtualMachineMigrationPerformanceOption SMBTransport

Storage Spaces Direct

[MeasureUp][mu:70-740]

New-Cluster -Name HC-CLU1 -Node node1, node2, node3, node4 -NoStorage
Enable-ClusterStorageSpacesDirect -CacheMode Disabled -AutoConfig:0 -SkipEligibilityChecks
New-StoragePool -StorageSubSystemFriendlyName *Cluster* -FriendlyName S2DPool -ProvisioningTypeDefault Fixed -PhysicalDisk (Get-PhysicalDisk | Where-Object -Property CanPool -eq $true)
$pool = Get-StoragePool S2DPool
New-StorageTier -StoragePoolUniqueID ($pool).UniqueID -FriendlyName Performance -MediaType HDD -ResiliencySettingName Mirror
New-StorageTier -StoragePoolUniqueID ($pool).UniqueID -FriendlyName Capacity -MediaType HDD -ResiliencySettingName Parity
The next step would be the creation of a new volume
New-Volume -StoragePool $pool -FriendlyName SharedVol1 -FileSystem CSVFS_REFS -StorageTiersFriendlyNames Performance, Capacity -StorageTierSizes 2GB, 10GB

Scheduled task

Automatically run SSH server in WSL on system start

$action = New-ScheduledTaskAction -Execute C:\WINDOWS\System32\bash.exe -Argument '-c sudo service ssh start'
$trigger = New-ScheduledTaskTrigger -AtLogon

Register-ScheduledTask -TaskName 'SSH server' -Trigger $trigger -Action $action

Network connection alert

Cmdlets

Select-String

Select-String -Path *.yml -Pattern 'ansible_host' -Context 2,3