<< $ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

–$–

$_ The current pipeline object; used in script blocks, filters, the process clause of functions, where-object, foreach-object and switch

$^ contains the first token of the last line input into the shell (does not contain the whole command)

$$ contains the last token of last line input into the shell (does not contain the whole command)

$? Contains the success/fail status of the last statement

False if the previous command ended with an error; True otherwise.

–A–

add column to array or object – see array or object, add column

all except first letter of a string

$empID = "xAC0291"
$empID.Substring(1,$empID.Length-1)

and – -and

append more text to a file – see write to file

array, append

$array = ("1","2")
$array+="3"
$array

array or object, add column

#Original array
$originalArray = @(
    [PSCustomObject]@{Name = "John"; Age = 25},
    [PSCustomObject]@{Name = "Jane"; Age = 30},
    [PSCustomObject]@{Name = "Bob"; Age = 40}
)
 
# Modify the AgePlus5 property in the original PSCustomObject
foreach ($obj in $originalArray) {
    $obj.PSObject.Properties.Add([PSNoteProperty]::new("AgePlus5", ($obj.Age + 5)))
}
 
# Display the modified array
$originalArray

example to add OU to fetched User object

$users = Get-ADUser -SearchBase "OU=Users,DC=africa,DC=com" -Filter * -Properties DisplayName, CanonicalName
$users | Get-Member
foreach ($obj in $srvUsers) {
    $obj.PSObject.Properties.Add([PSNoteProperty]::new("OU", (($obj.CanonicalName -split "/")[1..(($obj.CanonicalName -split "/").Length - 2)][0])))
}
$users | Get-Member

array, combine

$a = @("a","b")
$b = @("c","d")
$c = $($a; $b)

array, compare to find common members – see also Join-Object

$a = 1..4
$b = 3..6
$a | ? {$b -contains $_} | sort

array, compare – differences – see also Join-Object

in $a but not in $b: 1 and 2

$a = 1..4
$b = 3..6
$a | ? {$b -notcontains $_} | sort

elements not common between $a and $b: 1, 2, 5, 6

(Compare-Object $a $b ).InputObject | sort

array, compare – SideIndicator – see also Join-Object

$a = 1..4
$b = 3..6
Compare-Object $a $b

returns

InputObject SideIndicator
----------- -------------
  5 =>
  6 =>
  1 <=
  2 <=

array, convert into from object – see object, convert into array

array, create from string with delimited values

$recipients = $addresses -split ";"

array, declare so increasing the number of elements is not restricted

[System.Collections.ArrayList]$array = @()

array, delete element – see array, remove element

array, duplicate elements – find

$a = @(1,2,3,1,2)
$ht = @{}
$a | foreach {$ht["$_"] += 1}
$ht.keys | where {$ht["$_"] -gt 1} | % {Write-Host "Duplicate element found $_"}

array, exclude first element

$myArray = 1, 2, 3, 4, 5
$excludedArray = $myArray[1..($myArray.Length - 1)]

array, exclude first and last elements

$myArray = 1, 2, 3, 4, 5
$excludedArray = $myArray[1..($myArray.Length - 2)]

array, exclude last element

$array = (1..5)
$array | select -SkipLast 1

array, find string as a member of

$array = 1,2,5,8,3,4,5

to use:

$array.Contains(2)

above returns True

$array.Contains(12)

above returns False

array, find elements of an array somewhere in a string

We’re looking for a user that might belong to

the line below beginning with “if($null” has the test

$arrayTenants = ("UK","US","Germany")
#declare a hash of tenants with their respective qualifying OUs as elements
$O365Tenant = @{}
$O365Tenant["US"] = ("one","two","three")
$O365Tenant["UK" = ("four","five","six")
$O365Tenant["Germany"] = ("seven")
Get-ADUser -Filter 'name -like "*"' | ForEach-Object {
    $distinguishedName = $_.distinguishedName
    ForEach ($tenantName in $arrayTenants)
    {
        if($null -ne ($O365Tenant[$tenantName] | ? { $distinguishedName -match $_ }))
        {
           "$tenantName found"
        }
    }
}

see here for more

array, import from text file – see text file, import into an object, record for each field in a file, parse, CSV, import into an object

array index, find which inside of foreach loop

foreach ($item in $array) {
    $array.IndexOf($item)
}

array, index was outside the bounds of – see Index was outside the bounds of the array

array, initialize

$myArray = @("a","b","c","d","e")
$myArray = "a","b","c","d","e"
$myArray = "a".."e"
$myArray = 1..5

array, join – see array, combine

array, join members together into one string with members separated by character(s) – see join members of an array together into one string with members separated by character(s)

array, last element – see also array, exclude last element

$array = (1,2,3)
$array[-1]

array, loop through

create array and then display one at a time

$upn = @("user1@yourDomain.com", "user2@yourDomain.com", "user3@yourDomain.com")
$upn | %{"$_"}

to also list the indices:

$upn | %{"$($upn.IndexOf($_)) $_"}

array, maximum element of

(((Get-Date "8/7/23 10:34 am"), (Get-Date "3/4/22 2:23 pm")) | Measure -Maximum).Maximum

array, methods available (static) – display

[array] | gm -s | select name, definition | ft -a

This is basically a "get help" command for the PowerShell array function

array of hashes, populate – see hash, array of, populate

array of objects – convert to array of strings

put all our OU names into an array

$arrayOUs = Get-ADObject -Filter {ObjectClass -eq "organizationalunit"} -Properties CanonicalName | Select -Property Name
# convert the array of objects into an array of strings
$strArrayOUs = $arrayOUs | % {"$($_.Name)"}

array of objects or hashes, update one property in – see update element in array of objects or hashes

array, print contents of

the trick is the built-in ofs variable

$proxyAddresses = $user.proxyAddresses
$ofs = '; ' # render $proxyAddresses array printable - separated by "; "
"$($proxyAddresses.Count) useful proxyAddresses: $proxyAddresses" # prints out array count & array contents separated by "; "

array one-liners

array, read into from text file – see text file, import into an object, record for each field in a file, parse, CSV, import into an object

array, remove element

$a = ("a1", "b2", "c3")
$a = @($a | ? {$_ -ne "b2"})
$a

array, remove first element – see array, exclude first element

array, remove first and last elements – see array, exclude first and last elements

array, remove last element – see array, exclude last element

array, reverse

$a = 1..10
$b = $a.Clone()
[array]::Reverse($a)
 
# check contents
$a # 10 through 1
$b # 1 through 10

The approach below does not work!

$a = 1,2,3,4,5
$b = $a | sort -Descending

It simply sorts by whatever numbers you happen to have. If you happened to start with them sorted ascending, then by sorting descending, it would indeed reverse the sort. But if you start by having them arbitrarily scrambled, then the command above would not reverse your original order but instead mess up your original order by sorting them descending.

array, select using an array for the properties but get “cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}” error – see select, “Cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}

array, split in two

Useful to queue up names of fields with their data type side by side, easy to visualize and match name to data type and to add, remove or re-arrange these pairs. But we must split them into their two component arrays to feed into parameters of the command that creates named fields with proper data types.

# Initialize first array with alternating elements, each destined for its own array
$userPropertiesWithDataTypes = @(
    "uSNCreated", [Microsoft.SqlServer.Management.Smo.DataType]::Int,
    "name", [Microsoft.SqlServer.Management.Smo.DataType]::NVarChar(50),
    "ObjectGUID", [Microsoft.SqlServer.Management.Smo.DataType]::UniqueIdentifier,
    "Created", [Microsoft.SqlServer.Management.Smo.DataType]::DateTime,
    "Info", [Microsoft.SqlServer.Management.Smo.DataType]::NVarChar(4000)
)

# Initialize empty arrays the two halves of the first array above
$userProperties = @()
$propertyDataTypes = @()
 
# Split the first array into two arrays: one for user property names and the other for each propertys data type
foreach ($userPropertyWithDataType in $userPropertiesWithDataTypes) {
    if ($userPropertyWithDataType -is [string]) {
        $userProperties += $userPropertyWithDataType # Add to the user properties array
    } else {
        $propertyDataTypes += $userPropertyWithDataType # Add to the data types array
    }
}
# bonus 3rd array which pre-pends an “@” to the first array which we'll use later for parameters
$propertyNames = $userProperties | ForEach-Object { "@$_" }

array, unique members – see also unique values, get from list (object with more than one field)

$array = 1,2,3,2
$array | select -Unique

This works OK with one-dimensional arrays but for arrays with more than one dimension, see unique values, get from list

array, is a variable an array? – see variable, is a variable an array?

ASCII, determine whether any characters in a string are not ASCII

$nonASCII = "[^\x00-\x7F]"
$containsNonASCII = "Normal┐╜∩┐╜"
if ($containsNonASCII -match $nonASCII) {"'$containsNonASCII' contains non-ASCII characters"} else {"'$containsNonASCII' contains no non-ASCII characters"}

–B–

break – see also continue, exit, return

terminates execution of a loop or switch statement and hands over control to next statement after it

This does not work like this if the loop in question is part of a piped foreach ("%") statement. If you try that, the dang code just dies right here and never proceeds to the next statement!

–C–

cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}” error – see select, “Cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}

cannot load or run a script – see also scripts disabled, code signing

File C:\Scripts\ListDir.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please see "get-help about_signing" for more details. At line:1 char:14 + .\ListDir.ps1 <<<< + CategoryInfo : NotSpecified: (:) [], PSSecurityException + FullyQualifiedErrorId : RuntimeException

From an elevated prompt:

Set-ExecutionPolicy Unrestricted

or better (restricted to only current user):

Set-ExecutionPolicy Unrestricted -Scope CurrentUser

Sometimes the command above won’t work. It will likely complain about some more specific scope overriding your attempt.

Set-ExecutionPolicy : Windows PowerShell updated your execution policy successfully, but the setting is overridden by a policy defined at a more specific scope. Due to the override, your shell will retain its current effective execution policy of AllSigned.

To find out more, list all the different scopes:

Get-ExecutionPolicy -List

might return something like this:

Scope         ExecutionPolicy
-----         ---------------
MachinePolicy Undefined
UserPolicy    Undefined
Process       Restricted
CurrentUser   Undefined
LocalMachine  Unrestricted

More specific scopes like Process override less specific scopes like CurrentUser. So we need to change the more inclusive Process scope. Try this:

Set-ExecutionPolicy Bypass -Scope Process -Confirm

