$_ 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.
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
#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
$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 $_"}
$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
=
(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
- US tenant if his distinguished name contains one,two or three
- UK if the distinguishedName contains four,five or six or
- Germany if the distinguishedName contains Germany
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]
create array and then display one at a time
$upn =
@("[email protected]",
"[email protected]",
"[email protected]")
$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, 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
$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
$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}
”
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"}
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!
“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
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
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")
systeminfo
$User
=
"[email protected]"
$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
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)
date component of time – see time, date component of
$myDate = [DateTime]"Jul-16"
date, include in file name – see timestamp, include in file name
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
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
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
$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
}
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
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 {"$(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)."
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
$users | group OU, employeeType | select @{n="OU";e={$_.Group[0].OU}}, @{n="employeeType";e={$_.Group[0].employeeType}}, Count
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
hash, clear
$a.Clear()
hash, declare
$guestUsers = @{}
$guestUsers
=
@
@{email
=
'[email protected]';
DisplayName
=
"Bob Smith"}
@{email
=
'[email protected]';
DisplayName
=
"Jim Jones"}
@{email
=
'[email protected]';
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")
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
-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?
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.
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
- objects on properties, each with a different property name
- inner, left, right, full instead of only inner
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:
AllInLeft
This is the default parameter, which displays all Left elements in the output present at least once, depending on how many elements apply in Right.AllInRight
This is similar to AllInLeft.OnlyIfInBoth
Places all elements from Left in the output, regardless of how many matches are found in Right.AllInBoth
Includes all entries in both Right and Left in output.
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.
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
Here’s an example for the ActiveDirectory module
Get-Command-Module ActiveDirectory
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}
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")
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:
- not recognized even though library already installed and imported
- not recognized because snapin missing
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
$user.displayName = $use.displayName ?? ""
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
$users | Get-Member
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
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
$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)
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
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
$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
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 $_
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
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
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
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
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
[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
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
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"
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
(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
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
variable, is a variable an array?
$a
=
1,2,4,5,6
$a
-is
[array]
variable name has spaces – see spaces in property name
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.
… 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
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
#
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()
whoami
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 "show how to`nadd a new line"
# 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
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
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:[email protected]
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
$users | Group {$_.Created.ToString("yyyy")} | select @{n="year";e={$_.Group[0].Created.ToString("yyyy")}}, Count | sort "year" -Descending
yesterday
[DateTime]::Today.AddDays(-1)