= Cheatsheet PowerShell =
**Summary**: Powershell hints, tips, oneliners and best practices. \\
**Date**: 8 December 2024 \\
{{tag>cheatsheet powershell}}
== Useful links ==
* [[https://poshcode.gitbook.io/powershell-practice-and-style/style-guide/introduction|Best practices and Styling]]
* [[https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_commonparameters|Common parameters]]
== PowerShell Help ==
>Get-Help with -Parameter is a quick way to examine the data type a given parameter expects:
PS> Get-Help Get-ChildItem -Parameter Path
-Path
Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (`.`).
Required? false
Position? 0
Default value Current directory
Accept pipeline input? True (ByPropertyName, ByValue)
Accept wildcard characters? false
== PowerShell History ==
> View history of powershell commands and invoke them using "r #"
Get-History
<# Output #>
r 1
\\
> See all saved commands in vscode
code (Get-PSReadLineOption).HistorySavePath
== PowerShell If ==
> If statement
if (($alllogs -eq "True") -AND ($result)){
Write-Host "Both statements are true"
}elseif (($buildreason -eq "IndividualCI") -or ($buildreason -eq "BatchedCI") -or ($buildreason -eq "PullRequest")){
Write-Host "At least one of the statements is true"
}else {
Write-Host "None of the statements is true"
}
== PowerShell Count ==
> Count number of strings / objects
# string
$een = "een"
$een.Count
1
# Array
$twee = @("een","twee")
$twee.count
2
\\
> Length
# string
$een = "een"
$een.Length
3
# Array
$twee = @("een","twee")
$twee.Length
2
$twee[0].Length
3
\\
> Measure-Object - Sometimes counting when there is only one item can [[https://stackoverflow.com/questions/36574283/powershell-count-function-not-working-as-expected|fail]], use measure-object instead.
$een = "een"
($een | Measure-Object).Count
1
$twee = @("een","twee")
($twee | Measure-Object).Count
2
== PowerShell Dates ==
# Dates
$timestamp = Get-Date -format "yyyyMMdd-HH.mm" # 20211011-14.27
$readdate = Get-Date -format "d MMM yyyy" # 11 Oct 2021
$weekdate = Get-Date -uformat %V # Week number # 41
$monthdate = Get-Date -format "MMMM-yyyy" # May-2021
\\
>add or subtract days
[datetime]$endDate = (Get-Date).AddDays(+90)
[datetime]$endDate = (Get-Date).AddDays(-90)
== Powershell Error Handling ==
> Error handling [[https://powershellexplained.com/2017-04-10-Powershell-exceptions-everything-you-ever-wanted-to-know/|explained]]
> Try Catch Error Finally with LineNumber for troubleshooting
function Get-ScriptLineNumber {
return $MyInvocation.ScriptLineNumber }
try{
Write-Host "Do something that you want to catch the error of"
}catch{
# Catch the error
Write-Host "Full Error: $_"
# Or error out after all
Throw "Failed on: $actionname; Status: Failed; Line: $(Get-ScriptLineNumber); Error: $($_.Exception.Message)"
}finally {
<#Do this after the try block regardless of whether an exception occurred or not#>
# For example, clean up remote ps sessions
}
\\
> Exception.options
try{
# Do something that you want to catch the error of
Get-ChildItem \\prox-hyperv -ErrorAction Stop
# [System.Net.DNS]::GetHostByName("server99")
# Connect-AzAccount
# Get-AzResourceGroup -Name "exampleGroup" -ErrorAction Stop
}catch [System.Management.Automation.ItemNotFoundException] {
Write-Host "Cannot find item."
Write-Host -NoNewLine "Exception Message : "
Write-Host -Foreground Red -Background Black "$($_.Exception.Message)"
}catch{
# Catch the error
Write-Host -Foreground Red -Background Black "Error found. Please try again, or if the error persists, contact support for evaluation. Please provide the following diagnostics:"
Write-Host -NoNewLine "Exception Message : "
Write-Host -Foreground Red -Background Black "$($_.Exception.Message)"
# Exceptions can contain inner exceptions. This is often the case when the code you are calling catches an exception and throws a different exception. They will place the original exception inside the new exception.
Write-Host -NoNewLine "Inner Exception Message : "
Write-Host -Foreground Red -Background Black "$($_.Exception.InnerException.Message)"
# The FullyQualifiedErrorId is the .Message property of the exception object along with the the fully-qualified name of the class where the exception originated.
Write-Host -NoNewLine "Fully Qualified Error Id : "
Write-Host -Foreground Red -Background Black "$($_.FullyQualifiedErrorId)"
# Output provides the error type you need to catch this specific error type
Write-Host -NoNewLine "Exception type : "
Write-Host -Foreground Red -Background Black "$($_.Exception.GetType().fullname)"
# Shows the powershell command. Does not work for .Net exceptions
Write-Host -NoNewLine "Command : "
Write-Host -Foreground Red -Background Black "$($_.InvocationInfo.MyCommand)"
# Position of the error in the scriptblock or file
Write-Host -NoNewLine "Position : "
Write-Host -Foreground Red -Background Black "At line: $($_.InvocationInfo.ScriptLineNumber) char:$($_.InvocationInfo.OffsetInLine)"
# Shows the faulty line. Also works for .Net exceptions
Write-Host -NoNewLine "Full line : "
Write-Host -Foreground Red -Background Black "$($_.InvocationInfo.Line)"
# Shows the full error. Useful for troubleshooting. Provides output so use careful when returning output.
Write-Host "Full Error:"
Get-Error
# Shows the full error for AZ cmdlets. Useful for troubleshooting. Provides output so use careful when returning output. Usually also works with non AZ cmdlets.
Write-Host "Full Azure Error:"
Resolve-AzError -Last
}finally {
#Do this after the try block regardless of whether an exception occurred or not. For example, clean up remote ps sessions, clear variables or cleanup files.
}
== PowerShell Input and Output ==
> Enable logging of script
### Script Variables ###
$scriptname = [System.IO.Path]::GetFilenameWithoutExtension($MyInvocation.MyCommand.Path.ToString())
$scriptlocation = Split-Path $myinvocation.mycommand.path
$date = (Get-Date).ToString('yyyyMMdd-HHmm')
### Start transcript for full logging capabilities ###
start-transcript -path "$scriptlocation\$scriptname-$date-logtranscript.txt"
### Stop transcript
Stop-transcript
\\
> Script output and Write-Host options
# Inside a function use return or write-output
return "value"
Write-Output $value
# Output an object without adding it to the function output
Write-Host "color" -ForegroundColor Red -BackgroundColor green -noNewLine
$object | Out-Host
\\
> Input password
$password = Read-Host "Enter password" -asSecureString
\\
> Use padright to fill a line with one symbol
Write-Host ''.PadRight(78, '=')
# Or with a variable
$wide = 32
Write-Host ''.PadRight($wide, '=')
\\
== Preference Variables ==
[[https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables|About Preference Variables]]
> Verbose Preference
# Default
$VerbosePreference = 'SilentlyContinue'
# Override: -Verbose
# Verbose output
$VerbosePreference = 'Continue'
# Override: -Verbose:$false
\\
> Debug Preference
# Default
$DebugPreference = 'SilentlyContinue'
# Debug output
$DebugPreference = 'Continue'
\\
> Whatif Preference
# Default
$WhatIfPreference = $false
# Override: -WhatIf
# When WhatIf is enabled, the cmdlet reports the expected effect of the command, but doesn't execute the command.
$WhatIfPreference = $true
# Override: -WhatIf:$false
== PowerShell Variables ==
> Set system (environment) variable
$env:var="value"
\\
> Get all system (environment) variables, sorted by name
get-childitem -path env:* | Sort Name
\\
> Variables in function
> Declare a variable with script scope to use it in a function
$script:orgUrl = "https://dev.azure.com/getshiftingcom"
$script:apiversion = "api-version=6.0"
function SetBuildTag {
# Load Azure DevOps API settings
AzureDevOpsAPISettings
foreach ($tag in $tags){
$tagurl = "$orgUrl/$env:System_Teamproject/_apis/build/builds/$env:BUILD_BUILDID/tags/$($tag)?$apiversion"
}
}
\\
> set variable with a dash/hyphen
New-Variable -Name "MODULE-SiteName" -Value "Incomingapi"
\\
> set system variable with a dash/hyphen
[Environment]::SetEnvironmentVariable('MODULE-SiteName','Incomingapi')
\\
> Set system variables with a dash/hyphen
${env:test-app_user} = "svc-test-dev"
$user = ${env:test-app_user}
== Remote PowerShell ==
> Set variables
$AdminCredentials = Get-Credential
$remotePort = 5986
$pso = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$Culture = "en-US"
$pso.Culture = $Culture
$pso.UICulture = $Culture
$sessionParams = @{}
$sessionParams.Add('UseSSL', $true)
$sessionParams.Add('Port', $remotePort)
$sessionParams.Add('Credential', $AdminCredentials)
\\
> Start session to restart computer
$session = New-PSSession -ComputerName webserverdmz.domain -EnableNetworkAccess -SessionOption $pso @sessionParams
Invoke-Command -Session $session -ScriptBlock {Restart-Computer}
Remove-PSSession $session
== PowerShell Function Template ==
> See [[https://poshcode.gitbook.io/powershell-practice-and-style/style-guide/function-structure|here]] for guidelines.
function Use-AzureDevOpsApi{ #Use Get-Verb for a list of approved verbs
<#
.SYNOPSIS
Short description
.DESCRIPTION
Long description
.PARAMETER Pat
Explain the parameter Pat. Repeat for additional parameters.
.OUTPUTS
Explain the powershell output, if any
.EXAMPLE
Pipeline example:
- task: PowerShell@2
displayName: "Use Default SystemAccessToken"
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
pwsh: true
targetType: 'inline'
script: |
# Load Azure DevOps API settings
Use-AzureDevOpsApi
Script example:
Use-AzureDevOpsApi -Pat $Pat
.LINK
System access token: https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken
.NOTES
Author : Sjoerd Hooft
Version: 2022-08-03
#>
[CmdletBinding()]
param (
[Parameter()]
[ValidateLength(52,52)] # See for more parameter validation https://poshcode.gitbook.io/powershell-practice-and-style/style-guide/function-structure
[ValidateSet("dev", "tst", "acc")]
[string]
$Pat
)
begin {
Write-Host "##[section]Function: $($MyInvocation.MyCommand)"
Write-Verbose "Input: $($PSBoundParameters | Out-String)"
# Set Verbose switch based on PSBoundParameters
$CmdLetOutput = @{Verbose = $($PSBoundParameters.Verbose)}
# Collect information
}
process {
# Do stuff
}
end {
# Cleanup
}
}
== PowerShell Function Validates ==
* Not null or empty: {{{[ValidateNotNullOrEmpty()]}}}
* Latest or YYYY-MM: {{{[ValidatePattern("latest|20\d{2}[-]\d{2}")]}}}
* Email: {{{[ValidatePattern('(.+@getshifting\.com)$')]}}}
* Specific server name: {{{[ValidatePattern("web(dev|tst|acc)dmz0[12]")]}}}
* Environment: {{{[ValidateSet("dev", "tst", "acc", "prd")]}}}
* Number between: {{{[ValidateRange(8,100)]}}}
* Length range: {{{[ValidateLength(8,12)]}}}
* Length exactly: {{{[ValidateLength(8,8)]}}}
== PowerShell Modules ==
> Check Azure powershell module depending on PS version
Write-Host "Check required PowerShell Modules `n"
# The "new" PS module for Azure requires PowerShell 7
if ($($PSVersionTable.PSVersion).Major -eq 7){
if (Get-Module -ListAvailable -Name AZ) {
Write-Host "PowerShell module Azure exists"
} else {
Write-Host "PowerShell module Azure does not exist. Start installation. "
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
}
}else{
Throw "Powershell needs to be version 7"
}
\\
> PowerShell DNS Module
if (Get-Module -ListAvailable -Name DnsServer) {
Write-Host "PowerShell module DnsServer exists"
} else {
Write-Host "Module DNSServer does not exist. Starting installation"
Import-Module ServerManager
Add-WindowsFeature -Name "RSAT-DNS-Server"
}
\\
> PowerShell AD Module
# Because ActiveDirectory is a 5.1 module you need to add -All
if (Get-Module -ListAvailable -All -Name ActiveDirectory) {
Write-Host "PowerShell module ActiveDirectory exists"
} else {
Write-Host "Module ActiveDirectory does not exist. Starting installation"
Import-Module ServerManager
Add-WindowsFeature -Name "RSAT-AD-PowerShell" –IncludeAllSubFeature
}
\\
> PowerShell Pester module, remove shipped version and install latest version (Needs PS 7)
if (Get-Module -ListAvailable -Name Pester) {
# Check for version 3.4 and if available remove it
Get-Module -ListAvailable -Name Pester
if (((((Get-Module -ListAvailable -Name Pester).Version).Major) -eq 3) -AND ((((Get-Module -ListAvailable -Name Pester).Version).Minor) -eq 4)) {
Write-Host "PowerShell Module Pester is version 3.4 which is shipped along with Win10/Windows 2016. Start removal:"
$module = "C:\Program Files\WindowsPowerShell\Modules\Pester"
takeown /F $module /A /R
icacls $module /reset
icacls $module /grant "*S-1-5-32-544:F" /inheritance:d /T
Remove-Item -Path $module -Recurse -Force -Confirm:$false
}
# Verifieer of Pester al aanwezig met minimaal versie 5
if ((((Get-Module -ListAvailable -Name Pester).Version).Major) -ne 5) {
Write-Host "PowerShell module Pester is not up to date"
Install-Module -Name Pester -Force -Scope CurrentUser
}else{
Write-Host "PowerShell Module Pester is available with minimal version of 5:"
Get-Module -ListAvailable -Name Pester
}
} else {
Write-Host "PowerShell module Pester does not exist. Start installation. "
Install-Module -Name Pester -Force -Scope CurrentUser
}
\\
> IIS Administration Module, install extra version, see the version and the available commandlets. Note that this needs to be done in PowerShell 5 Administrative Shell
Install-Module -Name IISAdministration -Scope AllUsers -Force #Installs latest version next to it (1.1.0.0)
Import-Module IISAdministration -RequiredVersion 1.1.0.0
$mod = Get-Module -Name IISAdministration
$mod.Version
$mod.ExportedCmdlets | Format-Table
\\
>Import PS Module
Import-Module Pester
\\
> Get commands within a module
Get-Command -module Pester
\\
> Get module for a command
Get-Command Get-Service
\\
> Remove / Unload a PowerShell module
Remove-Module Pester
== PowerShell Requires ==
#Requires -Version 7.0
#Requires -PSEdition Core
# Requires for modules is slow because it also imports them
# Requires does not work for non-PS Core modules like DNS and ActiveDirectory
#Requires -Modules @{ ModuleName="Pester"; ModuleVersion="5.0" }
#Requires -Modules Az
#Requires -Modules AzureRmStorageTable
== PowerShell ForEach ==
> With custom PS Object and export to csv
$myCol = @()
foreach ($vm in (get-vmhost esxprd101.intranet | get-vm )){
$VMInfo = "" | Select-Object VMName,VMHostName,NICCount
$VMInfo.VMName = $vmview.Name
$VMInfo.VMHostName = $vmview.Guest.HostName
$VMInfo.NICCount = $vmview.Guest.Net.Count
$myCol += $VMInfo
}
$myCol |Export-csv -NoTypeInformation $csvfile
== PowerShell Comparison ==
> Overview
# logical and comparison
# -and, -or, -not , ! : Connect expressions
# -eq, -ne : Equal, not equal
# -gt / -lt, -ge / -le : Greater/less than, greater or equal / less or equal
# -replace
# -match / -notmatch : Regular expression match
# -like / -notlike : wilcard matching
# -contains / -notcontains : check for value in array: $array -contains $value
# -in / -notin : reverse syntax from contains: $value in $array
== PowerShell Location ==
> Change directory (alias cd)
Set-Location c:\
\\
> Change directory and back
PS C:\Users\sjoer> Push-Location c:\
PS C:\> Pop-Location
PS C:\Users\sjoer>
== Azure PowerShell ==
> **NOTE: Module AZ only works in PowerShell 7**
\\
> TLS version
# Set TLS version to use TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
=== Login to Azure ===
> Login and context
Connect-AzAccount
Get-AzContext -ListAvailable
Set-AzContext -Name $dtapEnvironment -Subscription $azureSubscriptionName
\\
>Get TenantId
$tenantId = (Get-AzContext).Tenant.Id
=== KeyVault ===
> Get secret from KeyVault
$adadminpass = (Get-AzKeyVaultSecret -VaultName kv-operations-global -Name "adAdminPass").SecretValue
\\
> Set secret to KeyVault
$userpasswordsecure = ConvertTo-SecureString -String $userpassword -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName kv-operations-global -Name $username -SecretValue $userpasswordsecure
=== Resourcegroup Creation Time ===
((Invoke-AzRestMethod -Path "/subscriptions/$subId/resourcegroups?api-version=2020-06-01&`$expand=createdTime" -Method GET).Content | ConvertFrom-Json).value | Select-Object name,createdTime