You may wonder: why not just set this Process scope to Unrestricted as well? But the system may not allow it. But setting it to Bypass instead will probably be allowed.

capitalize word

(Get-Culture).TextInfo.ToTitleCase("god")

carriage return, insert new line using Write-Host – see Write-Host, insert new line

case statement – see switch

can’t run scripts – see scripts disabled, code signing, can’t load or run a script

carriage return, remove strings in a file that include – see also split on carriage return \ new line

$path = "C:\files"
$text = Get-Content"$path\page.htm"
$lines = @()
# to look for patterns spanning more than one line, concatenate the lines into a single string
$concatenatedString = $lines -join "`n"
# Define the pattern to remove (including the embedded carriage return and newline)
$pattern = "<span`nstyle=color:#CCCCCC> </span>"
# Replace the pattern with a new line
$cleanedString = $concatenatedString -replace [regex]::Escape($pattern),"`n"
# second pattern
$pattern = "`n<span`nstyle="
# Replace the pattern with the same thing except get rid of one of the new lines
$cleanedString = $cleanedString -replace [regex]::Escape($pattern), "`n<span style="
# Split the cleaned string back into lines
$lines = $cleanedString -split"`n"

or queue up the patterns to replace into an array:

$path = "C:\files"
$text = Get-Content "$path\page.htm"

$lines = @()
# to look for patterns spanning more than one line, concatenate the lines into a single string
$concatenatedString = $lines -join "`n"

# Define patterns to remove or replace
$patterns = @(
  "<span`n=color:#CCCCCC> </span>",
  "`n<span`n="
)

# Replace patterns in the text
$cleanedString = $concatenatedString -replace [regex]::Escape($patterns[0]), "`n"
$cleanedString = $cleanedString -replace [regex]::Escape($patterns[1]), "`n<span style="

# Split the cleaned string back into lines
$lines = $cleanedString -split "`n"

carriage return, split on – see split on carriage return \ new line, carriage return, remove strings in a file that include

catch specific error – see also error, expand, try/catch

Usually want to catch a specific error. The example below catches problems when user has no permission to execute a command:

catch [System.UnauthorizedAccessException]

But what if you encounter an error and want to catch just that particular error? You search all over the place but you can’t find the magic string to put into those square brackets right after “catch”. How to catch just the error you found? Find the category of the error you just encountered:

$error[0].CategoryInfo.Category

Perhaps you were searching for a user that doesn’t exist, which yields “ObjectNotFound” when you run the command above. If you put that all by itself into the square brackets, won’t work. But you can handle this by switch on the category:

catch {switch ($_.CategoryInfo.Category) {
   "ObjectNotFound" {
       Write-Host "user not found!" -ForegroundColor Yellow
    }

   default { # Catch any other types of errors
       Write-Host "some other error occurred: $($_.CategoryInfo.Category)" -ForegroundColor Red
    }
  }
}

clipboard, send output of a command to

normally, simply append your command with: | clip

if result contains columns which you want to paste into Excel (CSV), append: | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | clip

code signing

find all certs on local machine suitable to sign code

Get-ChildItem Cert:\LocalMachine\My -Recurse | ? {$_.EnhancedKeyUsages -contains "Code Signing"}

find cert for a script

Get-AuthenticodeSignature -FilePath "C:\Jobs\myscript.ps1"

if it isn't signed, it will return one row saying so

color, display color by days ago – see days ago, switch by

color of Write-Host command – see also Write-Color

Write-Host "green color" -foreground "green"

multiple colors on one line

Write-Host "Green " -ForegroundColor Green -NoNewline; Write-Host "Red "-ForegroundColor Red -NoNewline; Write-Host "Yellow " -ForegroundColor Yellow

colors, show available

[enum]::GetValues([System.ConsoleColor]) | Foreach-Object {Write-Host $_ -ForegroundColor $_}

colors, multiple in one line – see Write-Color

column, add to array or object – see array or object, add column

combine many arrays into just one array – see array, combine

command history

shows each command, sequence and duration for the current session – most recent up top

Get-History | select id, CommandLine, StartExecutionTime, EndExecutionTime, @{n="duration";e={$_.EndExecutionTime - $_.StartExecutionTime}} | sort id -Descending | ogv

command "xx" is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. (Exchange) – see not recognized

comment out a line – use "#".  For a block of comments, "<#" at beginning of block followed by "#>" at end of block

computer name that you’re on – see also environment variables

$env:ComputerName

computers, run same script on many at once – see sessions, PowerShell script across several computers using, $using scope modifier

concatenate – +

continue – see also break, exit, return

skip the current execution of the loop and to return the execution to the top of the innermost loop

contains, does a variable contain a substring?

if ($DisplayName -match "departed")

CPU

systeminfo

credentials

$User = "someUser@yourDomain.com"
$PWord = ConvertTo-SecureString -String "topSecret" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord

CSV, import into an object – see also text file, read into object, record for each field in a file, parse

$dir = [environment]::getfolderpath("mydocuments")
$docName = "$($dir)\someFile.csv"
$csv = Import-Csv $docName
$i=0; $count = $csv.Count
$result = @()
foreach ($item in $csv)
{
    $i++; $percentTxt = ($i/$count).ToString("P")
    Write-Host "$i of $($count) ($percentTxt): Name = $($item.Name) and email = $($item.Email)" -ForegroundColor Green
    $result += New-Object -TypeName PSObject -Property @{
        Name = $item.Name
        Email = $item.Email}
}
$result = $result | sort Name | select Name, Email
$result | ogv

CSV with events associated with users, each user having many date/time entries, import into an object to find most recent entry for each user

Used this for Event ID 2889 (LDAP Server signing) with "service" users ("srv-")

$path = "\\server\C$\Jobs"
$file = "Event.csv"
$pathAndFile = "$path\$file"
$events = Import-Csv $pathAndFile
$srvs = @( "srv-1", "srv-2", "srv-3")
$now = Get-Date # Get the current date/time
$i=0; $count = $srvs.Count
foreach ($srv in $srvs) {
    $i++; $percentTxt = ($i/$count).ToString("P")
    $eventsThisSrv = $events | ? {$_.attemptToAuthenticateAs -eq "DOMAIN\$srv"}
    $datesThisSrvStr = $eventsThisSrv.("TimeCreated") # collapse one field of the object to an array of strings
    $dateTimes = $datesThisSrvStr | ForEach-Object{Get-Date $_} # Convert the array of strings to an array of date/time objects
    $sortedDateTimes = $dateTimes | Sort-Object -Descending # Sort the array of date/time objects in descending order
    $mostRecent = $sortedDateTimes[0]
    $daysAgo = New-TimeSpan -Start $mostRecent -End $now # Calculate the number of days between the most recent date/time and now
    $integerDaysAgo = [math]::Truncate($daysAgo.Days)
    $color=$null
    $color = switch ($integerDaysAgo) {
        0 {"Red"}
        1 {"yellow"}
        Default {"Green"}
    }
    Write-Host "$i of $($count) ($percentTxt): $srv has $($eventsThisSrv.count) event IDs; most recently $mostRecent ($integerDaysAgo days ago)" -ForegroundColor $color
}

CSV, import special characters

Even though a CSV may look like it has special characters, doesn’t mean those are actually stored as unicode that gets processed the right way.

$dir = [environment]::getfolderpath("mydocuments")
$originalFileName = "$($dir)/input.csv"
$convertedFileName = "$($dir)/$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss'))inputConverted.csv"
Get-Content $originalFileName | Out-File $convertedFileName -Encoding UTF8
Remove-Item $convertedFileName -recurse -force
$csv = Import-Csv $convertedFileName
Foreach ($item in $csv){Write-Host "$($item.Name) - $($item."email address")" -ForegroundColor "green"}

Had the original "input.csv" file had what appeared to be special characters and you merely "Import-Csv" on it and looped through using "foreach" without first "Get-Content" piped to "Out-File" with "-Encoding UTF8", you likely would have only seen unprintable character resembling a solid diamond with a question mark in it for each of those special characters.

Notice that we immediately delete the intermdiate $convertedFileNamefile because we don’t want it left around to clutter things up once we’re done with it.

CSV, export to clipboard to paste into Excel – see columns which you want to paste into Excel (CSV)

–D–

date component of time – see time, date component of

date, convert string to

$myDate = [DateTime]"Jul-16"

date, include in file name – see timestamp, include in file name

days ago, switch by

This file has many entries per user, each with a different datetime. Find the most recent entry for each user. Write each user's name and most recent datetime, color-coding red for less than a day, yellow for a day, otherwise green.

So, we convert text version of datetime to datetime variable, sort, find the newest and then convert to integer so switch will work.

