You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In PowerShell 5, there are several different types of arrays that you can work with. Here are the main types
Numeric arrays: Numeric arrays in PowerShell are created using integer indices. The index of the first element is 0, and subsequent elements have indices increasing by 1. Numeric arrays can store any type of data, including strings, numbers, and objects
$numericArray=@(1,2,3,4,5)
Associative arrays (also known as hash tables): Associative arrays use key-value pairs to store data. Each element in the array is accessed using a unique key instead of an index. Keys can be of any data type, and values can be any PowerShell object
Multidimensional arrays: PowerShell supports multidimensional arrays, which are arrays with more than one dimension. You can create arrays with two or more dimensions to store data in a tabular or matrix-like structure
$multiDimArray=@(
@(1,2,3),@(4,5,6),@(7,8,9)
)
Jagged arrays: Jagged arrays are arrays of arrays, where each subarray can have a different length. This allows you to create arrays with varying lengths within a single array
$jaggedArray=@(
@(1,2,3),@(4,5),@(6,7,8,9)
)
Numerical Array:
Indices: Numerical arrays use integer indices to access and identify elements. The indices typically start from 0 and increment by 1 for each subsequent element.
Element Type: Numerical arrays are often used to store numeric values, such as integers or floating-point numbers.
$numericalArray=@(1,2,3,4,5)
Regular Array (Indexed Array):
Indices: Regular arrays also use integer indices to access and identify elements, similar to numerical arrays.
Element Type: Regular arrays can store elements of any data type, including strings, numbers, objects, or even a mixture of different data types.
The key difference between the two lies in the intended usage and the types of values typically stored. Numerical arrays are often used when the elements have a numeric nature, while regular arrays offer flexibility and can store various types of data.
In PowerShell, the term "numerical array" is not an official designation or a specific data type; rather, it is a descriptive term used to refer to arrays that predominantly store numeric values. Regular arrays, on the other hand, can encompass arrays that hold any type of data.
Both numerical arrays and regular arrays use indices to access elements, but the distinction lies in the expected data types and the common use cases associated with each.
You typically need to use the $( ) subexpression syntax in PowerShell in the following situations
Complex Expressions: When you want to embed a complex expression within a string, you can use $( ) to ensure that the expression is evaluated correctly.
$num=5Write-Host"The result is $($num*2)"
The result is 10
Variable Names with Special Characters: If your variable name contains special characters, spaces, or includes properties or methods, using $( ) helps ensure that the entire variable reference is evaluated correctly.
$person= [PSCustomObject]@{
Name="John Doe"Age=30
}
Write-Host"Person's name is $($person.Name)"
Person's name is John Doe
Nested Variable Expansion: If you want to expand multiple variables within a string, you can use $( ) to nest variable references and ensure proper evaluation.
In summary, you can use $( ) in PowerShell to enclose complex expressions, reference variables with special characters, or nest multiple variable expansions within a string. In simpler cases, where you are directly accessing the value of a variable without any additional complexity, you can omit $( ).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# When we're scripting solutions, we need to understand how to concatenate strings and variables in PowerShell. "Concatenate" is just a posh word for joining/linking together.
# There are many ways to concatenate strings and variables in PowerShell – below are a few examples:
# The first example uses the join operator (-join), the second example uses the format operator (-f) and the third example uses the .Net Concat method.
# We can apply the same examples above by substituting the strings of text with variables like so:
# Note above that we also added a “Join 4” example, where we merely inject the three variables into a string using double quotes to join them together. This method of substitution works slightly differently when we’re dealing with object properties. Consider this example where, for example, we’re grabbing the ID or a process called ‘explorer’:
#get process ID of first process called 'explorer'
$join4 = "$word1 $word2 $word3 and the process ID is $proc.id"
Write-Host $join4
# You will notice that that the output is:
# Join me together and the process ID is System.Diagnostics.Process (explorer).id
# Which is not what we want! What we need to do in these instances is to ask PowerShell to resolve the value of $proc.id by enclosing it inside $() like so:
Dot-sourcing is a concept in PowerShell that allows you to reference code defined in one script.
When you writing large size of PowerShell scripts, then there always seems to come a time when a single script just isn’t enough. If we working on large size of script then it important to keep your scripts Simple and Reusable, so you can keep your block of script as modular. The Dot-sourcing is a way to do just that. Making script as modular it also useful when requirment came for adding some more functionlaiyt in exting script. Other than building a module, dot-sourcing is a better way to make that code in another script available to you.
For example, let’s say I’ve got a script that has two functions in it. We’ll call this script CommunicateUser.ps1. Here i had created single module which contain the two functionality in single file which used for sending email and SMS to the customer.
functionSendEmail {
param($EmailContent)
#Write your Logic hereWrite-Output"****************************************"Write-Output"Sending Mail"$EmailContentWrite-Output"****************************************"
}`functionSentSMS {
param($SMSContent)
#Write your Logic hereWrite-Output"****************************************"Write-Output"Sending SMS"$SMSContentWrite-Output"****************************************"
}
# Script-file JustAtest.ps1 with functionfunctionABCDE{
[CmdletBinding()]
param (
$ComputerName
)
Write-Output$ComputerName
}
# Script Call-Script.ps1 calling another script "JustAtest.ps1" in the same folder# relative path
.\JustAtest.ps1
ABCDE -ComputerName Computer12
# fullpath
C:\Junk\JustAtest.ps1
ABCDE -ComputerName Computer13
# fullpath&"C:\Junk\JustAtest.ps1"
ABCDE -ComputerName Computer14
# fullpath."C:\Junk\JustAtest.ps1"
ABCDE -ComputerName Computer15
Dotnet or not Dotnet this is the question we will ask in this post
Lets find out if the .NET .Where() method is significantly faster than their equivalent in native PowerShell
In this post, we'll compare the performance of native PowerShell methods with their .NET counterparts, specifically focusing on the .Where() method. We'll also use the .net[Linq.Enumerable] class to analyze a different dataset - passenger data from the Titanic - instead of the usual Active Directory user data.
The Code
We'll be using three different methods to compare performance:
A Scary Realization: Inconsistent Execution Times
As I was checking the results and testing the reliability of my code, I executed my code segments multiple times. I noticed that there were times when there was another winner when it comes to execution time, and the results were somewhat different each time I ran the code. I was wondering how this could happen, so I decided to switch from PowerShell Version 7.x to 5.1, but the results were nearly the same.
To investigate this further, I performed the same action 101 times for each version of PowerShell on my machine and took the average of each 101 runs, and put them into a table.
The Results: Comparing PowerShell Versions 7.X and 5.1
Here are the results of my tests:
AverageOf101ms
Method
PSVersion
3,0495049504950495
Linq Where-Method
7
5,851485148514851
Piped Where-Method
7
1,3465346534653466
.where()-Method
7
PowerShell Version 5.1
AverageOf101ms
Method
PSVersion
6,88118811881188
Linq Where-Method
5
11,2871287128713
Piped Where-Method
5
3,88118811881188
.where()-Method
5
$myArray=1..1000# Using ForEach-ObjectMeasure-Command {
$myArray|ForEach-Object {
# Do something with the array element$result=$_*2
}
}
# Using .ForEach() methodMeasure-Command {
$myArray.ForEach({
# Do something with the array element$result=$_*2
})
}
Here we explain how to use PowerShell to read and write environment variables, which exist in the user, process and machine contexts
$env:ComputerName# The name of the computer$env:UserName# The username of the current user$env:SystemRoot# The path to the Windows directory$env:TEMP# The path to the temporary folder$env:ProgramFiles# The path to the Program Files directory$env:UserProfile# The path to the user's profile directory$env:Path# The system PATH environment variable$DesktopFolder=Join-Path-Path $env:UserProfile-ChildPath "Desktop"$DocumentsFolder=Join-Path-Path $env:UserProfile-ChildPath "Documents"Get-ChildItem Env:
$Env:Path-split';'|ForEach-Object { Write-Host$_ }
# List Paths$Env:PathGet-Item-Path Env:
[Environment]::GetEnvironmentVariable("Path")
# List PowerShell's Paths$Reg="Registry::HKLM\System\CurrentControlSet\Control\Session Manager\Environment"
(Get-ItemProperty-Path "$Reg"-Name PATH).Path
# We can read and write environment variables in a variety of ways, but the easiest ways to read an environment variable (ComputerName in this example) are as follows# option 1
(Get-Item-Path Env:ComputerName).Value
# option 2$env:ComputerName# Similarly, we can set environment variables like so:Set-Item-Path Env:\ComputerName -Value "NewComputerName"$env:ComputerName="NewComputerName"# But both of these methods only set the environment variable for the current process (i.e the current session). To set it persistently, we can use the .net class to manipulate environment variables. To read we can do the following:
([System.Environment]::GetFolderPath('MyDocuments'))
# Add more folders as needed...$Console=Split-Path$Host.Name-Leaf
[System.Environment]::CommandLine
([System.Environment]::GetEnvironmentVariables()).COMPUTERNAME
# We can get variables from the different contexts like so:
[System.Environment]::GetEnvironmentVariable('ComputerName','User')
[System.Environment]::GetEnvironmentVariable('ComputerName','Process')
[System.Environment]::GetEnvironmentVariable('ComputerName','Machine')
# and set environment variables like so:
[System.Environment]::SetEnvironmentVariable('ComputerName','NewComputerName','User')
[System.Environment]::SetEnvironmentVariable('ComputerName','NewComputerName','Process')
[System.Environment]::SetEnvironmentVariable('ComputerName','NewComputerName','Machine')
# Special folders differ per user and platform. You can use this method to look up locations like the LocalAppData and favorites.
[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::ApplicationData)
[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Favorites)
[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop)
# Another useful method is Is64BitProcess to determine whether the currently running process is 64-bit.
[System.Environment]::Is64BitProcess
# You can also find informationa about the user, like user name.
[System.Environment]::UserName
# Using the System.Environment Class
[System.Environment]::OSVersion
# Using the Win32_OperatingSystem CIM ClassGet-CimInstance Win32_OperatingSystem
# Using the systeminfo executablesysteminfo.exe/fo csv |ConvertFrom-Csv# Using the Get-ComputerInfo Cmdlet# NOTE: OsHardwareAbstractionLayer was deprecated in version 21H1Get-ComputerInfo| Select WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer
<#.SYNOPSIS PowerShell function to modify Env:Path in the registry.DESCRIPTION Includes a parameter to append the Env:Path.EXAMPLE Add-Path -NewPath "D:\Downloads"#>FunctionGlobal:Add-Path {
Param (
[String]
$NewPath="D:\Powershell"
)
Begin
{
Clear-Host
} # End of small begin sectionProcess
{
Clear-Host$Reg="Registry::HKLM\System\CurrentControlSet\Control\Session Manager\Environment"$OldPath= (Get-ItemProperty-Path "$Reg"-Name PATH).Path
$NewPath=$OldPath+';'+$NewPathSet-ItemProperty-Path "$Reg"-Name PATH -Value $NewPath-Confirm
} #End of Process
}
# This is what you type to call the function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FunctionWrite-ProtocolEntry {
<#.SYNOPSIS Output of an event with timestamp and different formatting depending on the level. If the Log parameter is set, the output is also stored in a file.#>
[CmdletBinding()]
Param (
[String]
$Text,
[String]
$LogLevel
)
$Time=Get-Date-Format G
Switch ($LogLevel) {
"Info" { $Message="[*] $Time - $Text"; Write-Host$Message; Break }
"Debug" { $Message="[-] $Time - $Text"; Write-Host-ForegroundColor Cyan $Message; Break }
"Warning" { $Message="[?] $Time - $Text"; Write-Host-ForegroundColor Yellow $Message; Break }
"Error" { $Message="[!] $Time - $Text"; Write-Host-ForegroundColor Red $Message; Break }
"Success" { $Message="[$] $Time - $Text"; Write-Host-ForegroundColor Green $Message; Break }
"Notime" { $Message="[*] $Text"; Write-Host-ForegroundColor Gray $Message; Break }
Default { $Message="[*] $Time - $Text"; Write-Host$Message; }
}
If ($Log) {
Add-MessageToFile-Text $Message-File $LogFile
}
}
FunctionAdd-MessageToFile {
<#.SYNOPSIS Write message to a file, this function can be used for logs, reports, backups and more.#>
[CmdletBinding()]
Param (
[String]
$Text,
[String]
$File
)
try {
Add-Content-Path $File-Value $Text-ErrorAction Stop
} catch {
Write-ProtocolEntry-Text "Error while writing log entries into $File. Aborting..."-LogLevel "Error"Break
}
}
FunctionWrite-ResultEntry {
<#.SYNOPSIS Output of the assessment result with different formatting depending on the severity level. If emoji support is enabled, a suitable symbol is used for the severity rating.#>
[CmdletBinding()]
Param (
[String]
$Text,
[String]
$SeverityLevel
)
If ($EmojiSupport) {
Switch ($SeverityLevel) {
"Passed" { $Emoji= [char]::ConvertFromUtf32(0x1F63A); $Message="[$Emoji] $Text"; Write-Host-ForegroundColor Gray $Message; Break }
"Low" { $Emoji= [char]::ConvertFromUtf32(0x1F63C); $Message="[$Emoji] $Text"; Write-Host-ForegroundColor Cyan $Message; Break }
"Medium" { $Emoji= [char]::ConvertFromUtf32(0x1F63F); $Message="[$Emoji] $Text"; Write-Host-ForegroundColor Yellow $Message; Break }
"High" { $Emoji= [char]::ConvertFromUtf32(0x1F640); $Message="[$Emoji] $Text"; Write-Host-ForegroundColor Red $Message; Break }
Default { $Message="[*] $Text"; Write-Host$Message; }
}
} Else {
Switch ($SeverityLevel) {
"Passed" { $Message="[+] $Text"; Write-Host-ForegroundColor Gray $Message; Break }
"Low" { $Message="[-] $Text"; Write-Host-ForegroundColor Cyan $Message; Break }
"Medium" { $Message="[$] $Text"; Write-Host-ForegroundColor Yellow $Message; Break }
"High" { $Message="[!] $Text"; Write-Host-ForegroundColor Red $Message; Break }
Default { $Message="[*] $Text"; Write-Host$Message; }
}
}
}
## Set up logging functionfunctionLog-Message
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ValidateSet('Error','Info')]
[string]$Level,
[Parameter(Mandatory=$true)]
[string]$Message
)
# Get current date and time$timestamp= (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
# Get current script or function name$caller= (Get-Variable MyInvocation -Scope 1).Value.InvocationName
# Get current line number$line= (Get-Variable MyInvocation -Scope 1).Value.ScriptLineNumber
# Get current file name$file= (Get-Variable MyInvocation -Scope 1).Value.ScriptName
# Build log message$logMessage="[$timestamp] [$Level] [$file:$line] $caller: $Message"# Write log message to fileAdd-Content-Path C:\logs\script.log -Value $logMessage# Write log message to console (optional)Write-Output$logMessage
}
# Example usage
Log-Message -Level Info -Message "This is an info message"
Log-Message -Level Error -Message "This is an error message"
# Set up log file path and filename$logFile="C:\Logs\Script.log"# Set up log functionfunctionWrite-Log
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateSet('Error','Info')]
[string]$Type,
[Parameter(Mandatory=$true)]
[string]$Message
)
# Get caller information$caller= (Get-Variable MyInvocation -Scope 1).Value
$line=$caller.ScriptLineNumber$position=$caller.ScriptLineNumber$file=$caller.ScriptName$function=$caller.MyCommand.Name# Format log message$timestamp=Get-Date-Format "yyyy-MM-dd HH:mm:ss"$logMessage="$timestamp$Type: Line $line, Pos $position, File $file, Function $function - $Message"$logMessage='{0} [{1}] [{2} {3} {4}]: {5}'-f$Timestamp,$Type,$Line-$Function-$File,$Message# Write log message to fileAdd-Content-Path $logFile-Value $logMessage# Write log message to consoleWrite-Output$logMessage
}
# Example usageWrite-Log-Type Error -Message "An error has occurred."Write-Log-Type Info -Message "This is an info message."
functionLog
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory=$false)]
[ValidateSet("Info","Warning","Error")]
[string]$Severity="Info"
)
# Determine the current date and time$Timestamp=Get-Date-Format "yyyy-MM-dd HH:mm:ss"# Write the log message to the consoleWrite-Output"$Timestamp$Severity: $Message"# Append the log message to a log fileAdd-Content-Path "C:\Scripts\Logs\Script.log"-Value "$Timestamp$Severity: $Message"
}
Log -Message "This is an info message"
Log -Message "This is a warning message"-Severity "Warning"
Log -Message "This is an error message"-Severity "Error"
FunctionWrite-LogEntry
{
[CmdletBinding()]
param(
[Parameter(
Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true
)]
[ValidateNotNullOrEmpty()]
[System.String]
$Message,
[Parameter(
Mandatory=$false,Position=2
)]
[string]
$Source='',
[Parameter(
Mandatory=$false,HelpMessage="Severity for the log entry (INFORMATION, WARNING or ERROR)"
)]
[ValidateNotNullOrEmpty()]
[ValidateSet(
"INFORMATION","WARNING","ERROR"
)]
[String]
$Severity="INFORMATION",
[parameter(
Mandatory=$false,HelpMessage="Name of the log file that the entry will written to"
)]
[ValidateNotNullOrEmpty()]
[string]
$OutputLogFile=$Global:LogFilePath
)
Begin
{
$TimeStamp=Get-Date-Format '[MM/dd/yyyy hh:mm:ss]'
}
Process
{
# Get the file name of the source scriptTry
{
If ($script:MyInvocation.Value.ScriptName)
{
[string]$ScriptSource=Split-Path-Path $script:MyInvocation.Value.ScriptName-LeafBase -ErrorAction 'Stop'
}
Else
{
[string]$ScriptSource=Split-Path-Path $script:MyInvocation.MyCommand.Definition-LeafBase -ErrorAction 'Stop'
}
}
Catch
{
$ScriptSource=''
}
# $LogFormat = "{0} {1}: {2} {3}" -f $TimeStamp, $Severity, $ScriptSource, $Message$LogFormat= ("[$TimeStamp] [$Severity]:: In File:$ScriptSource Error:$Error[0].Exception.Message $Message")
# Add value to log filetry
{
if ( -not (Test-Path-Path $LogFilePath-PathType leaf) )
{
Add-Content-Path $OutputLogFile-Value $LogFormat-Encoding Default
}
}
catch
{
Write-Host ("[{0}] [{1}]: Unable to append log entry to [{1}], Error: {2}"-f$TimeStamp,$ScriptSource,$OutputLogFile,"$Error[0].Exception.Message") -ForegroundColor Red
}
}
}
FunctionWrite-LogEntry
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory=$false,Position=2)]
[string]$Source,
[parameter(Mandatory=$false)]
[ValidateSet(0,1,2,3,4,5)]
[int16]$Severity=1,
[parameter(Mandatory=$false,HelpMessage="Name of the log file that the entry will written to.")]
[ValidateNotNullOrEmpty()]
[string]$OutputLogFile=$Global:LogFilePath,
[parameter(Mandatory=$false)]
[switch]$Outhost
)
## Get the name of this function#[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Nameif (-not$PSBoundParameters.ContainsKey('Verbose'))
{
$VerbosePreference=$PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference')
}
if (-not$PSBoundParameters.ContainsKey('Debug'))
{
$DebugPreference=$PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference')
}
#get BIAS time
[string]$LogTime= (Get-Date-Format 'HH:mm:ss.fff').ToString()
[string]$LogDate= (Get-Date-Format 'MM-dd-yyyy').ToString()
[int32]$script:LogTimeZoneBias= [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes
[string]$LogTimePlusBias=$LogTime+$script:LogTimeZoneBias# Get the file name of the source scriptIf ($Source)
{
$ScriptSource=$Source
}
Else
{
Try
{
If ($script:MyInvocation.Value.ScriptName)
{
[string]$ScriptSource=Split-Path-Path $script:MyInvocation.Value.ScriptName-Leaf -ErrorAction 'Stop'
}
Else
{
[string]$ScriptSource=Split-Path-Path $script:MyInvocation.MyCommand.Definition-Leaf -ErrorAction 'Stop'
}
}
Catch
{
$ScriptSource=''
}
}
#if the severity is 4 or 5 make them 1; but output as verbose or debug respectfully.If ($Severity-eq4) { $logSeverityAs=1 }Else { $logSeverityAs=$Severity }
If ($Severity-eq5) { $logSeverityAs=1 }Else { $logSeverityAs=$Severity }
#generate CMTrace log format$LogFormat="<![LOG[$Message]LOG]!>"+"<time=`"$LogTimePlusBias`""+"date=`"$LogDate`""+"component=`"$ScriptSource`""+"context=`"$([Security.Principal.WindowsIdentity]::GetCurrent().Name)`""+"type=`"$logSeverityAs`""+"thread=`"$PID`""+"file=`"$ScriptSource`">"# Add value to log filetry
{
Out-File-InputObject $LogFormat-Append -NoClobber -Encoding Default-FilePath $OutputLogFile-ErrorAction Stop
}
catch
{
Write-Host ("[{0}] [{1}] :: Unable to append log entry to [{1}], error: {2}"-f$LogTimePlusBias,$ScriptSource,$OutputLogFile,$_.Exception.ErrorMessage) -ForegroundColor Red
}
#output the message to hostIf ($Outhost)
{
If ($Source)
{
$OutputMsg= ("[{0}] [{1}] :: {2}"-f$LogTimePlusBias,$Source,$Message)
}
Else
{
$OutputMsg= ("[{0}] [{1}] :: {2}"-f$LogTimePlusBias,$ScriptSource,$Message)
}
Switch ($Severity)
{
0 { Write-Host$OutputMsg-ForegroundColor Green }
1 { Write-Host$OutputMsg-ForegroundColor Gray }
2 { Write-Host$OutputMsg-ForegroundColor Yellow }
3 { Write-Host$OutputMsg-ForegroundColor Red }
4 { Write-Verbose$OutputMsg }
5 { Write-Debug$OutputMsg }
default { Write-Host$OutputMsg }
}
}
}
functionWrite-ErrorsToFile
{
[CmdletBinding()]
param(
[Parameter(
Mandatory=$false,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true
)]
[string]
$Message,
[parameter(
Mandatory=$false
)]
[ValidateSet(0,1,2,3,4,5)]
[int16]
$Severity=1,
[parameter(
Mandatory=$false,HelpMessage="Name of the log file that the entry will written to."
)]
[ValidateNotNullOrEmpty()]
[string]
$OutputLogFile=$Global:LogFilePath
)
# $DateStamp = Get-Date -Format 'MM-dd-yyyy'# $LogFilePath = "$env:USERPROFILE\Desktop\$DateStamp-$env:ComputerName-GlobalErrors.log"if ($Global:Error)
{
if ( -not (Test-Path-Path $LogFilePath-PathType leaf) )
{
New-Item-Path $LogFilePath-ItemType File -Force
}
($Global:Error|ForEach-Object-Process {
# Some errors may have the Windows nature and don't have a path to any of the module's files$Global:ErrorInFile=if ($_.InvocationInfo.PSCommandPath)
{
Split-Path-Path $_.InvocationInfo.PSCommandPath-Leaf
}
[PSCustomObject] @{
TimeStamp= (Get-Date-Format '[MM/dd/yyyy hh:mm:ss]')
LineNumber=$_.InvocationInfo.ScriptLineNumberInFile=$ErrorInFileMessage=$_.Exception.Message
}
} |Sort-Object TimeStamp |Format-Table-HideTableHeaders -Wrap |Out-String).Trim() |Add-Content-Path $LogFilePath-Force -Encoding Defaultif (Test-Path-Path '\\DD2\Logs$'-PathType Container)
{
Move-Item-Path "$env:USERPROFILE\Desktop\*.log"-Destination '\\DD2\Logs$'-Force
}
}
}
<#.SYNOPSIS Writes a message to a log file..DESCRIPTION Writes an informational, warning or error message to a log file. Log entries can be written in basic (default) or cmtrace format. When using basic format, you can choose to include a date/time stamp if required..PARAMETERMessage THe message to write to the log file.PARAMETERSeverity The severity of message to write to the log file. This can be Information, Warning or Error. Defaults to Information..PARAMETERPath The path to the log file. Recommended to use Set-LogPath to set the path. #.PARAMETER AddDateTime (currently not supported) Adds a datetime stamp to each entry in the format YYYY-MM-DD HH:mm:ss.fff.EXAMPLE Write-LogEntry -Message "Searching for file" -Severity Information -Path C:\MyLog.log Description ----------- Writes a basic log entry.EXAMPLE Write-LogEntry -Message "Searching for file" -Severity Warning -LogPath C:\MyLog.log -CMTraceFormat Description ----------- Writes a CMTrace format log entry.EXAMPLE $Script:LogPath = "C:\MyLog.log" Write-LogEntry -Message "Searching for file" -Severity Information Description ----------- First line creates the script variable LogPath Second line writes to the log file.#>functionWrite-LogEntry
{
[CmdletBinding()]
param (
[Parameter(
Mandatory=$true,Position=0
)]
[ValidateNotNullOrEmpty()]
[String]
$Message,
[Parameter(
Mandatory=$false,Position=1,HelpMessage="Severity for the log entry (Information, Warning or Error)"
)]
[ValidateNotNullOrEmpty()]
[ValidateSet(
"Information","Warning","Error")]
[String]
$Severity="Information",
[Parameter(
Mandatory=$false,Position=2,HelpMessage="The full path of the log file that the entry will written to"
)]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{ (Test-Path-Path $_.Substring(0,$_.LastIndexOf("\")) -PathType Container) -and (Test-Path-Path $_-PathType Leaf -IsValid) }
)]
[String]
$Path=$LogFilePath,
[Parameter(
ParameterSetName="CMTraceFormat",HelpMessage="Indicates to use cmtrace compatible logging"
)]
[Switch]
$CMTraceFormat
)
# Construct date and time for log entry (based on current culture)$Date=Get-Date-Format (Get-Culture).DateTimeFormat.ShortDatePattern
$Time=Get-Date-Format (Get-Culture).DateTimeFormat.LongTimePattern.Replace("ss","ss.fff")
# Determine parameter setif ($CMTraceFormat)
{
# Convert severity valueswitch ($Severity)
{
"Information"
{
$CMSeverity=1
}
"Warning"
{
$CMSeverity=2
}
"Error"
{
$CMSeverity=3
}
}
# Construct components for log entry$Component= (Get-PSCallStack)[1].Command
$ScriptFile=$MyInvocation.ScriptName# Construct context for CM log entry$Context=$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
$LogText="<![LOG[$($Message)]LOG]!><time=""$($Time)"" date=""$($Date)"" component=""$($Component)"" context=""$($Context)"" type=""$($CMSeverity)"" thread=""$($PID)"" file=""$($ScriptFile)"">"
}
else
{
# Construct basic log entry# AddDateTime parameter currently not supported#if ($AddDateTime) {$LogText="[{0} {1}] {2}: {3}"-f$Date,$Time,$Severity,$Message$logMessage='{0} [{1}] [{2} {3} {4}]: {5}'-f$Timestamp,$Type,$Line-$Function-$File,$Message#}#else {# $LogText = "{0}: {1}" -f $Severity, $Message#}
}
# Add value to log filetry
{
Out-File-InputObject $LogText-Append -NoClobber -Encoding Default-FilePath $Path-ErrorAction Stop
}
catch [System.Exception]
{
Write-Warning-Message "Unable to append log entry to $($Path) file. Error message: $($_.Exception.Message)"
}
}
<#.SYNOPSIS A short one-line action-based description, e.g. 'Tests if a function is valid'.DESCRIPTION A longer description of the function, its purpose, common use cases, etc..NOTES Information or caveats about the function e.g. 'This function is not supported in Linux'.LINK Specify a URI to a help page, this will show when Get-Help -Online is used..EXAMPLE Write-LogEntry -Message ("Removed {0} built-in App PROVISIONED Package's" -f $d) -Outhost Write-LogEntry ("Reboot is required for remving the Feature on Demand package: {0}" -f $FeatName) Write-LogEntry -Message ("Removed {0} built-in App PROVISIONED Package's" -f $d) -Outhost Write-LogEntry -Message ("Removed {0} built-in App PROVISIONED Package's" -f $d) -Outhost try { Show-ProgressStatus -Message ("Removing Feature on Demand V2 package: {0}" -f $Feature) -Step $f -MaxStep $OnDemandFeatures.count -Outhost $results = Remove-WindowsCapability -Name $Feature -Online -ErrorAction Stop if ($results.RestartNeeded -eq $true) { Write-LogEntry ("Reboot is required for remving the Feature on Demand package: {0}" -f $FeatName) } } catch [System.Exception] { Write-LogEntry -Message ("Failed to remove Feature on Demand V2 package: {0}" -f $_.Message) -Severity 3 -Outhost } $ErrorMessage = $_.Exception.MessageWrite-LogEntry ("Unable to remove item from [{0}]. Error [{1}]" -f $Path.FullName, $ErrorMessage) -Source ${CmdletName} -Severity 3 -OuthostWrite-LogEntry -Message ("Failed to copy files: {0}. Error [{1}]" -f $ErrorMessage) -Source ${CmdletName} -Severity 3 -OuthostWrite-LogEntry ("Unable to remove item from [{0}] because it does not exist any longer" -f $Item.FullName) -Source ${CmdletName} -Severity 2 -OuthostWrite-LogEntry -Message ("Removed {0} built-in App PROVISIONED Package's" -f $d) -OuthostWrite-LogEntry ("Reboot is required for remving the Feature on Demand package: {0}" -f $FeatName)Write-LogEntry -Message ("Removed {0} built-in App PROVISIONED Package's" -f $d) -OuthostWrite-LogEntry -Message ("Removed {0} built-in App PROVISIONED Package's" -f $d) -Outhosttry{ Show-ProgressStatus -Message ("Removing Feature on Demand V2 package: {0}" -f $Feature) -Step $f -MaxStep $OnDemandFeatures.count -Outhost $results = Remove-WindowsCapability -Name $Feature -Online -ErrorAction Stop if ($results.RestartNeeded -eq $true) { Write-LogEntry ("Reboot is required for remving the Feature on Demand package: {0}" -f $FeatName) }}catch [System.Exception]{ Write-LogEntry -Message ("Failed to remove Feature on Demand V2 package: {0}" -f $_.Message) -Severity 3 -Outhost}#>FunctionWrite-LogEntry
{
[CmdletBinding()]
param(
[Parameter(
Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true
)]
[ValidateNotNullOrEmpty()]
[string]
$Message,
[Parameter(
Mandatory=$false,Position=2
)]
[string]
$Source='',
[parameter(
Mandatory=$false
)]
[ValidateSet(0,1,2,3,4)]
[int16]
$Severity,
[parameter(
Mandatory=$false,HelpMessage="Name of the log file that the entry will written to"
)]
[ValidateNotNullOrEmpty()]
[string]
$OutputLogFile=$Global:LogFilePath,
[parameter(
Mandatory=$false
)]
[switch]
$Outhost
)
Begin
{
[string]$LogTime= (Get-Date-Format 'HH:mm:ss.fff').ToString()
[string]$LogDate= (Get-Date-Format 'MM-dd-yyyy').ToString()
[int32]$script:LogTimeZoneBias= [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes
[string]$LogTimePlusBias=$LogTime+$script:LogTimeZoneBias
}
Process
{
# Get the file name of the source scriptTry
{
If ($script:MyInvocation.Value.ScriptName)
{
[string]$ScriptSource=Split-Path-Path $script:MyInvocation.Value.ScriptName-Leaf -ErrorAction 'Stop'
}
Else
{
[string]$ScriptSource=Split-Path-Path $script:MyInvocation.MyCommand.Definition-Leaf -ErrorAction 'Stop'
}
}
Catch
{
$ScriptSource=''
}
If (!$Severity)
{
$Severity=1
}
$LogFormat="<![LOG[$Message]LOG]!>"+"<time=`"$LogTimePlusBias`""+"date=`"$LogDate`""+"component=`"$ScriptSource`""+"context=`"$([Security.Principal.WindowsIdentity]::GetCurrent().Name)`""+"type=`"$Severity`""+"thread=`"$PID`""+"file=`"$ScriptSource`">"# Add value to log filetry
{
Out-File-InputObject $LogFormat-Append -NoClobber -Encoding Default-FilePath $OutputLogFile-ErrorAction Stop
}
catch
{
Write-Host ("[{0}] [{1}] :: Unable to append log entry to [{1}], error: {2}"-f$LogTimePlusBias,$ScriptSource,$OutputLogFile,$_.Exception.Message) -ForegroundColor Red
}
}
End
{
If ($Outhost-or$Global:OutTohost)
{
If ($Source)
{
$OutputMsg= ("[{0}] [{1}] :: {2}"-f$LogTimePlusBias,$Source,$Message)
}
Else
{
$OutputMsg= ("[{0}] [{1}] :: {2}"-f$LogTimePlusBias,$ScriptSource,$Message)
}
Switch ($Severity)
{
0 { Write-Host$OutputMsg-ForegroundColor Green }
1 { Write-Host$OutputMsg-ForegroundColor Gray }
2 { Write-Warning$OutputMsg }
3 { Write-Host$OutputMsg-ForegroundColor Red }
4 { If ($Global:Verbose) { Write-Verbose$OutputMsg } }
default { Write-Host$OutputMsg }
}
}
}
}
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[void][reflection.assembly]::loadwithpartialname('System.Windows.Forms')
[void][reflection.assembly]::loadwithpartialname('System.Drawing')
$notify=New-Object system.windows.forms.notifyicon
$notify.icon= [System.Drawing.SystemIcons]::Information
$notify.visible=$true$notify.showballoontip(10000,'Header Title','Hey look at me!!', [system.windows.forms.tooltipicon]::Info)
[System.Windows.MessageBox]::Show('Message','Title', [System.Windows.MessageBoxButton]::YesNoCancel, [System.Windows.MessageBoxImage]::Question)
$TitleBar=Split-Path-Path $PSCommandPath-Leaf
$Method1='System.Windows.Forms.MessageBox'$Method2='Microsoft.VisualBasic.Interaction.MsgBox()'$Method3='WScript.Shell.Popup()'$MessageBase="This message box is brought to you by:`n{0}`nHere are some Unicode characters:`n😀 ā ی ± ≠ 👩"$Message1=$MessageBase-f$Method1$Message2=$MessageBase-f$Method2$Message3=$MessageBase-f$Method3# Load System.Windows.Forms and enable visual stylesWrite-Output'Loading System.Windows.Forms'
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
# Enable visual styles# This command is required on Windows XP and later (yes, even on Windows 10)# Without this, your message boxes look like something form Windows 95Write-Output'Enabling visual styles'
[System.Windows.Forms.Application]::EnableVisualStyles()
<# METHOD 1: System.Windows.Forms.MessageBox https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.messagebox Comforms with visual styles To get the details of parameter types: [enum]::GetValues([System.Windows.Forms.MessageBoxButtons]) [enum]::GetValues([System.Windows.Forms.MessageBoxIcon]) To get the details of the return type: [enum]::GetValues([System.Windows.Forms.DialogResult])#>Write-Output"Demonstrating method 1: $Method1"$result= [System.Windows.Forms.MessageBox]::Show($'Testing',$TitleBar,'OKCancel')
Write-Output$resultAdd-Type-AssemblyName System.Windows.Forms
$MessageBoxIcon= [System.Windows.Forms.MessageBoxIcon]::Warning
$MessageBoxButtons= [System.Windows.Forms.MessageBoxButtons]::YesNo
$Result= [System.Windows.Forms.MessageBox]::Show('','Warning',$MessageBoxButtons,$MessageBoxIcon)
<# METHOD 2: Microsoft.VisualBasic.Interaction.MsgBox() https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.interaction.msgbox Comforms with visual styles To get the details of parameter type: [enum]::GetValues([Microsoft.VisualBasic.MsgBoxStyle]) To get the details of the return type: [enum]::GetValues([Microsoft.VisualBasic.MsgBoxResult])#>Write-Output"Demonstrating method 2: $Method2"
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$result= [Microsoft.VisualBasic.Interaction]::MsgBox($Message2,'OKOnly,Information',$TitleBar)
Write-Output$result<# METHOD 3 (DEPRECATED): WScript.Shell.Popup() https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/x83z1d9f(v=vs.84) Not affected by visual styles (Looks like something from Windows 95!)#>Write-Output"Demonstrating method 3: $Method3"$MsgBox=New-Object-ComObject wscript.shell
$result=$MsgBox.Popup($Message3,0,$TitleBar,0-bor64)
Write-Output$result
<#The Math class contains various basic math operationsYou can execute methods like round a valueAnother operation is square rootIt even contains fields like Pi#>
System.TimeZoneInfo
# The TimeZoneInfo class contains information about time zones on the system. You can use it to do things like look up time zones
[TimeZoneInfo]::GetSystemTimeZones()
# You can also convert a DateTime object to another time zone$Date=Get-Date$TimeZoneInfo= [TimeZoneInfo]::GetSystemTimeZones() |Where-Object Id -Eq'Chatham Islands Standard Time'
System.UriBuilder
# The UriBuilder class is used to compose URIs without string concatenation. It ensures that you can produce valid URIs without having to worry about back slashes and encoding$Builder= [UriBuilder]::new()
$Builder.Host="www.google.com"$Builder.Scheme="https"$Builder.Port=443$Builder.UserName="adam"$Builder.Password="SuperSecret"$Builder.Uri.AbsoluteUri
System.Collections.ArrayList
# The ArrayList class is actually used within the PowerShell engine a little bit and you'll see it show up occasionally. It's a useful class for quickly adding items to an array rather than the += operator# If you're collecting many thousands of items, you may want to use an array list instead of an array$ArrayList= [System.Collections.ArrayList]::new()
1..1000|ForEach-Object { $ArrayList.Add($_) } |Out-Null$ArrayList
System.ComponentModel.Win32Exception
# The Win32Exception is a good utility class for deducing what a Win32 error may mean. Try casting an exception code to the exception class to see the description of the code
[System.ComponentModel.Win32Exception]0x00005
System.IO.FileSystemWatcher
# The FileSystemWatcher class can be used to watch for changes to the file system. You can define filters for types of changes and also file extensions# This example creates a file system watcher that watches the desktop for text files being created$FileSystemWatcher= [System.IO.FileSystemWatcher]::new("C:\users\adamr\desktop","*.txt")
$FileSystemWatcher.IncludeSubDirectories=$falseRegister-ObjectEvent$FileSystemWatcher Created -SourceIdentifier FileCreated -Action {
$Name=$Event.SourceEventArgs.Name$Type=$Event.SourceEventArgs.ChangeTypeWrite-Host"'$Name' = $Type"
}
System.IO.Path
# The Path class is handy for inspecting, creating and dealing with cross-platform paths# Unlike Join-Path, the Path class can including more than two paths when joining them# It's also useful because it works cross-platform and will create paths that work on Unix and Windows systems# It also helps when trying to determine whether a path is relative or absolute
System.IO.StreamReader
# Many types in .NET will return a stream. Streams are typically used when reading large data sets or data that isn't all available at once. The StreamReader class is helpful for translating these streams into strings. This is particularly useful when reading web request bodies in Windows PowerShelltry
{
Invoke-RestMethod<http://localhost:5000/throws>
}
catch
{
[System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream()).ReadToEnd()
}
System.Net.Dns
# The Dns class is useful for invoking the DNS client from .NET. You can use it to resolve IP Address and host names
System.Net.IPAddress
# The IPAddress class is useful for dealing with IPv4 and IPv6 addresses. You can use it to parse and inspect addresses$Address= [System.Net.IpAddress]::Parse("127.0.0.1")
$Address.AddressFamily
System.Reflection.Assembly
# The Assembly class can be used to load assemblies from the file system or even from base64 strings encoded in the PowerShell script
System.Runtime.InteropServices.RuntimeInformation
# Since PowerShell is now cross-platform, it's useful to understand the platform that the script is running within. There are some variables available like $IsLinux and $IsWindows to determine this information but this class provides even more info
System.Security.SecureString
# The SecureString class is commonly used with PSCredential. It's not considered secure, especially on Unix systems, but it's still used quite often# SecureString has a bit of a strange API and you need to append characters to it to produce a new one$SS= [System.Security.SecureString]::new()
"Hello!".ToCharArray() |ForEach-Object { $SS.AppendChar($_) }
System.Text.Encoding
# The Encoding class is useful when dealing with conversion between the Roman alphabet and other alphabets. It’s also great for emojis# You can also decode byte arrays to text$Bytes= [System.Text.Encoding]::Ascii.GetBytes("Hello!")
[System.Text.Encoding]::Unicode.GetString($Bytes)
System.Text.StringBuilder
# The StringBuilder class can be used to create dynamic strings without much overhead. Strings in .NET are immutable which means that lot's of resources are required to perform string operations# You can use the methods of the this class to quickly perform operations like concatenations# See the below performance differences between standard string concatenation and string concatenation with StringBuilderMeasure-Command {
$Str=""for($i=0; $i-lt10000000; $i++)
{
$Str+="Str-{0}"-f$_
}
$Str
}
# Did not return after 30 minutesMeasure-Command {
$SB= [System.Text.StringBuilder]::new()
for($i=0; $i-lt10000000; $i++)
{
$SB.AppendFormat("Str-{0}",$i)
}
$SB.ToString()
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 9
Milliseconds : 960
Ticks : 99602440
TotalDays : 0.000115280601851852
TotalHours : 0.00276673444444444
TotalMinutes : 0.166004066666667
TotalSeconds : 9.960244
TotalMilliseconds : 9960.244# While basic concatenation will show about a 20% improvement with StringBuilder, if you use something like the script above, where formatting is involved, you'll notice an immense improvement
As you can see the selected type parameter was not beautiful aligned by the dividing dash/pipe to the message, which makes reading a bit harder in my opinion. So I decided to work with padding. Padding in a string as Method can add one or multiple extra characters to the left or the right of the string, like:
back to the starting topic my function looked a way like this:
functionWrite-Log {
[CmdletBinding()]
param (
[string] $Message,
[Validateset("Warning","Info","Error","Success")]
$Type
)
begin {
}
process {
Write-Host"$(Get-Date-format 'dd.MM.yyyy')|$(Get-Date-format 'hh:mm:ss')|$Type|$Message"
}
end {
}
}
And if we now would align the divider properly, we have to find out what is the length of the longest string in the $type parameter to add dynamically whitespaces via padding. My attempt looks like this:
Keep running a command until pressing CTRL-C/Break.
Use Out-GridView to display and filter results.
Count the number of items found
When searching for items, users, or folders, for example, you sometimes just need to count them. You can copy/paste the results in notepad and see how many lines but you can also use this
You can wrap your command with parentheses and use the ‘(‘ and ‘)’ signs around it and add ".count" behind it. This way the output will not be shown on screen but it will just show you the amount found
Finding the cmdlet that you need
Sometimes you want to search for a cmdlet, but you're not sure if it exists or how it's formatted, you can search for it using
C:\Users\HarmV>Get-Help*json*
Name Category Module Synopsis
--------------------------ConvertFrom-Json Cmdlet Microsoft.PowerShell.Uti… …
ConvertTo-Json Cmdlet Microsoft.PowerShell.Uti… …
Test-Json Cmdlet Microsoft.PowerShell.Uti… …
ConvertTo-AutopilotConfiguration… Function WindowsAutoPilotIntune …
Write-M365DocJson Function M365Documentation …
Update-M365DSCExchangeResourcesS… Function Microsoft365DSC …
New-M365DSCConfigurationToJSON Function Microsoft365DSC …
Update-M365DSCSharePointResource… Function Microsoft365DSC …
Update-M365DSCResourcesSettingsJ… Function Microsoft365DSC …
C:\Users\HarmV>Get-Help*json* Name Category Module Synopsis --------------------------ConvertFrom-Json Cmdlet Microsoft.PowerShell.Uti… … ConvertTo-Json Cmdlet Microsoft.PowerShell.Uti… … Test-Json Cmdlet Microsoft.PowerShell.Uti… … ConvertTo-AutopilotConfiguration… Function WindowsAutoPilotIntune … Write-M365DocJson Function M365Documentation … Update-M365DSCExchangeResourcesS… Function Microsoft365DSC … New-M365DSCConfigurationToJSON Function Microsoft365DSC … Update-M365DSCSharePointResource… Function Microsoft365DSC … Update-M365DSCResourcesSettingsJ… Function Microsoft365DSC …
C:\Users\HarmV>Get-Help*json*
Name Category Module Synopsis
--------------------------ConvertFrom-Json Cmdlet Microsoft.PowerShell.Uti… …
ConvertTo-Json Cmdlet Microsoft.PowerShell.Uti… …
Test-Json Cmdlet Microsoft.PowerShell.Uti… …
ConvertTo-AutopilotConfiguration… Function WindowsAutoPilotIntune …
Write-M365DocJson Function M365Documentation …
Update-M365DSCExchangeResourcesS… Function Microsoft365DSC …
New-M365DSCConfigurationToJSON Function Microsoft365DSC …
Update-M365DSCSharePointResource… Function Microsoft365DSC …
Update-M365DSCResourcesSettingsJ… Function Microsoft365DSC …
By using the Get-Help cmdlet you can search using wildcard through all your installed modules, it will show you if it's a cmdlet or a function and in which module it's present
Display your command-line history
You can browse through your previous commands using the up-and-down arrow, but you can also show it using:
C:\Users\HarmV>get-history12.910Get-ChildItem-Path c:\temp -Filter *.exe -Recurse
20.172 (Get-ChildItem-Path c:\temp -Filter*.exe -Recurse).count
C:\Users\HarmV>get-history Id Duration CommandLine ---------------------12.910Get-ChildItem-Path c:\temp -Filter *.exe -Recurse 20.172 (Get-ChildItem-Path c:\temp -Filter*.exe -Recurse).count 30.036Get-History43.396 help *history*51.116 help *json*61.190Get-Help*json*
C:\Users\HarmV>get-history
Id Duration CommandLine
---------------------12.910Get-ChildItem-Path c:\temp -Filter *.exe -Recurse
20.172 (Get-ChildItem-Path c:\temp -Filter*.exe -Recurse).count
30.036Get-History43.396 help *history*51.116 help *json*61.190Get-Help*json*
But that's just the history from your current session, the complete command-line history is saved in your profile in a text file. You can retrieve the path by using:
In this text file, you will find the complete history of every command you entered, my file is 14921 lines long
Searching your command-line history
In the chapter above I showed you how to retrieve your history. If you want to search your history without opening the text file, you can use CTRL+R and start typing a part of the command that you are searching for:
I started typing “Get-Azureadgroupm” and it retrieve it from my command-line history and it highlights the part found.
Keep running a command until pressing CTRL-C/Break
I'm a bit impatient sometimes and want to check a status a lot but don’t want to repeat the same command the whole time, you can use a while $true loop to keep on executing a command until you stop it by typing CTRL-C. For example:
while ($true) {
Clear-HostGet-DateGet-MoveRequest|Where-Object {$_.Status-ne"Suspended"-and$_.Status-ne"Failed"}
Start-Sleep-Seconds 600
}
In this example, the screen is cleared, and the date is shown so that you know from what timestamp the Get-MoveRequest (Exchange migration cmdlet) is. It will wait 10 minutes and keep on repeating the steps until you type CTRL-C to break it. You can use any command between the curly brackets and interval time you want.
Use Out-GridView to display and filter results
If PowerShell_ISE is installed on your system you can use the Out-GridView cmdlet to output results in a pop-up window with a filter field. You can do this by running:
Get-ChildItem-Path c:\temp -Recurse |Out-GridView
This will output all files in c:\temp and you can then filter the results by typing a few letters of the word you want to search for.
Every script I write has variables in it, but there are different types of variables. This short blog post will show a few types you can use in your scripts.
You can store all types of values in PowerShell variables. For example, store the results of commands and elements used in commands and expressions, such as names, paths, settings, and values.
A variable is a unit of memory in which values are stored. In PowerShell, variables are represented by text strings that begin with a dollar sign ($), such as $a, $process, or $my_var.
Variable names aren't case-sensitive and can include spaces and special characters.
Array
This is something that I use a lot. An array is a list of items that you can use, for example:
$files=Get-ChildItem-Path d:\temp -Filter *.ps1
$files=Get-ChildItem-Path d:\temp -Filter*.ps1
$files=Get-ChildItem-Path d:\temp -Filter *.ps1
This will search for all*.ps1 files in d:\temp and store the found items in the $files array. You can check what type the variable is by adding ".GetType()" behind it. This looks like this:
The BaseType tells you that it's an Array, and the contents of the Array can be listed by running $files in our example:
If you want to select a specific item from the array, for example, the second one, you can use: (It starts counting at zero, so the second item is one )
C:\Users\HarmV>$files[1]
Mode LastWriteTime Length Name
----------------------------a---25-2-202214:30261 ﲵ Activation.ps1
C:\Users\HarmV>$files[1] Directory: D:\Temp Mode LastWriteTime Length Name ----------------------------a---25-2-202214:30261 ﲵ Activation.ps1
C:\Users\HarmV>$files[1]
Directory: D:\Temp
Mode LastWriteTime Length Name
----------------------------a---25-2-202214:30261 ﲵ Activation.ps1
You can create an array with your own items by running this:
$array=@( "Item 1""Item 2""Item 3" )
$array=@(
"Item 1""Item 2""Item 3"
)
It's basically system variables you can use in scripts so that you don't hardcode specific paths. It might be that c:\users\public is remapped to d:\users\public, and your scripts will fail if you don't use the $env:public variable.
Hash table
A hash table is a data structure of key and value pairs. You can create one using the following:
C:\Users\HarmV>$hashtable
C:\Users\HarmV>$hashtable Name Value --------- MacOS Apple iOS Apple Windows Microsoft Android Google
C:\Users\HarmV>$hashtable
Name Value
---------
MacOS Apple
iOS Apple
Windows Microsoft
Android Google
You can search for a certain value by using the following to get all values that contain Apple:
C:\Users\HarmV>$hashtable.GetEnumerator().Where({$_.Value-contains'Apple'})
C:\Users\HarmV>$hashtable.GetEnumerator().Where({$_.Value-contains'Apple'}) Name Value --------- MacOS Apple iOS Apple
C:\Users\HarmV>$hashtable.GetEnumerator().Where({$_.Value-contains'Apple'})
Name Value
---------
MacOS Apple
iOS Apple
Int32/64
You can store numbers in variables, for example:
C:\Users\HarmV>$number=1
C:\Users\HarmV>$number.GetType()
IsPublic IsSerial Name BaseType
----------------------------
True True Int32 System.ValueType
C:\Users\HarmV>$number=1 C:\Users\HarmV>$number.GetType() IsPublic IsSerial Name BaseType ---------------------------- True True Int32 System.ValueType
C:\Users\HarmV>$number=1
C:\Users\HarmV>$number.GetType()
IsPublic IsSerial Name BaseType
----------------------------
True True Int32 System.ValueType
You can do math with this as well,for example:
C:\Users\HarmV>$number*8
C:\Users\HarmV>$number*88
C:\Users\HarmV>$number*88
You can also add 1 to an existing number variable. I use this to show progress in a script. For example:
[1/49] Found D:\Temp\365healthstatus.ps1
[2/49] Found D:\Temp\Activation.ps1
[3/49] Found D:\Temp\AdminGroupChangeReport.ps1
[4/49] Found D:\Temp\AdminGroups.ps1
[5/49] Found D:\Temp\AdminReport.ps1
[6/49] Found D:\Temp\AppleDEPProfile_Assign.ps1
[7/49] Found D:\Temp\applevpp_sync.ps1
[8/49] Found D:\Temp\BIOS_Settings_For_HP.ps1
[9/49] Found D:\Temp\bitlocker.ps1
[10/49] Found D:\Temp\bitlockerremediate.ps1
[11/49] Found D:\Temp\bitlockertest.ps1
[12/49] Found D:\Temp\calendar_events.ps1
[1/49] Found D:\Temp\365healthstatus.ps1 [2/49] Found D:\Temp\Activation.ps1 [3/49] Found D:\Temp\AdminGroupChangeReport.ps1 [4/49] Found D:\Temp\AdminGroups.ps1 [5/49] Found D:\Temp\AdminReport.ps1 [6/49] Found D:\Temp\AppleDEPProfile_Assign.ps1 [7/49] Found D:\Temp\applevpp_sync.ps1 [8/49] Found D:\Temp\BIOS_Settings_For_HP.ps1 [9/49] Found D:\Temp\bitlocker.ps1 [10/49] Found D:\Temp\bitlockerremediate.ps1 [11/49] Found D:\Temp\bitlockertest.ps1 [12/49] Found D:\Temp\calendar_events.ps1
[1/49] Found D:\Temp\365healthstatus.ps1
[2/49] Found D:\Temp\Activation.ps1
[3/49] Found D:\Temp\AdminGroupChangeReport.ps1
[4/49] Found D:\Temp\AdminGroups.ps1
[5/49] Found D:\Temp\AdminReport.ps1
[6/49] Found D:\Temp\AppleDEPProfile_Assign.ps1
[7/49] Found D:\Temp\applevpp_sync.ps1
[8/49] Found D:\Temp\BIOS_Settings_For_HP.ps1
[9/49] Found D:\Temp\bitlocker.ps1
[10/49] Found D:\Temp\bitlockerremediate.ps1
[11/49] Found D:\Temp\bitlockertest.ps1
[12/49] Found D:\Temp\calendar_events.ps1
String
A string is a simple object with a value, for example:
$string="Hello world"
This is displayed as:
C:\Users\HarmV>$string Hello world
C:\Users\HarmV>$string
Hello world
And you can see it's an object:
C:\Users\HarmV>$string.GetType()
IsPublic IsSerial Name BaseType
----------------------------
True True String System.Object
C:\Users\HarmV>$string.GetType() IsPublic IsSerial Name BaseType ---------------------------- True True String System.Object
C:\Users\HarmV>$string.GetType()
IsPublic IsSerial Name BaseType
----------------------------
True True String System.Object
You can combine two strings in your output like this:
C:\Users\HarmV>$string2="!"
C:\Users\HarmV>Write-Host$string$string2
C:\Users\HarmV>$string2="!" C:\Users\HarmV>Write-Host$string$string2 Hello world!
C:\Users\HarmV>$string2="!"
C:\Users\HarmV>Write-Host$string$string2
Hello world!
Or join two strings together like this using -join with the space separator using ""
C:\Users\HarmV>$string1="Good"
C:\Users\HarmV>$string2="Evening"
C:\Users\HarmV>$string1,$string2-join""
C:\Users\HarmV>$string1="Good" C:\Users\HarmV>$string2="Evening" C:\Users\HarmV>$string1,$string2-join"" Good Evening
C:\Users\HarmV>$string1="Good"
C:\Users\HarmV>$string2="Evening"
C:\Users\HarmV>$string1,$string2-join""
Good Evening
But you can also split a string using -split using the ";" character as a delimiter,for example:
C:\Users\HarmV>$string="Hello;world"
C:\Users\HarmV>$string-split";"
C:\Users\HarmV>$string="Hello;world" C:\Users\HarmV>$string-split";" Hello world
C:\Users\HarmV>$string="Hello;world"
C:\Users\HarmV>$string-split";"
Hello
world
This will output the string in two lines. You can combine those again by using -join:
C:\Users\HarmV>$string-split";"-join""
C:\Users\HarmV>$string-split";"-join"" Hello world
C:\Users\HarmV>$string-split";"-join""
Hello world
You can also select a certain range of characters from a string using SubString,for example:
C:\Users\HarmV>$string="Hello world"
C:\Users\HarmV>$string.SubString(0,5)
C:\Users\HarmV>$string="Hello world" C:\Users\HarmV>$string.SubString(0,5) Hello
C:\Users\HarmV>$string="Hello world"
C:\Users\HarmV>$string.SubString(0,5)
Hello
And you can search/replace words in a string using the replace method,for example:
C:\Users\HarmV>$string="Hello world"
C:\Users\HarmV>$string.Replace('Hello','Goodbye')
C:\Users\HarmV>$string="Hello world" C:\Users\HarmV>$string.Replace('Hello','Goodbye') Goodbye world
C:\Users\HarmV>$string="Hello world"
C:\Users\HarmV>$string.Replace('Hello','Goodbye')
Goodbye world
5 PowerShell Script Examples To Inspire You to Get Scripting
Creating and Updating Registry Keys and Values
Each application and operating system on your Windows computer is registered in a central location, the Windows Registry. The Windows Registry is composed of values and keys, where keys being the containers for the values.
PowerShell has many built-in commands to help you create, update and modify registry keys and values.
To make changes to the registry, listed below are three different PowerShell commands. Let's cover some examples of how each of these PowerShell cmdlets works.
New-Item- Creates new registry keys.
New-ItemProperty- Creates new registry values.
Set-ItemProperty- Changes registry key values.
The example script below defines a list of registry keys, checks to see if each key exists. If so, it then updates the registry values inside. If not, it creates the keys and then creates new registry values inside of those keys.
## Defines three registry key paths in an array$tls10='HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server','HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client'## Checks to see if all of the registry keys in the array exists$tls10check= ($tls10|Test-Path) -notcontains$false## If all of the registry keys existif ($tls10check-eq$True){
## Updates four different DWORD registry values to either 0 or 1Set-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'-name 'Enabled'-value '0'-Type 'DWORD'Set-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'-name 'DisabledByDefault'-value '1'-Type 'DWORD'Set-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client'-name 'Enabled'-value '0'-Type 'DWORD'Set-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client'-name 'DisabledByDefault'-value '1'-Type 'DWORD'
} else { ## If at least one of the registry keys do not exist## Creates the missing registry keys skipping the confirmation (Force)New-Item'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'-Force
New-Item'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client'-Force
## Creates four different DWORD registry values setting the value to either 0 or 1New-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'-name 'Enabled'-value '0'-Type 'DWORD'New-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'-name 'DisabledByDefault'-value '1'-Type 'DWORD'New-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client'-name 'Enabled'-value '0'-Type 'DWORD'New-ItemProperty-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client'-name 'DisabledByDefault'-value '1'-Type 'DWORD'
}
Starting a Windows Service (If Not Running)
Once you're done editing the registry, lets move right along to managing Windows services.Starting a Windows Service
In the below PowerShell script example, you'll see a great example of performing some comparison logic followed by an action. When run, this script will get the Status of the EventLog service. If the Status is anything but Running, it will write some text to the console and start the service.
If the service is already started, it will tell you so and perform no further actions.
## Define the service name in a variable$ServiceName='EventLog'## Read the service from Windows to return a service object$ServiceInfo=Get-Service-Name $ServiceName## If the server is not running (ne)if ($ServiceInfo.Status-ne'Running') {
## Write to the console that the service is not runningWrite-Host'Service is not started, starting service'## Start the serviceStart-Service-Name $ServiceName## Update the $ServiceInfo object to reflect the new state$ServiceInfo.Refresh()
## Write to the console the Status property which indicates the state of the serviceWrite-Host$ServiceInfo.Status
} else { ## If the Status is anything but Running## Write to the console the service is already runningWrite-Host'The service is already running.'
}
## Define the service name in a variable$ServiceName='EventLog'## Read the service from Windows to return a service object$ServiceInfo=Get-Service-Name $ServiceName## If the server is not running (ne)if ($ServiceInfo.Status-ne'Running') {
## Write to the console that the service is not runningWrite-Host'Service is not started, starting service'## Start the serviceStart-Service-Name $ServiceName## Update the $ServiceInfo object to reflect the new state$ServiceInfo.Refresh()
## Write to the console the Status property which indicates the state of the serviceWrite-Host$ServiceInfo.Status
} else { ## If the Status is anything but Running## Write to the console the service is already runningWrite-Host'The service is already running.'
}
Finding CIM/WMI Classes
CIM is a handy repository of information in Windows, and PowerShell can, by default, query it. Using a combination of CIM cmdlets, you can gather all kinds of handy information from CIM.
CIM data is broken out in CIM classes. CIM classes hold categories of Windows information. Perhaps you're looking for some hardware information and discovered that the CIM class has some variation of System in the class name. Using the Get-CimClass cmdlet, you can find all classes matching a particular pattern.
To retrieve one or more CIM classes via the Get-CimClass cmdlet, specify the ClassName parameter with the exact class name or search pattern on a PowerShell command prompt. If you don't know the whole class name, you can use a wildcard (*). Below, you'll see the command to find all CIM classes matching the pattern "Win32_*System".
Get-CimClass-ClassName Win32_*System
Querying WMI for Computer Information
Once you've found the CIM class you'd like to query, PowerShell now has another cmdlet called Get-CimInstance to help you query information from that class.
Below you'll find another great PowerShell example script, this time demonstrating a real-world case of querying CIM. This example is querying the Win32_OperatingSystem CIM class on two remote computers at once and creating a CSV file with a few select properties returned from that query.
After finding your desired -ClassName, perhaps you wish to filter the information further where the property names are separated by commas.
## Query the Win32_OperatingSystem CIM instance on both the serv1 and serv2 computersGet-CimInstance-ClassName Win32_OperatingSystem -ComputerName Serv1,Serv2 |`## Limit the output to only a few select propetiesSelect-Object-Property BuildNumber,BuildType,OSType,ServicePackMajorVersion,ServicePackMinorVersion |`## Send each CIM instance object to a CSV file called C:\Folders\Computers.csvExport-CSV C:\Folder\Computers.csv -NoTypeInformation -Encoding UTF8 -Verbose
When the script finishes, you'll find a CSV file called Computers.csv in the C:\Folder directory looking something like this:
Now that you know how to gather computer information, you can now use that information to tell whether those computers are compatible with certain applications, for example.
Manually installing software on a single computer may be doable, but if you have many computers to install software on, that task soon becomes untenable. To help out, you can create a PowerShell script to install software (if your software supports it) silently.
Perhaps you're working on a Microsoft Installer (MSI) package, and you'd like to install the software silently. You can install MSI packages via the msiexec.exe utility. This utility isn't a PowerShell command, but you can invoke it with PowerShell.
Maybe you have an MSI called package.msi in the C:\folder directory. To silently install software, you must first know what switches that installer requires to do so. To find the available switches, run the MSI and provide a /? switch. The /? should display each available parameter, as shown below.
C:\Folder\package.msi /?
Once you know the switches the installer package needs, it's now time to invoke the msiexec.exe utility. Since msiexec.exe is not a PowerShell command, you must invoke it as an external process. One of the easiest ways to invoke processes with PowerShell is using the Start-Process cmdlet.
The Start-Process has two parameters you'll need to use in this example; Name and Wait. In this below example, you'll see that Start-Process is invoking the msiexec.exe utility by using the Name parameter and waiting (Wait) for the process to finish before releasing control back to the console.
Since msiexec.exe needs a few parameters to install the software silently, the example uses the ArgumentList to provide each parameter that msiexec.exe needs to install the software silently.
## Invoke the msiexec.exe process passing the /i argument to indicate installation## the path to the MSI, /q to install silently and the location of the log file## that will log error messages (/le).Start-Process-Name 'msiexec.exe'-Wait -ArgumentList '/i "C:\Folder\package.msi" /q /le "C:\Folder\package.log"'
Handling Errors with a Try, Catch, and Finally Statement
To sum up this PowerShell script example post, let's end on a topic you can (and should) apply to any PowerShell script; error handling. Error handling "catches" unexpected errors found in your script, making your script more robust and able to execute with fewer hiccups.
Error handling is a big topic that could be explained in an entire book but let's only cover the basics; the try, catch, and finally statements. Try/catch blocks are blocks of code that PowerShell "monitors" for hard-terminating errors or exceptions.
If code inside a try/catch block produces a hard-terminating error, that exception will be "caught" by a block and specific code run against it. Once the script is finished, either returning an exception or completing successfully, PowerShell will process the finally block.
In the below example, the script creates a File Transfer Protocol (FTP) script file that connects to an FTP server, attempts to upload a file to an FTP server, and then removes the FTP script file once complete.
You'll see in the example the "functional" code has been wrapped in a try, catch and finally block. For example, if the Out-File cmdlet returns a hard-terminating error, PowerShell will catch that error and return Error: the error/exception message and then exit with a code of 1.
Once the script successfully creates the FTP script file, it then attempts to invoke the ftp.exe utility, which executes the commands inside of the FTP script file. If _that
Notice that you're creating the FTP script file via the Out-File cmdlet with the first try statement. If the try statement failed, the catch statement below would catch the errors. Then the $($_.Exception.Message) property followed by an exit 1 will end the script, displaying the error status.
But if the first try statement succeeds, the following try statement will run the generated FTP script. When the FTP script runs successfully, you will see log output with the successful connection to the FTP server and file download.
Then regardless of the results of the try and catch statements, the finally statement will run and remove the FTP script file.
## Create the try block and create any code inside.try
{
## Create the FTP script file using a here string (https://devblogs.microsoft.com/scripting/powertip-use-here-strings-with-powershell/)## If Out-File creates the FTP script, it then invokes ftp.exe to execute## the script file. $Script=@" open localhost username password BINARY CD remotefolder LCD C:\folder GET remote.file BYE"@$Script|Out-File"C:\Folder\ftp.txt"-Encoding ASCII
ftp -s:C:\folder\ftp.txt
}
catch
{
## If, at any time, for any code inside of the try block, returns a hard-terminating error## PowerShell will divert the code to the catch block which writes to the console## and exits the PowerShell console with a 1 exit code.Write-Host"Error: $($_.Exception.Message)"exit1
}
finally
{
## Regardless if the catch block caught an exception, remove the FTP script fileRemove-Item-Path "C:\folder\ftp.txt"
}
## When the code inside of the try/catch/finally blocks completes (error or not),## exit the PowerShell session with an exit code of 0exit0
If an exception is caught anywhere inside the try block, you'll see an error message ($_.Exception.Message) indicating what went wrong.
Before taking on complex stuff with PowerShell, start with the basics, like getting your system information. With PowerShell, you can quickly get the Window version via the systeminfo command.
Open your PowerShell, and run the following command to get detailed information about your system, including the operating system (OS) version.
.\\systeminfo
Below, you can see detailed information about the OS, including the current version number, 10.0.19044 N/A Build 19044.
Selecting Specific Properties to Get the Windows Version
Great! You have just retrieved detailed system information. But if you look closely, the command is short, yet the output seems a lot.
If you only want specific property values, the Get-ComputerInfo command is one of the quickest methods of getting specific system information, like your Windows version.
Run the below to get the Windows version, product name, and version of Windows Kernel and your system hardware’s Operating Hardware Abstraction (OHA).
Below, piping the Select-Object command to the Get-ComputerInfo command lets you retrieve only select properties.
Retrieving the Windows Version via the System.Environment Class
The System.Environment class also has a property called OSVersion, which contains information about the current OS.
Run the following command to call the OSVersion.Version property from the System.Environment class. The double colon (::) symbol is used to call static methods from a class.
[System.Environment]::OSVersion.Version
As you can see below, the output displays the OSVersion information as follows:
PROPERTY
VALUE
DESCRIPTION
Major
10
Stands for Windows version 10 (Windows 10).
Minor
0
There are two types of Windows releases, major and minor. Major releases are the "big" updates like the Creator update, and minor releases are smaller cumulative updates.
Windows PowerShell 5.1
Profile Path
Current User - Current Host $Home\[My ]Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Current User - All Hosts $Home\[My ]Documents\WindowsPowerShell\Profile.ps1
All Users - Current Host $PSHOME\Microsoft.PowerShell_profile.ps1
All Users - All Hosts $PSHOME\Profile.ps1
💡 The $Home variable references the current users home directory while the $PSHOME variable references the installation path of PowerShell.
PowerShell 7.x
Profile Path
Current User - Current Host - Windows $Home\[My ]Documents\Powershell\Microsoft.Powershell_profile.ps1
Current User - Current Host - Linux/macOS ~/.config/powershell/Microsoft.Powershell_profile.ps1
Current User - All Hosts - Windows $Home\[My ]Documents\Powershell\Profile.ps1
Current User - All Hosts - Linux/macOS ~/.config/powershell/profile.ps1
All Users - Current Host - Windows $PSHOME\Microsoft.Powershell_profile.ps1
All Users - Current Host - Linux/macOS /usr/local/microsoft/powershell/7/Microsoft.Powershell_profile.ps1
All Users - All Hosts - Windows $PSHOME\Profile.ps1
All Users - All Hosts - Linux/macOS /usr/local/microsoft/powershell/7/profile.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example 1: Extracts the file extension using [System.IO.Path]::GetExtension($path) and removes it from the file name using [System.IO.Path]::GetFileNameWithoutExtension($path).
# Example 2: Uses -replace '_.*', '' to remove everything after the first underscore in a string.
# Combined Example: Demonstrates both concepts together, extracting the file name without extension from a path and then cleaning it by removing everything after the first underscore.
# Example 1: Extracting and Removing the File Extension
Write-Host "File name without extension: $filenameWithoutExtension"
Write-Host "Cleaned file name: $cleanedFilename"
# Key Points
# Flexibility: Split-Path -Leaf is versatile and can handle both full file paths and filenames.
# Regex Replacement: Using -replace '\.[^.]*$', '' ensures that only the extension is removed, preserving the rest of the filename.
# These examples illustrate how Split-Path -Leaf can be used effectively to manipulate filenames and paths in PowerShell. Adjust them based on your specific scenarios and requirements.
# Explanation
# Full File Path: With $path = "C:\Users\JohnDoe\Documents\Report.docx", Split-Path -Leaf $path returns "Report.docx", which is the filename with extension.
# Remove Extension: To remove the extension from $filename, we use -replace '\.[^.]*$', ''. This regex pattern ('\.[^.]*$') matches the last dot (\.) followed by any characters that are not dots ([^.]*) until the end of the string ($), effectively removing the extension.
# Result: $filenameWithoutExtension will contain "Report", which is the filename without the .docx extension.
Write-Host "File name without extension: $filenameWithoutExtension"
# Using Split-Path -Leaf with Filenames
# Even if you only have a filename (without a path), you can still use Split-Path -Leaf to handle it. PowerShell treats the filename as if it were a leaf in the current directory structure.
# Example with filename only
$filename = "Report.docx"
# Get the filename using Split-Path -Leaf (simulating a full path)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# In PowerShell, there are three types of quotes that can be used to define strings:
# Double-quoted strings: Double-quoted strings (") allow for variable expansion and escape sequences.
"Hello, $name!"
# Single-quoted strings: Single-quoted strings (') treat the content literally, without variable expansion or escape sequences.
'Hello, $name!'
# Here-strings: Here-strings (@" "@ or @' '@) allow multiline string literals and preserve line breaks and formatting.
$message = @"
This is a multiline
string using a here-string.
It preserves line breaks and formatting.
"@
# These quotes can be used interchangeably depending on your requirements. Double-quoted strings are commonly used when you need to include variable values within the string. Single-quoted strings are useful when you want to treat the content literally without variable expansion. Here-strings are beneficial for multiline strings that require preserving line breaks and formatting.
It's useful to know how to use PowerShell variables and object properties in a double-quoted string as part of PowerShell'’'s string expansion
As a simple example we cab expand the $name variable inside the $sentence variable like so
$name = "John"
$sentence = "My name is $name"
Write-Host $sentence
output
My name is John
This is easy enough to grasp. Note that PowerShell expansion does NOT work inside single quotes like so
$name = "John"
$sentence = 'My name is $name'
Write-Host $sentence
output
My name is $name
We can use PowerShell expansion to write the PowerShell version to a variable and output it in a similar way like so
$version = $PSVersionTable.PSVersion
$sentence = "Powershell version is $version"
Write-Host $sentence
output
Powershell version is 5.1.19041.1682
However, if we wanted to inject the PowerShell version directly we can see that the following does not work
$sentence = "Powershell version is $PSVersionTable.PSVersion"
Write-Host $sentence
output
Powershell version is System.Collections.Hashtable.PSVersion
Instead, what we need to do when expanding object properties in PowerShell is to enclose it in $() like so
$sentence = "Powershell version is $($PSVersionTable.PSVersion)"
Write-Host $sentence
output
Powershell version is 5.1.19041.1682
A final alternative is to use the string format (-f) operator like so – here we use placeholders for variable names such as { 0 } { 1 } and { 2 } and after the -f parameter we specify a comma-delimited array of values to substitute in
$sentence = "Powershell version is {0}" -f $PSVersionTable.PSVersion
Write-Host $sentence
output
Powershell version is 5.1.19041.1682
A slightly more complex version might be
$sentence = "Powershell major version is {0} and minor version is {1}" -f $PSVersionTable.PSVersion.Major, $PSVersionTable.PSVersion.Minor
Write-Host $sentence
output
Powershell major version is 5 and minor version is 1
Visual Studio Code Snippets - the Definitive VS Code Snippet Guide for Beginners
If you want to track down the source file yourself, the built-in snippets live inside each individual
language extension directory. The file is located at «app
root»\resources\app\extensions\«language»\snippets\«language».code-snippets on
Windows. The location is similar for Mac and Linux.
To create the snippets file, run the 'Preferences: Configure User Snippets' command, which
opens a quickpick dialog as below. Your selection will open a file for editing.
Example
Here is a markdown snippet that comes with VS Code.
This snippet inserts a level 1 heading which wraps the markdown around the current selection (if there is one).
A snippet has the following properties:
"Insert heading level 1"is the snippet name. This is the value that is displayed in the
IntelliSense suggestion list if no description is provided.
The prefix property defines the trigger phrase for the snippet. It can be a string or an
array of strings (if you want multiple trigger phrases). Substring matching is performed on
prefixes, so in this case, typing "h1" would match our example snippet.
The body property is the content that is inserted into the editor. It is an array of strings,
which is one or more lines of content. The content is joined together before insertion.
The description property can provide more information about the snippet. It is
optional.
The scope property allows you to target specific languages, and you can supply a
comma-separated list in the string. It is optional. Of course, it is redundant for a languagespecific snippet file.
The body of this snippet has 2 tab stops and uses the variable ${TM_SELECTED_TEXT} .
Let's get into the syntax to understand this fully.
Snippet syntax
VS Code's snippet syntax is the same as the TextMate snippet syntax. However, it does not
support 'interpolated shell code' and the use of the \u transformation.
The body of a snippet supports the following features
Tab Stops
Tab stops are specified by a dollar sign and an ordinal number e.g. $1 . $1 will be the first
location, $2 will the second location, and so on. $0 is the final cursor position, which exits the
snippet mode.
For example, let's say we want to make an HTML div snippet and we want the first tab stop to be
between the opening and closing tags. We also want to allow the user to tab outside of the tags
to finish the snippet.
Then we could make a snippet like this:
Mirrored Tab Stops
There are times when you need to provide the same value in several places in the inserted text.
In these situations you can re-use the same ordinal number for tab stops to signal that you want
them mirrored. Then your edits are synced.
A typical example is a for loop which uses an index variable multiple times. Below is a JavaScript
example of a for loop.
Placeholders
Placeholders are tab stops with default values. They are wrapped in curly braces, for example
${1:default} . The placeholder text is selected on focus such that it can be easily edited.
Placeholders can be nested, like this: ${1:first ${2:second}} .
Choices
Choices present the user with a list of values at a tab stop. They are written as a commaseparated list of values enclosed in pipe-characters e.g. ${1|yes,no|} .
This is the code for the markdown example shown earlier for inserting a task list. The choices are'x' or a blank space.
Variables
There is a good selection of variables you can use. You simply prefix the name with a dollar sign
to use them, for example $TM_SELECTED_TEXT .
For example, this snippet will create a block comment for any language with today's date:
You can specify a default for a variable if you wish, like ${TM_SELECTED_TEXT:default} . If a
variable does not have a value assigned, the default or an empty string is inserted.
If you make a mistake and include a variable name that is not defined, the name of the variable is
transformed into a placeholder.
The following workspace variables can be used:
TM_SELECTED_TEXT : The currently selected text or the empty string,
TM_CURRENT_LINE : The contents of the current line,
TM_CURRENT_WORD : The contents of the word under cursor or the empty string,
TM_LINE_INDEX : The zero-index based line number,
TM_LINE_NUMBER : The one-index based line number,
TM_FILENAME : The filename of the current document,
TM_FILENAME_BASE : The filename of the current document without its extensions,
TM_DIRECTORY : The directory of the current document,
TM_FILEPATH : The full file path of the current document,
CLIPBOARD : The contents of your clipboard,
WORKSPACE_NAME : The name of the opened workspace or folder.
The following time-related variables can be used:
CURRENT_YEAR : The current year,
CURRENT_YEAR_SHORT : The current year's last two digits,
CURRENT_MONTH : The month as two digits (example '07'),
CURRENT_MONTH_NAME : The full name of the month (example 'July'),
CURRENT_MONTH_NAME_SHORT : The short name of the month (example 'Jul'),
CURRENT_DATE : The day of the month,
CURRENT_DAY_NAME : The name of day (example 'Monday'),
CURRENT_DAY_NAME_SHORT : The short name of the day (example 'Mon'),
CURRENT_HOUR : The current hour in 24-hour clock format,
CURRENT_MINUTE : The current minute,
CURRENT_SECOND : The current second,
CURRENT_SECONDS_UNIX : The number of seconds since the Unix epoch.
The following comment variables can be used. They honour the syntax of the document's
language:
BLOCK_COMMENT_START : For example, in HTML,
LINE_COMMENT : For example, // in JavaScript.
Transformations
Transformations can be applied to a variable or a placeholder. If you are familiar with regular
expressions (regex), most of this should be familiar.
The format of a transformation is: ${«variable or placeholder»/«regex»/«replacement
string»/«flags»} . It is similar to String.protoype.replace() in JavaScript. The "parameters" do
the following:
«regex» : This is a regular expression that is matched against the value of the variable or
placeholder. The JavaScript regex syntax is supported.
«replacement string» : This is the string you want to replace the original text with. It can
reference capture groups from the «regex» , perform case formatting (using the special
functions: /upcase , /downcase , and /capitalize ), and perform conditional insertions.
See TextMate Replacement String Syntax for more in-depth information.
«flags» : Flags that are passed to the regular expression. The JavaScript regex flags can
be used:
g : Global search,
i : Case-insensitive search,
m : Multi-line search,
s : Allows . to match newline characters,
u : Unicode. Treat the pattern as a sequence of Unicode code points,
y : Perform a "sticky" search that matches starting at the current position in the
target string.
To reference a capture group, use $n where n is the capture group number. Using $0 means
the entire match.
This can be a bit confusing since tab stops have the same syntax. Just remember that if it is
contained within forward slashes, then it is referencing a capture group.
The easiest way to understand the syntax fully is to check out a few examples.
| SNIPPET BODY | INPUT | OUTPUT| EXPLANATION |
| ------------ | ----- | ----- | ----------- |
| ["${TM_SELECTED_TEXT/^.+$/• $0/gm}"] | line1 | line2 | • line1 | • line2 | Put a bullet point before each non-empty line of the selected text.
| ["${TM_SELECTED_TEXT/^(\\w+)/${1:/capitalize}/}"] | the cat is on the mat. | The cat is on the mat. | Capitalize the first word of selected text.
| ["${TM_FILENAME/.*/${0:/upcase}/}"] | example.js | EXAMPLE.JS | Insert the filename of the current file uppercased.
| ["[","${CLIPBOARD/^(.+)$/'$1',/gm}","]"] | line1 line2 | ['line1', 'line2',] | Turn the contents of the clipboard into a string array. | Each non-empty line is an element.
As you can see from the second example above, metacharacter sequences must be escaped, for
example insert \w for a word character.
Placeholder Transformations
Placeholder transforms do not allow a default value or choices! Maybe it is more suitable to
call them tab stop transformations.
The example below will uppercase the text of the first tab stop.
You can have a placeholder and perform a transformation on a mirrored instance. The
transformation will not be performed on the initial placeholder.?
Would you use this behaviour somewhere?I find it confusing initially, so it may have the same
affect on others.
You define a shortcut by specifying the key combination you want to use, the command ID, and
an optional when clause context for the context when the keyboard shortcut is enabled.
Through the args object, you can target an existing snippet by using the langId and name
properties. The langId argument is the language ID of the language that the snippets were
written for. The name is the snippet's name as it is defined in the snippet file.
You can define an inline snippet if you wish using the snippet property.
You can use the Keyboard Shortcuts UI also, but it does not have the ability to add a new shortcut.
Another downside of the UI is that it does not show the args object, which makes it more
In this example, we define a function called "New-Directory" that creates a new directory at the
specified path. We've included the
[CmdletBinding()]
attribute at the beginning of
the function definition, which enables a set of common parameters, including -WhatIf.
We've also added the
[SupportsShouldProcess()]
parameter to the CmdletBinding attribute. This enables the function to support the What-If feature. The $PSCmdlet.ShouldProcess() method is used to determine whether to execute the code or not, based on whether the -WhatIf parameter was used or not.
When the function is called, you can use the -WhatIf parameter to preview the changes that would occur if the function were to run:
PS C:\>New-Directory-Path C:\Temp -WhatIf
What if: Performing the operation "Create directory" on target "C:\Temp".
This shows that if the New-Directory function were to run, it would create a new directory at C:\Temp. However, because we used the -WhatIf parameter, it only shows us a preview of what would happen, without actually executing the command.
If you were to run the command without the -WhatIf parameter, it would create the directory:
One of the simplest types of while loop is the single condition type. Unlike the If statement, this type of loop executes a set of given commands so long as a specified condition evaluates to true. When a 'False' is returned, the loop breaks, and the execution continues with the rest of the script
Below is the syntax for a single condition while loop
The first thing you will notice is the use of parentheses. The condition must be enclosed in parentheses, while the code block is a set of PowerShell commands that executes while the condition is true
The second thing to note is that the condition must return a Boolean value, which evaluates to either True or False
while (condition)
{
# Code block to execute
}
In the code below, the if statement checks the value of $i. If the value of $i is 5, the continue keyword skips over the remaining code in the loop and continues with the next iteration. If the value of $i is 8, the break keyword exits the loop and continues with the rest of the script. Otherwise, the while loop prints (Write-Host) and increments the $i variable's value by 1
# Declares an $array of 10 items$array=1..10# Declares the $i variable with the initial value of 0$i=0# Sets the While look to execute until the condition is metwhile ($i-lt$array.Count)
{
# Checks if $i equals 5if ($i-eq5)
{
# If yes, increment $i by 1$i++# Continue with the next iterationcontinue
}
# Checks if $i equals 8if ($i-eq8)
{
# If yes, break the While loop and continue with the rest of the scriptbreak
}
# Prints the current value of $iWrite-Host"Processing item $i"# Increments $1 by 1$i++
}
Executing PowerShell While Loop with Built-in Variables ($true/$false)
The previous example, using conditions in the while loop, works fine. But you can also use PowerShell's built-in variables, such as $true and $false, to create a While loop
The syntax below executes until $true is no longer $true. In other words, the loop runs forever. But you must always include a way to break out of an infinite loop. Otherwise, you are forever stuck. You will learn more about how to break from a loop using break and continue later in this tutorial
while($true)
{
# Code block to execute
}
Execute the code block below, which runs forever, printing the value of $i to the console
# Declares the $i variable with the initial value of 0$i=0# Sets the While loop to run while the condition is $truewhile($true)
{
# Increments the $i value by 1$i++# Prints the $i variable's current valueWrite-Host$i
}
Now, press Ctrl+C to break out of the loop. This loop consumes many system resources, so be careful when using them
Executing Multi-Condition While Loops
In addition to single-condition while loops, you can also create multi-condition while loops. Like the single condition While loop, the conditions must return a Boolean value, either True or False
Below is the syntax for a multi-condition while loop, similar to the syntax for a single-condition while loop. The main difference is that you can include multiple conditions separated by the following operators
AND (-and)- Both conditions must be true
OR (-or) (Either condition can be true)
# AND operatorwhile (condition1 -AND condition2)
{
# Code block to execute
}
# OR operatorwhile (condition1 -OR condition2)
{
# Code block to execute
}
Execute the code below, which loops while $val is not equal (-ne) to 3 -and $i is not equal (-ne) to 5
When both variables' values reach their respective conditions, the loop breaks, and execution continues with the rest of the script
# Declares the $val and $i variables with initial values of 0$val=0$i=0# Sets the While loop to execute until $val is equal to 3 and $i is equal to 5while ($val-ne3-and$i-ne6)
{
# Increments $val by 1$val++# Increments $i by 2$i+=2# Prints $val and $i variables' current valueWrite-Host"$val, $i"
}
Executing a while loop with AND operator
Now, execute the below code, which asks the user for their age, stored in the $age variable
If the user enters a number either less than (-lt) 1 or is not a number (-nomatch), the user is prompted again to enter a valid number. This behavior is useful in giving users multiple chances to enter valid input
# Prompts the users to enter their age$age=Read-Host"Please Enter Your Age"# Sets the While loop to run until the user provides a valid inputwhile ($age-notmatch"\\d+"-or$age-lt1)
{
# Re-prompts the user to enter a valid age number$age=Read-Host"Please Enter Your Valid Age"
}
# Prints the valid age inputWrite-Host"Your age is $age
In the output below, you can see the user was prompted to enter their age three times, as follows:
The first time, the user entered ten, which is not a number.
The second time, the user entered 0, which is below 1.
The third time, the user entered 10, which is a valid age.
Using BREAK and CONTINUE Keywords in While Loops
You have seen how while loops add flexibility to your PowerShell script. But to better control, your While loops’ execution, add the break and continue keywords.
For example, if you only want to process a certain number of items in an array, you can use the BREAK keyword to exit the loop when the desired number of items has been processed.
These keywords function as follows:
KEYWORD
FUNCTION
break
Immediately exits the loop and continues execution with the rest of the script.
continue
Skips over the remaining code block in the current iteration of the loop and continues with the next iteration.
Execute the code below, which loops through an array of 10 items.
In the code below, the if statement checks the value of $i. If the value of $i is 5, the continue keyword skips over the remaining code inthe loop and continues with the next iteration. If the value of $i is 8, the break keyword exits the loop and continues with the rest of the script.
Otherwise, the while loop prints (Write-Host) and increments the $i variable's value by 1.
# Declares an $array of 10 items$array=1..10# Declares the $i variable with the initial value of 0$i=0# Sets the While look to execute until the condition is metwhile ($i-lt$array.Count)
{
# Checks if $i equals 5if ($i-eq5)
{
# If yes, increment $i by 1$i++# Continue with the next iterationcontinue
}
# Checks if $i equals 8if($i-eq8)
{
# If yes, break the While loop and continue with the rest of the scriptbreak
}
# Prints the current value of $iWrite-Host"Processing item $i"# Increments $1 by 1$i++
}
As you can see in the output below, the loop skipped over the fifth and eighth items in the array. The while loop processed all other items in the array and exited after reaching the eighth item.
Limiting the Time a While Loop Runs
Typically, you may want to limit the amount of time a loop runs. Perhaps you are trying to connect to a remote server. If so, you can give the server time to respond before timing out and exiting the loop by using the Start-Sleep cmdlet inside your while loop.
The Start-Sleep cmdlet pauses the execution of the script for a specified amount of time.
Execute the code below to get and store the current date and time Get-Date in the $startTime variable. The while loop runs while the current date/time is less than 10 seconds from the value stored in $startTime.
As the while loop runs, a message prints while the Start-Sleep cmdlet pauses the execution of the script for 1 second.
The code block below is just a boilerplate for what you would actually use in practice. You can put more in the code inside the loop as needed.
# Get and store the current date/time$startTime=Get-Date# Sets the While loop to run while the current date/time is less than 10 seconds# from the value stored in $startTime.while ((Get-Date) -lt ($startTime.AddSeconds(10)))
{
# Prints a messageWrite-Host"Waiting for server to respond..."# Pauses the script for one secondStart-Sleep-Seconds 1
}