$path = "C:\Jobs\LDAP\report"
$file = "Event2889.csv"
$pathAndFile = "$path\$file"
$events = Import-Csv $pathAndFile
$users= @("user1", "user2", "user3) | sort
$now = Get-Date # Get the current date/time
$i=0; $count = $users.Count
foreach ($user in $users) {
    $i++; $percentTxt = ($i/$count).ToString("P")
    $eventsThisUser = $events | ? {$_.attemptToAuthenticateAs -eq "mydomain\$user"}
    $datesThisUserStr = $eventsThisUser.("TimeCreated") # compress one field of the object to an array of strings
    $dateTimes = $datesThisUserStr | ForEach-Object {Get-Date $_} # Convert the array of strings to an array of date/time objects
    $sortedDateTimes = $dateTimes | Sort-Object -Descending # Sort the array of date/time objects in descending order
    $mostRecent = $sortedDateTimes[0]
    $daysAgo = New-TimeSpan -Start $mostRecent -End $now # Calculate the number of days between the most recent date/time and now
    $integerDaysAgo = [math]::Truncate($daysAgo.Days)
    $color=$null
    $color = switch ($integerDaysAgo) {
        0 {"Red"}
        1 {"yellow"}
        Default {"Green"}
    }
    Write-Host "$i of $($count) ($percentTxt): $user has $($eventsThisUser.count) event IDs; most recently $mostRecent ($integerDaysAgo days ago)" -ForegroundColor $color
}

delete variable – see variable, remove

delimited string, remove first element – see array, exclude first element

delimited string, remove first and last elements – see array, exclude first and last elements

deserialize – see serialize

determine whether a variable is an array – see variable, is a variable an array?

digitally signed, ps1 file won’t run

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

see also code signing

display value of a variable – simply put the variable in its own line. No write-host necessary:

$variable

disabled scripts – see scripts disabled, code signing

distinct elements in an object or array – see array, unique members (one dimensional array), unique values, get from list (object with more than one field)

duplicate elements in an array, find – see array, duplicate elements – find – also see array, unique members (one dimensional array), unique values, get from list (object with more than one field)

duration of last command – see time it took to run most recent command

–E–

edit scripts – powershell ISE (included with Windows 7, 8)

elapsed time – see time elapsed

empty, check if string is empty or null – see null, check if string is null or empty immediately below

environment variables

list all

gci env:* | sort-object name

domain

$env:UserDomain

computer name

$env:ComputerName

path

$env:Path -split(";") | sort $_

path, permanently append

[System.Environment]::SetEnvironmentVariable("Path", $Env:Path + ";C:\Program Files\OpenSSL-Win64\bin", [System.EnvironmentVariableTarget]::Machine)

user name

$env:UserName

or

whoami

user profile

$PROFILE

equal – -eq

error, catch specific – see catch specific error

error, expand – see also catch specific error, try/catch

most recent error

$error[0] | Format-List -Force

all errors

$error | Format-List -Force

error message, suppress

$ErrorActionPreference = "SilentlyContinue"
try {
    $reader = $command.ExecuteReader()
} catch {
    $msg = $_.Exception.Message
    Write-Host "$msg" -ForegroundColor Blue
    Add-Content $logfile -value $msg
}

error line number, return – see also line number, return

try {
  # Your script code here
}
catch {
  $errorLine = $_.InvocationInfo.ScriptLineNumber
  $errorMessage = $_.Exception.Message
  Write-Host -ForegroundColor Red "Caught exception: $errorMessage at line $errorLine"
}

error when running script – see can’t load a script

even number, test for

1..20 | % {if ($_ % 2 -eq 0 ) {"$_ is even"}}

execution policy – see scripts disabled, code signing

exit – see also break, continue, return

terminates the current execution session altogether. It also closes the console window and may or may not close ISE.

exit from for loop –

if $thing -eq 'some_condition')
{
  break
}

–F–

field, add to array or object – see array or object, add column

file, append data

$logFile = "C:\Logs\LogFile.txt"
Add-Content $logFile "a new line"

file, folder exist?

if (Test-Path C:\Scripts)
{
 if (Test-Path C:\Scripts\Test.txt)
 {
  "Folder and file exist"
 }
 else
 {
  "Folder exists, file doesn't"
 }
}
else
{
 "Folder does not exist"
}

file, read into array – see text file, import into an object

$file = "someFile.htm"
$myDocs = [environment]::GetFolderPath("MyDocuments")
$file = $myDocs + "\" + $file
$fileContent = Get-Content $file
for ($i= 0; $i -lt $fileContent.length; $i++){
   $fileContent[$i]
}

This is all well and good. But what if we really want an array of hashes? The following works good for CSVs

$csv= Import-Csv $inputfile
foreach($item in $csv)
{
   "$($item.UserPrincipalName) -  $($item.DisplayName)"
}

file name, include timestamp in – see timestamp, include in file name

file, write to – see write to file

files, find matching pattern

useful if you have log files mixed in together because they have not been separated into folders.

$matchingFiles = Get-ChildItem -Path "C:\Jobs\Maintenance\Logs\" -Filter "*.InactiveAccounts.csv"
$fileInfoArray = @()
foreach ($file in $matchingFiles) {
    $fileInfo = [PSCustomObject]@{
        Name = $file.Name
        Date = $file.LastWriteTime
        Size = $file.Length
    }
    $fileInfoArray += $fileInfo
}

Now that you have all the similar files in an array, you can further filter and sort

$fileInfoArray | ? {$_.Name -like "2023*" -and $_.Size -gt 200} | sort date -Descending | ogv

filter timestamp example

filter timestamp {"$(Get-Date -Format G): $_"}

write-output "hello world" | timestamp

find whether elements of an array can be found anywhere in a string – see array, can elements of an array can be found anywhere in a string

first letter of a string, find everything after – see all except first letter of a string

foreign characters – see also special powershell characters and tokens, special characters, replace, CSV, import special characters

If a command exports users whose names contain foreign characters into a CVS file, those foreign characters will be replaced with a ?. To preserve those foreign characters, use Export-Csv $outputfile -Encoding UTF8

Get-Content $file | Out-File $fileUTF8 -NoTypeInformation -Encoding UTF8

function, invoke from within ScriptBlock – see ScriptBlock, invoke function within

function, invoke function residing outside file

Occasionally you may want to keep functionality in a file and maybe even directory outside of the script you’re running rather than have to store those functions inside the script.

$directoryWithFunctions = "\\deathstar.galaxyfarfaraway.com\Infrastructure\"
. "$($directoryWithFunctions)DestroyPlanetLaser.ps1"
cd $directoryWithFunctions

Or the other way around:

cd $directoryWithFunctions
. ".$($directoryWithFunctions)DestroyPlanetLaser.ps1"

function, return more than one variable – see Returning multiple values from a PowerShell function, Return Several Items From a PowerShell Function

Use PSCustomObject to Return Several Items From a PowerShell Function

function user()
{
    $obj = [PSCustomObject]@{
        Name = 'Joe'
        Age = 29
        Address = 'US'
    }
    return $obj
}
$a=user
Write-Host "$($a.Name) is $($a.Age) and lives in $($a.Address)."

–G–

git – click Windows icon → start typeing "git" and select "git bash"

greater than– -gt

greater than or equal – -ge

gridview – | Out-GridView or | ogv

group by

Get-User | Group RecipientTypeDetails

with percents and totals, example below groups all users without email by OU

$objectsAll = Get-ADUser -Filter * -Properties $mail
$group = $objectsAll | ? {$_.Mail -eq $null -or $_.Mail -eq ""}
$grouped = $group | group OU | select Count, Name | sort Count -Descending
$resultGrouped = @()
$grouped | ForEach-Object {
    $resultGrouped += New-Object -TypeName PSObject -Property @{
    constituent = $_.Name
    count = $_.Count
    percent = ($_.Count / $group.Count).ToString("P")}
}
$resultGrouped = $resultGrouped | select constituent, count, percent | sort count -Descending
$resultGrouped += New-Object -TypeName PSObject -Property @{
    constituent = "total"
    count = $group.Count
    percent = "100%"}
$resultGrouped | ft -a

group by hour – see hour, group by

group by month – see month, group by

group by percent – see percent of group, percent progress through a loop

group by year – see year, group by

group by multiple columns

$users | group OU, employeeType | select @{n="OU";e={$_.Group[0].OU}}, @{n="employeeType";e={$_.Group[0].employeeType}}, Count

group by whether null

Below lumps empty values in with nulls

$users | select @{n="enabled";e={$_.enabled -ne $null -and $_.enabled -ne ""}} | group enabled | select @{n="enabled";e={$_.Group[0].enabled}}, count

fancier, with percents and total

$objectsAll = Get-ADUser -Properties extensionAttribute9 -Filter * | select samaccountname, @{n="extensionAttribute9IsNull";e={$_.extensionAttribute9 -eq $null -or $_.extensionAttribute9 -eq ""}}
$usersGrouped = $objectsAll | group extensionAttribute9IsNull | select Count,Name
$resultWithPercent = @()
$usersGrouped | ForEach-Object {
    $groupName = switch ($_.Name) {
        $true {"have nothing for extensionAtribute9"}
        $false {"have something in extensionAtribute9"}
    }
    $resultWithPercent += New-Object -TypeName PSObject -Property @{
        groupName = $groupName
        count = $_.Count
        percent = ($_.Count / $objectsAll.Count).ToString("P")}
}
$resultWithPercent = $resultWithPercent | select groupName, count, percent | sort count -Descending
$resultWithPercent += New-Object -TypeName PSObject -Property @{
    groupName = "total"
    count = $objectsAll.Count
    percent = "100%"}
$resultWithPercent | ogv

–H–

hash, clear

$a.Clear()

hash, declare

$guestUsers = @{}

hash, array of, populate

$guestUsers = @(
    @{email = 'bob.smith@yourDomain.com'; DisplayName = "Bob Smith"}
    @{email = 'jim.jones@yourDomain.com'; DisplayName = "Jim Jones"}
    @{email = 'sally.hayes@yourDomain.com'; DisplayName = "Sally Hayes"}
)

hash property, update one property in an array of – see update element in array of objects or hashes

history of commands – see Command History

hour component of a date

whatever hour it is right now

Get-Date -UFormat "%H"

whatever hour it is for an arbitrary time you have in a variable

$teaTime = [DateTime] "5/26/23 2:10 pm"
$teaTime.ToString("HH")

hour, group by

includes date as well as hour

$MSExchMonitor = Get-ADUser -SearchBase "CN=Monitoring Mailboxes,CN=Microsoft Exchange System Objects,DC=DrStrangelove,DC=com" -Filter * -SearchScope Subtree -Properties WhenCreated, employeeType | Select *, @{Name="Date1";Expression={$_.WhenCreated.toString("yyyy-MM-dd hh")}} | group Date1 | select @{n="date hour";e={$_.Group[0].Date1}}, count

how long did the last command take to run? See time it took to run most recent command, Command History

–I–

-in operator – see also -notin operator

"orange" -in "orange","apple" # True

Index was outside the bounds of the array

If I declare an empty array and then try to populate it, this gives “Index was outside the bounds of the array” error:

$i=0
$orig = ("one", "two")
$dupe = @()
foreach ($item in $orig) {
    $dupe[$i] = $item
    $i++
}

If I instead explicitly state the upper and lower bounds of the array, it now works:

$i=0
$orig = ("one", "two")
$dupe = @(0..1)
foreach ($item in $orig) {
    $dupe[$i] = $item
    $i++
}

import text file into array – see text file, import into an object, record for each field in a file, parse, CSV, import into an object

insert new line using Write-Host – see Write-Host, insert new line

'Install-Module' is not recognized – see also not recognized

Start with Get-PackageProvider

Get-PackageProvider

Hopefully, you ought to get at least 3 rows, one of which ought to be PowerShellGet. If Get-PackageProvider isn’t recognized, try Install-PackageProvider

Install-PackageProvider -Name NuGet -Force

invisible characters – see ASCII, determine whether any characters in a string are not ASCII

invoke function residing outside file – see function, invoke function residing outside file

is a variable an array? – see variable, is a variable an array?

–J–

join many arrays into just one array – see array, combine

join members of an array together into one string with members separated by character(s)

$groups = @("US Users","Staff","Big building")

$allGroups = ($replace | % {$groups}) -join ','

see also convert an array object to a string

none of these methods work if you have an array sprinkled with parentheses. For example, if a couple members of your array looks like this:

('12/6/2022 4:00:00 PM', 'HC-Print02', 426, 218)
('12/6/2022 4:00:00 PM', 'HC-Print03', 385, 127)

And you try to join them with a comma, the resulting string never contains those commas.

you want:

('12/6/2022 4:00:00 PM', 'HC-Print02', 426, 218), ('12/6/2022 4:00:00 PM', 'HC-Print03', 385, 127)

Instead, of what the members are butted up against each other without the comma separating each string.

('12/6/2022 4:00:00 PM', 'HC-Print02', 426, 218)('12/6/2022 4:00:00 PM', 'HC-Print03', 385, 127)

I ended up having to loop through the array and doing something like this:

if ($null -eq $concatenanteWithComma) {$concatenanteWithComma = $thisString}
else {$concatenanteWithComma = "$concatenanteWithComma, $thisString"}

I don’t care so much about the extra space after each comma, that’s just window dressing. But I do want that comma.

Join-Object

This emulates database table joins.

There are at least two out there. You might choose Connect data with PowerShell Join-Object module because it can join

The other one is OK, I guess. But don’t install both, or else they’ll get all mixed up with each other, won’t work right, and you’ll end up having to remove both modules

install

Install-Module -Name Join-Object

Did it install right? That is: can we find it when we look for it?

Get-Module -ListAvailable Join-Object

sample test: populate two objects

$processes = Get-Process
$services = Get-Service

join the two, with any properties of the second (right) object prefixed with “service_”. In the example below, both properties have the same name, but the properties used to join can have different names.

$JoinedObject = Join-Object -Left $processes -Right $services -LeftJoinProperty name -RightJoinProperty name -KeepRightJoinProperty -Type OnlyIfInBoth -Prefix service_

If you get this error:

Join-Object: Exception calling "SingleOrDefault" with "1" argument(s): "Sequence contains more than one element"

Probably one of the two objects duplicates the object property used to join. You'll need to remove that duplicate.

$JoinedObject | select Name, Description, service_name, service_status

This example uses OnlyIfInBoth to emulate an inner join. But there are:

–K–

–L–

last command, how long did it take to run? – see time it took to run most recent command

less than – -lt()

less than or equal – -le()

line continuation – use the “grave accent” (`) mark.

Get-ChildItem -Recurse `
-Filter *.jpg `
| Select LastWriteTime

However, this is only ever necessary in such cases as shown above. Usually you get automatic line continuation when a command cannot syntactically be complete at that point. This includes starting a new pipeline element:

Get-ChildItem |
Select Name,Length

Similar to the | a comma will also work in some contexts.

strings (in all varieties) may also extend beyond a single line:

'foo
bar'

They include the line breaks within the string, then.

line number, return – see also error line number, return

use Get-Content to get the contents of the script file and Select-String to match the line that sets the $lineNumber variable. The LineNumber property of the match object will give you the actual line number where the variable is set. The pattern '^\$lineNumber' is looking for a line that starts with $lineNumber, which might not match exactly what’s in your script file, especially if there are spaces or other characters before $lineNumber. Additionally, if the Select-String doesn’t find a match, it won’t return any line number So, modify the pattern to account for possible spaces and also check if the result is $null to handle cases where no match is found.

# testLineNumber
Write-Host "line 2"
$content = Get-Content $PSCommandPath
$match = $content | Select-String -Pattern '^\s*\$lineNumber' -SimpleMatch
if ($match -ne $null) {$lineNumber = $match.LineNumber}
else {$lineNumber = "No match found"}
Write-Host "recorded line number: $lineNumber

Doesn’t work if you try to use double quotes to enclose the “^\$lineNumber

loop, percent progress through – see percent progress through a loop

loop – several types

For - read content of a file as in

$file = "someFile.htm"
$myDocs = [environment]::GetFolderPath("MyDocuments")
$file = $myDocs + "\" + $file
$fileContent = Get-Content $file
for ($i = 0; $i -lt $fileContent.length; $i++){
   $fileContent[$i]
}

ForEach – for “normal” arrays – see also array, loop through

ForEach-Object (or "%") for objects – you need to have the “{” on the same line right after the “ForEach-Object” or you’ll get prompted for:

cmdlet ForEach-Object at command pipeline position 2

Supply values for the following parameters:

Process[0]:

unlike the other flavors of “ForEach” and “While” which expect the “{” to be on the next line.

While - loop through the contents of a file or database query/table

continue – see also break, exit, return

loop, escape – break (all by itself) – see break, continue, exit, return

Keep in mind that this does not work like you’d expect if the loop in question is part of a piped foreach ("%") statement. If you try that, the dang thing just dies and never proceeds to the next statement after the loop!

lower case – see also proper case

append ".ToLower" to string. For example:

$Username = "$($FirstName.Substring(0,1))$LastName".ToLower()

takes the first letter of $FirstName variable and appends to $LastName and then puts whole thing lower case.

–M–

many computers, run same script on many at once – see sessions, PowerShell script across several computers using, $using scope modifier

memory installed on machine you’re on

(Get-CimInstanceWin32_ComputerSystem).TotalPhysicalMemory

or

(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum

in bytes or

(systeminfo | Select-String "Total Physical Memory:").ToString().Split(":").Trim()

in MB

memory available (or free) on machine you’re on

this should work to get available memory in MB…

Get-Counter "\\$env:computername\Memory\Available MBytes"

…or to get available memory in bytes

(Get-CimInstanceWin32_OperatingSystem | Select-Object FreePhysicalMemory).FreePhysicalMemory * 1KB

to find percent free memory:

$freePhysicalMemoryBytes = (Get-CimInstanceWin32_OperatingSystem | Select-Object FreePhysicalMemory).FreePhysicalMemory * 1KB # free memory in bytes
$totalPhysicalMemoryBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory # total memory in bytes
$percentFreeMemory = ($freePhysicalMemoryBytes / $totalPhysicalMemoryBytes) * 100 # Calculate free memory percentage

or, round to 2 decimals:

$percentFreeMemory = [System.Math]::Round(($freePhysicalMemoryBytes / $totalPhysicalMemoryBytes) * 100, 2)

here’s a short routine to capture duration and memory consumed for a resource-intensive command such as Get-ADUser -Filter *

$usersHC = $null

$freePhysicalMemoryBytesBegin = (Get-CimInstance Win32_OperatingSystem | Select-Object FreePhysicalMemory).FreePhysicalMemory * 1KB # free memory in bytes
$totalPhysicalMemoryBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory # total memory in bytes
$percentFreeMemoryBegin = ($freePhysicalMemoryBytesBegin / $totalPhysicalMemoryBytes) * 100 # Calculate free memory percentage
$percentFreeMemoryBegin = [System.Math]::Round(($freePhysicalMemoryBytesBegin / $totalPhysicalMemoryBytes) * 100, 2)
Write-Host "$percentFreeMemoryBegin% free memory"
$timeBegin = $(Get-Date)
Write-Host "start with $percentFreeMemoryBegin% ($([System.Math]::Round(($freePhysicalMemoryBytesBegin/ 1GB), 2))GB) free memory at $timeBegin" -ForegroundColor Magenta

$usersHC = Get-ADUser -Filter *
Write-Host "$($usersHC.Count) users found" -ForegroundColor Blue

$freePhysicalMemoryBytesEnd = (Get-CimInstanceWin32_OperatingSystem | Select-Object FreePhysicalMemory).FreePhysicalMemory * 1KB # free memory in bytes
$percentFreeMemoryEnd = ($freePhysicalMemoryBytesEnd / $totalPhysicalMemoryBytes) * 100 # Calculate free memory percentage
$percentFreeMemoryEnd = [System.Math]::Round(($freePhysicalMemoryBytesEnd / $totalPhysicalMemoryBytes) * 100, 2)
Write-Host "$percentFreeMemoryEnd% free memory"
$timeEnd = $(Get-Date)
$elapsedTime = New-TimeSpan $timeBegin $timeEnd

$memoryConsumed = $([System.Math]::Round(($freePhysicalMemoryBytesBegin - $freePhysicalMemoryBytesEnd)/1MB, 2))

Write-Host "end with $percentFreeMemoryEnd% ($([System.Math]::Round(($freePhysicalMemoryBytesEnd/ 1GB), 2))GB) free memory at end time $timeEnd taking $($elapsedTime.Minutes) minutes and $($elapsedTime.Seconds) seconds and consuming $($memoryConsumed)MB" -ForegroundColor Cyan

other putative methods to find available (free) memory that fail…

Get-Counter "\\Memory\\Available MBytes"

…with error

Get-Counter: Unable to connect to the specified computer or the computer is offline.

this might also return the same error

Get-Counter "\\Memory\\Available MBytes" -ComputerName localhost

module ExportedCommands, list

Here’s an example for the ActiveDirectory module

Get-Command-Module ActiveDirectory

module install attempt fails

If you attempt to run some command like

Install-PackageProvider -Name "PowerShellGet"

or

Get-PSRepository

or

Get-InstalledModule

and get some error like this

PacketManagement\Find-Package: Unable to find module providers (PowerShellGet) At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModules.psm1:1397 char:3

look under Documents\WindowsPowerShell folder for a "Modules" folder under that and then delete or rename that "Modules" folder to something like "ModulesOld" and try again. It should recreate that "Modules" folder. And now things might work better.

modules installed – see also module, remove

Gets installed modules on a computer:

Get-InstalledModule

Gets the modules that have been imported or that can be imported into the current session:

Get-Module

What’s available:

Get-Module -ListAvailable

update a module:

Update-Module -Name someModule -Verbose

Sometimes you must know where a module is installed. Especially if you need to uninstall or import.

$AzureInstalled = Get-InstalledModule | Where-Object {$_.Name -like "Azure*"}
$AzureInstalled | select Name, InstalledLocation

module path – There could be many, not just one. See PSModulePath environment variable

module providers

You may try to run a command like

Find-Module *ms*

and get an error

PacketManagement\Find-Package: Unable to find module providers (PowerShellGet) At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModules.psm1:1397 char:3

or some such directory path, file & line number. Before you go chasing down that directory to make sure the module exists, run this command first to find out what Package Providers you have

Get-PackageProvider

You should get a list of package providers. If the one you want (PowerShellGet in this case) is missing, then try:

Install-PackageProvider -Name "PowerShellGet"

if this fails, see Module install attempt fails

module, remove – see also modules installed

This can be a tough nut to crack. Sometimes this works

Remove-Module -Name JoinModule

check if really removed: modules installed
Sometimes even if you use the -Force parameter, they still don’t seem to go away. In this case, try:

Remove-Module -Name JoinModule

when I see what’s "available":

Get-Module -ListAvailable Join-Object

still shows:

Version Name         Repository  Description
------- ----         ----------  -----------
2.0.3   Join-Object  PSGallery   Join-Object LINQ Edition.…

reinstall with -AllowClobber and -Force:

Install-Module -Name Join-Object -RequiredVersion 2.0.3 -AllowClobber -Force

and afterwards

Get-Module -ListAvailable Join-Object

shows:

ModuleType Version PreRelease Name        PSEdition ExportedCommands
---------- ------- ---------- ----        --------- ----------------
Script     2.0.3              Join-Object Desk      Join-Object

and then things started working OK.

But another time, I tried various things:

Remove-Module Duo
Uninstall-Module - Name Duo -Force

Neither of these two worked. when I ran the Get-InstalledModule command, it still showed.

Get-InstalledModule | Select-Object -Property Name

Since this last command still showed the module, I tried removing the exact version shown by the last Get-InstalledModule command

Uninstall-Module -Name Duo -RequiredVersion 1.1 -Force

Nope, still there. Can I find the “Duo” path?

(Get-Module -Name Duo).Path

That just gave an error, but when I ran the Get-InstalledModule command, it still showed. So finally, I list all the module paths:

$env:PSModulePath -split(";") | sort $_

I went to every one of the 3 directories the command above revealed and deleted the “Duo” path. That finally did the trick.

Duo still worked on another machine. But the method used to install There was completely different. All you had to do was put a couple “Duo” directories in one of the many $env:PSModulePath. But which?

$PowerShellModulePaths = $env:PSModulePath -split(";") | sort $_
foreach ($PowerShellModulePath in $PowerShellModulePaths) {
    try {
        Set-Location $PowerShellModulePath -ErrorAction Stop
        Write-Host $PowerShellModulePath -ForegroundColor Green
        Get-ChildItem -Path . -Directory -Filter "Duo*" -Recurse
    }
    catch {Write-Host "$PowerShellModulePath does not exist" -ForegroundColor Yellow}
}

Modulo operator (remainder in division)

if $i below is 10, 20, 30, etc., the if statement below will be true

if ($i % 10 -eq 0) {$i}

month, group by

Get-ADUsers -Filter * | Select *, @{Name="Date1";Expression={$_.WhenCreated.toString("yyyy-MM")}} | group Date1 | select @{n="date";e={$_.Group[0].Date1}}, count

multiple computers, run same script on many at once – see sessions, PowerShell script across several computers using, $using scope modifier

multiple variables, return from function – see function, return more than one variable

my documents folder variable

$dir = [environment]::getfolderpath("mydocuments")

–N–

name of the computer/PC/server that you’re on – see also environment variables

$env:ComputerName

new line, insert using Write-Host – see Write-Host, insert new line

new line, split on – see split on carriage return \ new line, carriage return, remove strings in a file that include

not

-not()

-notin operator – see also -in operator

"orange" -notin "orange","apple" # False

not recognized – as in:

'xx' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

possible reasons:

command not recognized even though library already installed and imported

This command

Get-CimInstance -Query " Select * from Win32_ComputerSystem where DomainRole=0 or DomainRole=1" # not recognized

Which is supposed to show computers within the scope which were workstations and not servers failed to work; the command was not recognized.  the CimCmdlets module is supposed to include the Get-CimInstance command.

Install-Module -Name CimCmdlets
Import-Module -Name CimCmdlets

Didn’t cause any errors but did’t help either, because this module was ostensibly already part of PowerShell 7 core, which means it doesn't need it’s own separate module to run. When I run

Get-Module -Name CimCmdlets

Returns

ModuleType Version PreRelease Name       ExportedCommands
---------- ------- ---------- ----       ----------------
Binary     7.0.0.0            CimCmdlets {gcai, gcls, gcim, gcms…}

Which makes me think it’s installed but neither

Get-WmiObject -Query " Select * from Win32_ComputerSystem where DomainRole=0 or DomainRole=1"

Or

Get-CimInstance -Query " Select * from Win32_ComputerSystem where DomainRole=0 or DomainRole=1"

Are recognized.  Get-CimInstance is supposed to be included in the CimCmdlets module but when I run:

(Get-Module -Name CimCmdlets).ExportedCommands

I only get<\p>

Key  Value
---  -----
gcai gcai
gcls gcls
gcim gcim
gcms gcms
icim icim
ncim ncim
ncms ncms
ncso ncso
rcie rcie
rcim rcim
rcms rcms
scim scim

If Get-CimInstance is supposed to be included in the CimCmdlets module, this list of exported commands ought to include Get-CimInstance, but it doesn’t. So, maybe there could be two conflicting versions?

Let’s look at the path variable

Get-Module -ListAvailable

Among a long list of results is:

Directory: C:\program files\powershell\7\Modules
 
ModuleType Version  PreRelease Name PSEdition ExportedCommands
---------- -------  ---------- ---- --------- ----------------
Manifest   7.0.0.0  CimCmdlets      Core      {Get-CimAssociatedInstance, Get-CimClass, Get-CimInstance, Get-CimSession…}

which includes the very Get-CimInstance command it claims can’t be found. When I run

$env:PSModulePath -split(";") | sort $_

I get

c:\program files\powershell\7\Modules
C:\Program Files\PowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

Perhaps

C:\Program Files\PowerShell\Modules

interferes or conflicts with

c:\program files\powershell\7\Modules

So delete the old path that probably points to older version 5.1 to leave only the path pointing to the newer version 7:

$Remove = "C:\Program Files\PowerShell\Modules"
$env:PSModulePath = ($env:PSModulePath.Split(";") | ? -FilterScript {$_ -ne $Remove}) -join ";"

And that fixed the problem

command not recognized because snapin missing

for example:

Get-Mailbox -ResultSize Unlimited

returns:

get-mailbox : The term 'Get-Mailbox' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ get-mailbox -ResultSize Unlimited
+ ~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (get-mailbox:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

need to run:

add-pssnapin *exchange* -erroraction SilentlyContinue

null, assign

sometimes you can assign the $null variable. Other times you must use the -clear operator.

null, check if string is null – see null, check if string is null or empty immediately below

null, check if string is null or empty

if ([string]::IsNullOrEmpty($mystring)) {Write-Host "string is NULL or EMPTY"}
else {Write-Host "string has a value"}

infuriatingly, this does not work:

if ($null -eq $record.employeeType -or $record.employeeType -eq "") {

wierdly, this does work:

if ("'$($record.employeeType)'" -eq "''") {

So, better stick with this:

if ([string]::IsNullOrEmpty($record.employeeType)) {

null, check if property is null when filtering

Normally, you can simply filter on someProperty -ne $null. But when trying to filter on some commands like Get-AdUser, problems because PowerShell doesn’t recognize what the $null variable is in that context. So instead, filter on -notlike "*".

Get-AdUser -filter (msExchHideFromAddressLists -notlike "*")} -Properties $Properties

null, group by whether null – see group by whether null

null, set to blank if

$user.displayName = $use.displayName ?? ""

–O–

object property, update one in an array of – see update element in array of objects or hashes

object or array, add column to – see array or object, add column

object columns, view

$users | Get-Member

object, convert into array

Let’s say you already have an object $objResult with many fields, one of whose fields is called "Source Workstation". To convert this particular field to an array $PCs:

$PCs = $objResult.("Source Workstation")

To get rid of dupes:

$PCs = $PCs | select -Unique

object fields, view – see object columns, view

odd number, test for

1..20 | % {if ($_ % 2 -eq 1 ) {"$_ is odd"}}

or – -or

output results of a command to clipboard – see clipboard, send output of a command to

–P–

parameters, return multiple variables from function – see function, return more than one variable

path environment variable – see also PSModulePath environment variable

$env:Path -split(";") | sort $_

to add:

$Env:PATH += ";C:\php8_2"

But the command above only works as long as this session lasts. To permanently add:

[System.Environment]::SetEnvironmentVariable("Path", $Env:Path + ";C:\Program Files\OpenSSL-Win64\bin", [System.EnvironmentVariableTarget]::Machine)

to remove:

$Remove = "C:\php8_2"
$env:Path = ($env:Path.Split(";") | ? -FilterScript {$_ -ne $Remove}) -join ";"

Find obsolete paths to remove

$Paths = $env:Path -split(";") | sort $_
foreach ($Path in $Paths) {
    try {
        Set-Location $Path -ErrorAction Stop
        Write-Host $Path -ForegroundColor Green
    }
    catch {Write-Host "$Path does not exist" -ForegroundColor Yellow}
}

PC name that you’re on – see also environment variables

$env:ComputerName

percent of group

$items = "A", "A", "B", "C", "C", "C", "D", "D", "D", "D"
$items | group | select Name, Count, @{Name="Percent";Expression={($_.Count / $items.Count)}}

percent progress through a loop

$employees = Get-Msoluser -All
$i=0; $count = $employees.Count
$employeesWithMailboxes = @()
foreach ($employee in $employees) {
    $i++; $percentTxt = ($i/$count).ToString("P")
    $displayName = $employee.DisplayName
    $messagePrefix = "$i of $($count) ($percentTxt): $displayName"
    Write-Host "$messagePrefix now" -ForegroundColor Green
}

phone number, get rid of spaces, dashes, parentheses

$phone = "(800) 234-5678"
$phone.replace(" ","") -replace "[()-]",""

phone number, pad with spaces, dashes, parentheses

$phone = "8002345678"
[String]::Format("{0:(###) ###-####}",[int64]$phone)

pivot

similar to a “pivot” query in MS Access where, in this case, we put days of week as rows and devote a separate column for each hour in the day and put record count in each grid formed thereby

$path = "C:\report"
$file = "Event2889.csv"
$pathAndFile = "$path\$file"
$events = Import-Csv $pathAndFile
 
# generate separate fields for DayOfWeek and HourOfDay
$filteredEvents = $events | ? {$_.attemptToAuthenticateAs -eq "DEATHSTAR\darthvadar"} | select attemptToAuthenticateAs, @{n="DayOfWeek";e={([datetime]$_.TimeCreated).DayOfWeek}}, @{n="HourOfDay";e={([datetime]$_.TimeCreated).Hour}}
 
# optional: group just on day of week first
$filteredEvents | group DayOfWeek | select @{n="DayOfWeek";e={$_.Group[0].DayOfWeek}}, Count # by week day
 
# group on both 1) day of week and 2)hour of day
# run this to find how many hours (e.g., 7 am - 6 pm ) we need to cover below
$groupedEvents = $filteredEvents | group DayOfWeek, HourOfDay | select @{n="DayOfWeek";e={$_.Group[0].DayOfWeek}}, @{n="HourOfDay";e={$_.Group[0].HourOfDay}}, Count
 
# group on day of week and within each day of week, list how many events during each hour of the day
$pivotGrid = $groupedEvents | Group-Object DayOfWeek | ForEach-Object {
    $day = $_.Name
    $counts = $_.Group | Group-Object HourOfDay | ForEach-Object {
        [PSCustomObject]@{
            HourOfDay = $_.Name
            Count = [int]($_.Group | Measure-Object Count -Sum).Sum
        }
    }
    $dailyTotal = [int]($counts | Measure-Object -Property Count -Sum).Sum
    [PSCustomObject]@{
        DayOfWeek   = $day
        "7-8 AM"    = $counts | ? {$_.HourOfDay -eq 7}  | select -ExpandProperty Count
        "8-9 AM"    = $counts | ? {$_.HourOfDay -eq 8}  | select -ExpandProperty Count
        "9-10 AM"   = $counts | ? {$_.HourOfDay -eq 9}  | select -ExpandProperty Count
        "10-11 AM"  = $counts | ? {$_.HourOfDay -eq 10} | select -ExpandProperty Count
        "11AM-noon" = $counts | ? {$_.HourOfDay -eq 11} | select -ExpandProperty Count
        "noon-1 PM" = $counts | ? {$_.HourOfDay -eq 12} | select -ExpandProperty Count
        "1-2 PM"    = $counts | ? {$_.HourOfDay -eq 13} | select -ExpandProperty Count
        "2-3 PM"    = $counts | ? {$_.HourOfDay -eq 14} | select -ExpandProperty Count
        "3-4 PM"    = $counts | ? {$_.HourOfDay -eq 15} | select -ExpandProperty Count
        "4-5 PM"    = $counts | ? {$_.HourOfDay -eq 16} | select -ExpandProperty Count
        "5-6 PM"    = $counts | ? {$_.HourOfDay -eq 17} | select -ExpandProperty Count
        "6-7 PM"    = $counts | ? {$_.HourOfDay -eq 18} | select -ExpandProperty Count
        "Total"     = $dailyTotal
    }
}
 
$pivotGrid | ogv
$pivotGrid | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | clip

PowerGui

PowerShell, install – see also version of PowerShell, install latest

winget install Microsoft.PowerShell

if winget isn't recognized, see winget not recognized. Otherwise, if there is a new version available, upgrade using command below:

winget upgrade --id Microsoft.Powershell

PowerShell, update – see also PowerShell, install, version of PowerShell, install latest

how does your version compare with what’s available?

winget list --id Microsoft.Powershell

if that fails, see winget not recognized. Otherwise, if there is a new version available, upgrade using command below:

winget upgrade --id Microsoft.Powershell

What if this returns

No available upgrade found.
No newer package versions are available from the configured sources.

even though you know a newer version is available on GitHub?

# Define the URL of the PowerShell release on GitHub
$releaseUrl = "https://github.com/PowerShell/PowerShell/releases/download/v7.4.4/PowerShell-7.4.4-win-x64.msi"

# Define the path where you want to save the downloaded MSI file
$downloadPath = "C:\Temp\PowerShell-7.4.4-win-x64.msi"

# Download the MSI file from the release URL
Invoke-WebRequest -Uri $releaseUrl -OutFile $downloadPath

# Install the downloaded MSI file
Start-Process -FilePath $downloadPath -ArgumentList "/quiet" -Wait

# Verify the installed version of PowerShell
$PSVersionTable
$Host.Version

PowerShell version – see also Windows version

Use the built in variable

$PSVersionTable

shows

Name                           Value
----                           -----
PSVersion                      5.1.17763.134
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17763.134
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

or

$PSVersionTable.psversion

gives version detail in table format

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      17763  134

Get-Host just shows you the version of the host (i.e. of Console.Exe or Visual Studio Code Host).

Name             : Visual Studio Code Host
Version          : 1.10.2
InstanceId       : 7f3dd862-f8ce-49a2-b420-b44ccebc0053
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-US
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.EditorServices.EditorServicesPSHost+ConsoleColorProxy
DebuggerEnabled  : True
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

Or $Host.Version will zero into more detailed version info in a table format.

$Host.Version
Major Minor Build Revision
----- ----- ----- --------
1     10    2     -1

means you have Visual Studio Code Host version 1

progress through loop, percent – see percent progress through a loop

proper case

$words = "wAr aNd pEaCe"

$TextInfo = (Get-Culture).TextInfo
$TextInfo.ToTitleCase($words)

if all caps:

$words = "WAR AND PEACE"

$TextInfo.ToTitleCase($words)

$TextInfo.ToTitleCase($words.ToLower())

property name has spaces – see spaces in property name

properties of object, list all

Just see what properties an object has, not to see what each property contains for each row

$users | Get-Member

properties of object, see all

This example gets all properties of a folder:

Get-ItemProperty \\fs.jiminy.org\songs | Format-List -Property *

or

Get-ItemProperty \\fs.jiminy.org\songs | Get-Member | ? {$_.memberType -eq "Property"}

properties, select using an array variable containing the properties but get “cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}” error – see select, “Cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}

properties – sometimes you can just add Select. But usually you need to add a Select-Object As in:

Get-MsolUser -All | Select-Object UserPrincipalName, isLicensed, immutableID

property list, store in variable

If you have a long list of properties you need to get and then display in a certain order, although the order when you select the properties to begin with doesn’t matter, if you then need to display the properties in a certain order, the order does matter. So, you might want to store them in a variable so you don’t forget or accidentally duplicate one. For that, use an array:

$properties = @("name", "DisplayName", "Title")
$users = Get-ADUser -SearchBase "OU=minions,DC=gru,DC=com" -Filter * -Properties $listProperties
$users = $users | select OU, $properties

property of an object array, update – see update element in array of objects or hashes

PSModulePath environment variable – see also path environment variable

$env:PSModulePath -split(";") | sort $_

–Q–

When to Quote in PowerShell

–R–

read text file into array – see text file, import into an object, record for each field in a file, parse, CSV, import into an object

recognized, not – as in

'xx' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

see not recognized

record for each field in a file, parse

Instead of getting one record with each field delimited by something like a comma as you would in a CSV, occasionally you get a separate record for each file. In the case below, we have two separate fields - “DisplayName” and “Department” – and each is on its own record. We need to gather these two fields together and put them all together in one record.

$dir = [environment]::getfolderpath("mydocuments")
$docName = "$($dir)/ticket1519133.txt"
$fileContents = Get-Content -Path $docName
$i=0; $count = $fileContents.Count
$fieldCount = 2
$result = @()
foreach ($line in $fileContents)
{
    $i++; $percentTxt = ($i/$count).ToString("P")
    if ($i % $fieldCount -eq 1) {
        $DisplayName = $line
    else {
        $j = [math]::floor([decimal]($i/$fieldCount))
        $jcount = [math]::floor($count/$fieldCount)
        Write-Host "$j of $($jcount) ($percentTxt): $line" -ForegroundColor Green
        $result += New-Object -TypeName PSObject -Property @{
            "DisplayName" = $DisplayName
            "Department" = $line
        }
    }
}
$result = $result | select DisplayName, Department
$result | ogv

remainder (as in division) – see modulo

remove module – see module, remove

remove variable – see variable, remove

replace

PowerShell Replace Method And Operator: Syntax, Examples or How to Use PowerShell Replace to Replace Text [Examples]

Method - case sensitive

("Sad to see you").Replace("S","Gl")

you can also “chain” these together

("Sad to see you go").Replace("S","Gl").Replace("you go","you're back!")

Operator - not case sensitive

("Sad to see you") -Replace("S","Gl")

return – see also break, continue, exit

terminates execution of the current function and passes control to the statement immediately after the function call

return more than one value from a function – see function, return more than one value

–S–

sign script digitally – see code signing

scripts disabled – see also can’t load or run a script, code signing

To see current policy:

Get-ExecutionPolicy

To change current policy:

Set-ExecutionPolicy RemoteSigned

But what if you get this?

Set-ExecutionPolicy : Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied.
At line:1 char:1
+ Set-ExecutionPolicy RemoteSigned
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   + CategoryInfo          : NotSpecified: (:) [Set-ExecutionPolicy], UnauthorizedAccessException
   + FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand

Answer: run PowerShell as an administrator

script, return name of

from inside the script

$scriptName = Split-Path -Path $PSCommandPath -Leaf

$PSCommandPath returns the whole path

from inside a function within the script

$scriptName = $MyInvocation.scriptName

ScriptBlock, invoke function within – see also $using scope modifier

function Arise ($a,$b) {
    $a
    $b
    return "Havoc"
}
 
$x = "Wreak"
$y= 123
 
Invoke-Command -ComputerName "Minions" -ScriptBlock ${function:Arise} -ArgumentList $x,$y

select, “Cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}” error

Start with an array of properties in a $listProperties variable

$listProperties = @("sAMAccountName", "DisplayName")

You have a $result containing all these properties (and more) where you want to select all the properties in that $listProperties variable but plus one other property (“name”) that is not in the $listProperties variable containing the rest of the properties. So you do this:

$result = $result | select name, $listProperties

but that fails:

Cannot convert System.Object[] to one of the following types {System.String, System.Management.Automation.ScriptBlock}

To fix, create an array containing the first variable, tack on the $listProperties variable to the $displayColumns, and then select selecting the properties in the $displayColumns array:

$displayColumns = @("name")
$displayColumns += $listProperties
$result = $result | select $displayColumns

serialize – see also xml, export to file

$Data = Get-ChildItem
#Serialize the object
$serialized = [System.Management.Automation.PSSerializer]::Serialize($Data)
$serialized
#Deserialize the object
$deserialized = [System.Management.Automation.PSSerializer]::Deserialize($serialized)
$deserialized

server name that you’re on – see also environment variables

$env:ComputerName

sessions, PowerShell script across several computers using, ScriptBlock, invoke function within, – see also $using scope modifier

This returns the results combined from all servers. It also tacks on a couple useless fields – RunspaceId, PSShowComputerName – and one useful field – PSComputerName. If any computer happens to not find anything, it also tacks on 5 useless, mostly empty fields for that computer.

See more detail for this particular query and how to deal with these extra useless rows and fields here

function Get2889OneDC ($startTime, $endTime) {
    $Filter = @{
        LogName = "Directory Service"
        ID = @(2889)
        StartTime = $startTime
        EndTime = $endTime
    }
    $XML2889 = Get-WinEvent -FilterHashtable $Filter -ErrorAction Stop | % {$_.ToXml()}
    return $XML2889
}

$DCs = @("DC1", "DC2")
$now = Get-Date
$endTime = $now.AddMinutes(-($now.Minute % 15)).AddSeconds(-$now.Second)
$startTime = $endTime.AddMinutes(-15)
$s = New-PSSession -ComputerName $DCs -ErrorAction Stop
$XML2889 = Invoke-Command -session $s -ScriptBlock ${function:Get2889OneDC} -ArgumentList $startTime, $endTime -ErrorAction Stop
Get-PSSession | Remove-PSSession

sort

Get-EventLog system -newest 5 | Sort-Object eventid

Get-MsolUser -All | Sort-Object -property UserPrincipalName

If you want to sort stuff descending, add the -Descending argument

Get-SPOSite | select Url, StorageUsageCurrent, WebsCount | sort StorageUsageCurrent -Descending | ft

If you want to sort stuff descending and ascending, need to do things a little differently

Get-SPOSite | select Url, StorageUsageCurrent | sort @{expression="StorageUsageCurrent";Descending=$true},@{expression="Url";Ascending=$true} | ft

spaces in property name – enclose with quotes as in

$_."IP Address"

spaces in string, trim – see white space, trim

special characters

gets them

$specialChars = 33..47 + 58..64 + 91..96 + 123..126

lists the two-digit ASCII next to what they look like

-join ($specialChars | % {"$($_)=$([char]$_), "})

which helps if you want to omit some of these special characters by tweaking $specialChars. For example, below omits ", ' and `

$specialChars = 33..33 + 35..38 + 40..47 + 58..64 + 91..95 + 123..126

special characters, replace

[char[]]$replace = '!@#$%^&*(){}[]":;,<>/|\+=`~ '''
$regex = ($replace | % {[regex]::Escape($_)})-join '|'
cd 'D:\test
Get-ChildItem -recurse |
  ForEach {
    if ($_.Name -match $RegEx){
      Ren $_.Fullname -NewName $($_.Name -replace $RegEx, '_') -whatif
    }
  }
if ($DisplayName -match "departed")

or

[char[]]$replace = '!@#$%^&*(){}[]":;,<>/|\+=`~ '''

$regex = ($replace | % {[regex]::Escape($_)}) -join '|'

Get-ChildItem -recurse | Where-Object { $_.Name -match $RegEx} | Rename-Item -NewName {$_.Name -replace $RegEx, '_'} -whatif

special character registered sign, replace with empty string

The $_.OperatingSystem.replace("`u{00AE}","").Substring to replace the ® symbol with empty string

special powershell characters and tokens – see also CSV, import special characters, foreign characters, special characters, replace

splatting

split – see also array, exclude first element, array, exclude first and last elements

Let’s say we want to extract the Organizational Unit from a user’s CanonicalName name. That is, Forest/Dwarves/Mine in this case

$CanonicalName = 'MagicKingom.com/Forest/Dwarves/Mine/Sneezy'

We don’t care about the domain out front and only want its Organizational Unit. Start by stripping off the domain.

first

$domain, $contentArray = $CanonicalName -split "/"

This assigns the first element of the array resulting from the split to the $domain variable and the array remaining elements to the $contentArray variable.

everything except the first

everything except the first goes to the $contentArray variable which we can concatenate back together:

$content = ($replace | % {$contentArray}) -join '/'

last

but now we still have the user name at the end we don’t want.

This gets the string at the end (the user), which we may want.

$content.Split("/")[-1]

everything except the last

But to get the OU, we want everything except the last element of the array:

$content.Substring(0,$content.Length-$content.Split("/")[-1].Length-1)

split on carriage return \ new line – see also carriage return, remove strings in a file that include

"This is `r`na string.".Split([Environment]::NewLine)

sometimes this doeosn’t work because instead of the “`r`n”, newline is indicated by only “`n”. In that case, use:

$infoArray = $info -Split"`n"

instead. I ran into this in the info field of Get-User where one out of hundreds of records somehow got a bunch of lines into that field separated by just the newline instead of the normal [Environment]::NewLine

string, everything after first letter of – see all except first letter of a string

string, how many instances – see Counting the number of occurrences of a string or file in PowerShell

"OU=Pumpkin Patch,DC=farms,DC=Dogpatch,DC=gov" -split ",DC=" | measure | select -exp count

If you expect these strings to always be in the middle somewhere, then subtract 1. Because the example above returns 4 because that’s how many substrings result from the split - even though there are only 3 instances of the string we’re searching.

string, find just one

$s = "New York City"

"New","Old" | %{ if ($s.contains($_)) { "$s contains $_" }}

string, find if any of several match – see also string, how many instances

$ignoreDirectories = @("SYSVOL","#recycle","SysStates","LogsConfig")
if(Select-String -InputObject $filename -pattern $ignoreDirectories)
{
  Write-Host "ignore" $filename
}

“-match” also works but is usually used to find match in an array

string, can elements of an array can be found anywhere in – see array, can elements of an array can be found anywhere in a string

string, find files that contain in a directory

if you’re already in the directory you want to search:

Get-ChildItem -recurse | Select-String -pattern "something went wrong" | group path | select name

if you’re not already in the directory you want to search, you can try this. You might get permission complaints, though:

Select-String -Path C:\inetpub\wwwroot -pattern "something went wrong"

string, find whether it is in an array – array, find string as a member of

string, replace

$a = $a -replace "one", "two"

string, split into right and left halves

$pos = $name.IndexOf(";")
$leftPart = $name.Substring(0, $pos)
$rightPart = $name.Substring($pos+1)

substring – see also

append ".Substring(start position, number of characters)" to string. For example:

$Username = "$($FirstName.Substring(0,1))$LastName".ToLower()

takes the first letter of $FirstName variable and appends to $LastName and then puts whole thing lower case.

switch by days ago – see days ago, switch by

switch statement – same as the “case” statement in other languages – here – see alsodays ago, switch by

$a = 2
$b = switch ($a)
    {
        1 {"The color is red."}
        2 {"The color is blue."}
        3 {"The color is green."}
        default {"The color could not be determined."}
    }
$b

–T–

term "xx" is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. (Exchange) – see not recognized

test whether a variable is an array – see variable, is a variable an array?

text file, import into an object - see also CSV, import into an object, record for each field in a file, parse

$dir = [environment]::getfolderpath("mydocuments")
$docName = "$($dir)\textToImport.txt"
$fileContents = Get-Content -Path $docName
$i=0; $count = $fileContents.Count
$result = @()
foreach($line in $fileContents)
{
    $i++; $percentTxt = ($i/$count).ToString("P")
    Write-Host "$i of $($count) ($percentTxt): $line" -ForegroundColor Green
    $result += New-Object -TypeName PSObject -Property @{
        line = $line}
}
$result | ogv

time, convert text to time to find newest – see also time, date component of for a [DateTime]::Parse() alternative to the Get-Date approach used here.

Start with an array of $events, each with an IP immediately followed by a port number and a date/time

$events = @(
    [PSCustomObject]@{IP = "123.45.67.89:59796"; TimeCreated = "2024-04-04 08:30:00"},
    [PSCustomObject]@{IP = "123.45.67.89:59798"; TimeCreated = "2024-04-04 11:15:00"},
    [PSCustomObject]@{IP = "123.45.67.99:59799"; TimeCreated = "2024-04-04 10:11:00"},
    [PSCustomObject]@{IP = "123.45.67.99:59800"; TimeCreated = "2024-04-04 11:10:00"}
)
 
# Convert "TimeCreated" to actual DateTime objects
$events | % {$_.TimeCreated = Get-Date $_.TimeCreated}
 
# Extract the IP component (before the ":")
$IPwithoutPort = $events | % {
    $IPcomponent = ($_.IP -split ":")[0]
    [PSCustomObject]@{
        IP = $IPcomponent
        TimeCreated = $_.TimeCreated
    }
}
 
# Group by IP component and find the most recent TimeCreated
$groupByIPmostRecent = $IPwithoutPort | group -Property IP | % {$_.Group | sort -Property TimeCreated -Descending | select -First 1}
 
foreach ($mostRecent in $groupByIPmostRecent){
    $IP = $mostRecent.IP
    $mostRecent = $mostRecent.TimeCreated
    $daysAgo = New-TimeSpan -Start $mostRecent -End $now # Calculate the number of days between the most recent date/time and now
    $integerDaysAgo = [math]::Truncate($daysAgo.Days)
    $color=$null
    $color = switch ($integerDaysAgo) {
        0 {"Red"}
        1 {"yellow"}
        Default {"Green"}
    }
    Write-Host "most recent Event ID 2889 from $IP was at $mostRecent ($integerDaysAgo days ago)" -ForegroundColor $color
}

time, date component of – see also time, convert text to time to find newest for a Get-Date alternative to the [DateTime]::Parse() approach used here.

$originalDateTime = [DateTime]::Parse("2023-08-15 14:30:00")
$truncatedDate = $originalDateTime.Date
Write-Host "Original DateTime: $originalDateTime"
Write-Host "Truncated Date: $truncatedDate"

time elapsed

the best way to see how long recent commands took to run that I’ve found so far:

Get-History | select id, CommandLine, StartExecutionTime, EndExecutionTime, @{n="duration";e={$_.EndExecutionTime - $_.StartExecutionTime}} | sort id -Descending | ogv

loop example

$startTime = $(Get-Date)
Write-Host "Elapsed:00:00:00"
$NoEvent = $true
While ($NoEvent)
{
  Start-Sleep 1
  $elapsedTime = New-TimeSpan $startTime $(Get-Date)
  Write-Host "Elapsed:$($elapsedTime.ToString("hh\:mm\:ss"))"
  #Handle event
  if(event){$NoEvent = $false}
}

or use the .NET Stopwatch class

$Time = [System.Diagnostics.Stopwatch]::StartNew()
$NoEvent = $true
while ($NoEvent) {
    $CurrentTime = $Time.Elapsed
    Write-Host $([string]::Format("`rTime: {0:d2}:{1:d2}:{2:d2}", $CurrentTime.hours, $CurrentTime.minutes, $CurrentTime.seconds)) -nonewline
    Sleep 1
    #Handle event
    if(event){$NoEvent = $false}
}

time it took to run most recent command - see also Command History

(Get-History)[-1].EndExecutionTime - (Get-History)[-1].StartExecutionTime

how long it took each of the previous 10 commands to run

for ($i = -1; $i -gt -10; $i--){
    $duration = (Get-History)[$i].EndExecutionTime - (Get-History)[$i].StartExecutionTime
    "duration for event $($i): $duration"
}

timestamp – see filter timestamp example

timestamp, convert to string

(Get-Date).ToString("MM/dd/yyyy hh:mm:ss tt")

or

(Get-Date -Format g

timestamp, include in file name

$outFile = "baseFileName_{0:yyyyMMdd-HHmm}.csv" -f (Get-Date)

or if you don’t want to mess with a variable and want to send to the My Documents folder

Get-AdUser | Export-Csv -Path "$([environment]::getfolderpath("mydocuments"))\fileBaseName$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation -Encoding UTF8

or

Get-AdUser | Export-Csv -Path "$([environment]::getfolderpath(("mydocuments"))\fileBaseName$(Get-Date -Format g).csv" -NoTypeInformation -Encoding UTF8

title case – see proper case

tools

ISESteroids - add-on for the Windows PowerShell ISE editor

top 5 – add following to any command: | Select-Object -First 5

trim – see also white space, trim

" first last ".Trim()

try/catch – will suppress system error message but instead keep going and show error of your choice (and highlight just the pertinent part of the error message with back color if you have Write-Color module installed)

see also catch specific error, error, expand

$dir = [environment]::getfolderpath("mydocuments")
$docName = "$($dir)/my.csv"
$csv = Import-Csv $docName
foreach ($item in $csv)
{
    try {
        $user = Get-MsolUser -UserPrincipalName $item.UserPrincipalName
        Write-Host "$($item.DisplayName) / $($item.UserPrincipalName) found" -ForegroundColor Green
    }
    catch {
        Write-Color -T "$($item.DisplayName) / $($item.UserPrincipalName) ", "not found!" -C Blue, Red -B Black, DarkYellow
    }
}

I’ve never been able to get try/catch to work properly with Get-Mailbox failure – see Get-Mailbox failure for work-around

What if the command stops dead in its tracks and never even gets to the “catch” section? For that, you must include “-ErrorAction Stop”. Errors that crop up below include not having permission to read the cluster or not even being able to find the cluster at all. Both those will Stop a “normal” try-catch cold, and you’ll never be able to parse the error.

$clusters = Get-Cluster -Domain gondor
try {
    Get-ClusterNode -cluster $clusters[0] -ErrorAction Stop
}
catch {
    $msg = $error[0].exception.gettype().fullname
    Write-Host "$msg" -ForegroundColor Blue
}

note that "-ErrorAction SilentlyContinue" will not work; must have "-ErrorAction Stop" instead

–U–

unique values, get from list – see also array, unique members (one dimensional array)

Start with variable/object/array $result which may have more than one field/property, one of whose properties is userName. This should work:

$result.userName | sort | Get-Unique # works

Must select only one property and be sorted by that Property before piping to Get-Unique. If you instead try to merely select a property and then try to sort and Get-Unique like this:

$result | select userName | sort | Get-Unique # fail

it will fail. However, if you merely have a one-dimensional array, see array, unique members where you can simply append | select -Unique

unprintable characters – see ASCII, determine whether any characters in a string are not ASCII

update element in array of objects or hashes

hash

$users = @(
    @{DisplayName = "Bob Smith"; phone = "(800) 234-5678"}
    @{DisplayName = "Joe Bagadonitz"; phone = "(800) 234-8567"}
)
$users | select DisplayName, phone
$users | % {$_.phone = $_.phone.replace(" ","") -replace "[()-]",""}

object

$users2 = @()
$users2 += New-Object -TypeName PSObject -Property @{
    DisplayName = "Bob Smith"
    phone = "(800) 234-7856"}
$users2 += New-Object -TypeName PSObject -Property @{
    DisplayName = "Joe Bagadonitz"
    phone = "(800) 234-8567"}
$users2 | ft
$users2 | % {$_.phone = $_.phone.replace(" ","") -replace "[()-]",""}

update PowerShell – see PowerShell, update – see also PowerShell, install, version of PowerShell, install latest

upper case – see also proper case

append " .ToUpper()" to string

user name environment variable – see environment variables

$using scope modifier – see also ScriptBlock, invoke function within

$using is not a built-in PowerShell variable. Instead, it is a scope modifier used in PowerShell to reference variables from the parent scope within a script block, such as in a ForEach-Object loop or a Start-Job script block.

$variable = "Hello, World!"
$job = Start-Job -ScriptBlock {
    Write-Output $using:variable
}

# Wait for the job to complete and get the result
$job | Wait-Job | Receive-Job

–V–

variable, is a variable an array?

$a = 1,2,4,5,6
$a -is [array]

variable name has spaces – see spaces in property name

variable, remove

Remove-Variable users

variables, return multiple variables from function – see function, return more than one variable

variable, store list of properties in – see property list, store in variable

version of PowerShell, install latest – see PowerShell, update, see also PowerShell, install

winget install Microsoft.PowerShell

if that fails, see winget not recognized or:

Invoke-Expression "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI"

As far as I can tell, this doesn’t allow you to specify a version. Whether you want it or not, it’s 7. But you can cancel it before it finishes.

version, which installed?

PowerShell

Windows

Visual Studio, install

Install-Script Install-VSCode -Scope CurrentUser; Install-VSCode.ps1

Visual Studio, shell setting, default – go to File → Preferences → Settings

Integrated Terminal

// Place your settings in this file to overwrite default and user settings.
{
     "terminal.integrated.shell.windows": "\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"
}

or 64-bit PowerShell

     "terminal.integrated.shell.windows": "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.ex"

check if 64-bit:

[Environment]::is64bitProcess

should return True

–W–

wait

Start-Sleep -Milliseconds 400

You can specify Seconds instead of Milliseconds and you can even specify fractional seconds such as -Seconds 1.4. But this only works down to .6 seconds; once you hit .5, there is no wait or pause at all, so you must switch to -Milliseconds.

where in, as in select from a group where elements are in another group – see -in operator

which computer/PC/server are you on? – see also environment variables

$env:ComputerName

white space, trim

# Expected to remove leading and trailing two spaces.
("  Test  ").Trim()
#Expected to remove the leading two spaces and carriage return and newline characters.
("  Test`r`n").Trim()
# Expected to remove the leading two spaces and trailing Unicode Next Line character.
("  Test Test $([Char]0x0085)").Trim()

Who am I?

whoami

winget not recognized

Winget is a part of App Installer, a preinstalled Windows package that allows users to easily install and manage programs. To fix App Installer, update (or install) it from the Microsoft Store.

or

Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.3.2691/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -OutFile .\MicrosoftDesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Add-AppXPackage -Path .\MicrosoftDesktopAppInstaller_8wekyb3d8bbwe.msixbundle

after install:

winget install Microsoft.PowerShell

If even after this, you get:

Program 'winget.exe' failed to run: No applicable app licenses foundAt line:1 char:1

then

Add-AppxProvisionedPackage -Online -PackagePath .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -LicensePath .\08d8788d59cf47ed9bf42c31e31f8efa_License1.xml -Verbose

which requires an elevated prompt

Write-Host, insert new line

Write-Host "show how to`nadd a new line"

Write-Color

# Install from Powershell Gallery https://www.powershellgallery.com/packages/PSWriteColor
Install-Module -Name PSWriteColor
Import-Module PSWriteColor

tabs and vertical space

Write-Color -Text "This is text in Green ",
"followed by red ",
"and then we have Magenta... ",
"isn't it fun? ",
"Here goes DarkCyan" -Color Green, Red, Magenta, White, DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1

with background colors and aliases

Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow

write to file

If file doesn’t exist, create it. If there’s already stuff there, append to it.

$dir = "C:\Jobs\"
$msg = "hi there"
$msg | Out-File -FilePath "$($dir)log.txt" -Append

–X–

xml, export to file – see also serialize

$path = $([environment]::getfolderpath("mydocuments"))
$object | Export-Clixml "$path\$((Get-Date).ToString('yyyy-MM-dd_hh-mm-ss'))object.xml"

alternative to produce similar result, but a bit larger file see also serialize

[System.Management.Automation.PSSerializer]::Serialize($object) | clip

xml, import from file

$path = $([environment]::getfolderpath("mydocuments"))
$object = Import-Clixml -Path "$path\object.xml"

xml, parse

If you include a line like this in your DNS:

        _dmarc	3600	 IN 	TXT	v=DMARC1; pct=100; p=none; rua=mailto:someUser@yourDomain.com

you’ll get XML records mailed to you from various email providers such as yahoo & google. The code below will get some of the columns; not all columns fetched for simplicity’s sake.

$dir = [environment]::getfolderpath("mydocuments")
$fileNameOnly = "google.com!youdDomain.com!1556064000!1556150399.xml"
$fileName = "$dir\$fileNameOnly"
[xml]$xml = Get-Content -Path $fileName
$xml_row = $xml.SelectNodes("//row")
$objResult = @()
foreach ($row in $xml_row) {
    $rowRecord = $row | select @{n="sourceIP";e={$_.source_IP}},@{n="DKIM";e={$_.policy_evaluated.dkim}},@{n="spf";e={$_.policy_evaluated.spf}}
    $objResult += $rowRecord
}
$objResult | ogv

–Y–

year, group by

$users | Group {$_.Created.ToString("yyyy")} | select @{n="year";e={$_.Group[0].Created.ToString("yyyy")}}, Count | sort "year" -Descending

yesterday

[DateTime]::Today.AddDays(-1)

–Z–

–No’s–