When scripting or just using the interactive shell, a handful of needs arise that are simple but useful: measuring commands, getting random numbers, and more.
The Get-Date command generates rich object-based
output, so you can use its result for many date-related tasks. For
example, to determine the current day of the week:
PS > $date = Get-Date PS > $date.DayOfWeek Sunday
For more information about the Get-Date cmdlet, type Get-Help Get-Date.
For more information about working with classes from the .NET Framework, see the section called “Work with .NET Objects”.
To measure the duration of a command, use the
Measure-Command cmdlet:
PS > Measure-Command { Start-Sleep -Milliseconds 337 }
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 339
Ticks : 3392297
TotalDays : 3.92626967592593E-06
TotalHours : 9.42304722222222E-05
TotalMinutes : 0.00565382833333333
TotalSeconds : 0.3392297
TotalMilliseconds : 339.2297In interactive use, it is common to want to
measure the duration of a command. An example of this might be running a
performance benchmark on an application you've developed. The Measure-Command cmdlet makes this easy to do.
Because the command generates rich object-based output, you can use its
output for many date-related tasks. See the section called “Work with .NET Objects” for more information.
If the accuracy of a command measurement is important, general system activity can easily influence the timing of the result. To improve accuracy, a common techique is to repeat the measurement many times, ignore the outliers (the top and bottom 10 percent), and then average the remaining results. Example 8.1, “Measure-CommandPerformance.ps1” implements this technique.
Example 8.1. Measure-CommandPerformance.ps1
<#
.SYNOPSIS
Measures the average time of a command, accounting for natural variability by
automatically ignoring the top and bottom ten percent.
.EXAMPLE
PS > .\Measure-CommandPerformance.ps1 { Start-Sleep -m 300 }
Count : 30
Average : 312.10155
(...)
#>
param(
[Scriptblock] $command,
[int] $iterations = 30)
Set-StrictMode -Version Latest
$buffer = [int] ($iterations * 0.1)
$totalIterations = $iterations + (2 * $buffer)
$results = 1..$totalIterations | Foreach-Object { Measure-Command $command }
$middleResults = $results | Sort TotalMilliseconds |
Select -Skip $buffer -First $iterations
$middleResults | Measure-Object -Average TotalMilliseconds
I'd suggest starting parameter names with a capital letter. This makes tab-completing the parameter names comparable with built-in cmdlets. Since PowerShell doesn't do this capitalization on its own the result would then be
PS Home:\> Measure-CommandPerformance -c<TAB>
PS Home:\> Measure-CommandPerformance -command
instead of
PS Home:\> Measure-CommandPerformance.ps1 -Command
This is probably very minor, but it'd be a nice touch.
Thanks, I definitely agree. I need to do a review of the included scripts for consistency, and this is one goal.
For more information about the Measure-Command cmdlet, type Get-Help Measure-Command.
Use the Get-Clipboard and
Set-Clipboard scripts.
Example 8.2. Get-Clipboard.ps1
<#
.SYNOPSIS
Retrieve the text contents of the Windows Clipboard.
.EXAMPLE
PS >Get-Clipboard
Hello World
#>
PowerShell -NoProfile -STA -Command {
Add-Type -Assembly PresentationCore
[Windows.Clipboard]::GetText()
}
Example 8.3. Set-Clipboard.ps1
<#
.SYNOPSIS
Sends the given input to the Windows clipboard.
.EXAMPLE
PS >dir | Set-Clipboard
This example sends the view of a directory listing to the clipboard
.EXAMPLE
PS >Set-Clipboard "Hello World"
This example sets the clipboard to the string, "Hello World".
#>
param(
[Parameter(ValueFromPipeline = $true)]
[object[]] $inputObject
)
begin
{
$objectsToProcess = @()
}
process
{
$objectsToProcess += $inputObject
}
end
{
$objectsToProcess | PowerShell -NoProfile -STA -Command {
Add-Type -Assembly PresentationCore
$clipText = ($input | Out-String -Stream) -join "`r`n"
[Windows.Clipboard]::SetText($clipText)
}
}
While Windows includes a command-line utility
(clip.exe) to place text in the Windows clipboard, it
doesn't support direct input (i.e.: clip.exe "Hello
World"), and doesn't have a corresponding utility to retrieve
the contents to the Windows clipboard.
The Set-Clipboard and
Get-Clipboard scripts given in the solution resolve
both of these issues.
Both rely on the
System.Windows.Clipboard class, which has a special
requirement that it be run from an application in Single
Threaded Apartment (STA) mode. To support that, the scripts
launch a new instance of PowerShell in this mode. For more information
about interacting with this type of class, see the section called “Interact With UI Frameworks and STA Objects”.
Maybe this should add a note that System.Windows.Clipboard is part of .NET 3 which is not required for PowerShell 2. In fact, Windows XP users with only .NET 2 won't be able to use this solution and probably might wonder why.
Alternatively the two scripts could be provided with System.Windows.Forms.Clipboard, though I don't know whether the code would be similarly short or not. Proper clipboard support usually is quite a lot code, as far as I have seen.
For more information about working with classes from the .NET Framework, see the section called “Work with .NET Objects”.
You want to generate a random number, or pick a random element from a set of objects.
Call the Get-Random cmdlet
to generate a random positive integer.
Get-Random
Use
the -Minimum and -Maximum
parameters to generate a number between
Minimum and up to (but not including)
Maximum.
Get-Random -Minimum 1 -Maximum 21
Use the -InputObject parameter (or simple pipeline input) to pick a random element from a list.
PS > $suits = "Hearts","Clubs","Spades","Diamonds"
PS > $faces = (2..10)+"A","J","Q","K"
PS > $cards = foreach($suit in $suits) { foreach($face in $faces) { "$face of $suit" } }
PS > $cards | Get-Random
A of Spades
PS > $cards | Get-Random
2 of ClubsThe Get-Random cmdlet
solves the problems usually associated with picking random numbers or
random elements from a collection: scaling and
seeding.
Most random number generators only generate numbers between 0 and 1. If you need a number from a different range, you have to go through a separate scaling step to map those numbers to the appropriate range. While not terribly difficult, it's a usability hurdle that requires more than trivial knowledge to do properly.
Actually, most PRNGs generate integer values, only very few directly yield floating-point numbers. Many random-number generation frameworks break generating numbers down to a bit stream that is then transformed the numbers as needed. Creating a double value from that is trivial then: Just grab 53 bits and put them into the mantissa.
Scaling such a number to get integer results gives rise to aliasing artifacts, though, which may result in an uneven distribution.
Ensuring that the random number generator picks good random numbers is a different problem entirely. All general-purpose random number generators use mathematical equations to generate their values. They make new values by incorporating the number they generated just before that—a feedback process that guarantees evenly-distributed sequences of numbers. Maintaining this internal state is critical, as restarting from a specific point will always generate the same number—not very random at all! You lose this internal state every time you create a new random number generator.
“just before that - a feedback process” → “just before that—a feedback process”
“the same number - not very random at all!” → “the same number—not very random at all!”
(em dashes)
To create their first value, generators need a
random number seed. While you can supply a seed
directly (through the -SetSeed parameter) for testing
purposes, it is usually derived from the system time.
Unless you re-use the same random number generator, this last point usually leads to the downfall of realistically random numbers. When you generate them quickly, you create new random number generators that are likely to have the same seed:
PS > 1..10 | Foreach-Object { (New-Object System.Random).Next(1, 21) }
20
7
7
15
15
11
11
18
18
18The Get-Random cmdlet saves you from
this issue by internally maintaining a random number generator and its
state:
PS > 1..10 | Foreach-Object { Get-Random -Min 1 -Max 21 }
20
18
7
12
16
10
9
13
16
14For more information about working with classes from the .NET Framework, see the section called “Work with .NET Objects”.
I would say that Count is another very useful parametr of Get-Random. So for example last code can be also in this form:
1..20 | Get-Random -Count 10
or another example:
Get-Process | Get-Random -Count 5
When working at the command line, you might want to launch a program that is normally found only on your Start menu. While you could certainly click through the Start menu to find it, you could also search the Start menu with a script, as shown in Example 8.4, “Search-StartMenu.ps1”.
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
When viewing or demonstrating scripts, syntax highlighting makes the information immensely easier to read. Viewing the scripts in the PowerShell Integrated Scripting Environment (ISE) is the most natural (and powerful) option, but you might want to view them in the console as well.
In addition to basic syntax highlighting, other useful features during script review are line numbers, and highlighting ranges of lines. Range highlighting is especially useful when discussing portions of a script in a larger context.
Example 8.5, “Show-ColorizedContent.ps1” enables all of these scenarios by providing syntax highlighting of scripts in a console session.
Figure 8.1. Sample colorized content

Parameter HighlightRange is with capital 'H' but in script is with lower 'h'
In addition to having utility all on its own,
Show-ColorizedContent.ps1 demonstrates how to use
PowerShell's Tokenizer API, as introduced in the section called “Parse and Interpret PowerShell Scripts”. While many of the
techniques are specific to syntax highlighting in a PoweShell console,
many more apply to all forms of script manipulation.
Example 8.5. Show-ColorizedContent.ps1
<#
.SYNOPSIS
Displays syntax highlighting, line numbering, and range highlighting for
PowerShell scripts.
.EXAMPLE
PS >Show-ColorizedContent Invoke-MyScript.ps1
001 | function Write-Greeting
002 | {
003 | param($greeting)
004 | Write-Host "$greeting World"
005 | }
006 |
007 | Write-Greeting "Hello"
.EXAMPLE
PS >Show-ColorizedContent Invoke-MyScript.ps1 -highlightRange (1..3+7)
001 > function Write-Greeting
002 > {
003 > param($greeting)
004 | Write-Host "$greeting World"
005 | }
006 |
007 > Write-Greeting "Hello"
#>
#requires -version 2.0
param(
$filename = $(throw "Please specify a filename."),
$highlightRange = @(),
[Switch] $excludeLineNumbers)
$replacementColours = @{
'Attribute' = 'DarkCyan'
'Command' = 'Blue'
'CommandArgument' = 'Magenta'
'CommandParameter' = 'DarkBlue'
'Comment' = 'DarkGreen'
'GroupEnd' = 'Black'
'GroupStart' = 'Black'
'Keyword' = 'DarkBlue'
'LineContinuation' = 'Black'
'LoopLabel' = 'DarkBlue'
'Member' = 'Black'
'NewLine' = 'Black'
'Number' = 'Magenta'
'Operator' = 'DarkGray'
'Position' = 'Black'
'StatementSeparator' = 'Black'
'String' = 'DarkRed'
'Type' = 'DarkCyan'
'Unknown' = 'Black'
'Variable' = 'Red'
}
$highlightColor = "Red"
$highlightCharacter = ">"
$highlightWidth = 6
if($excludeLineNumbers) { $highlightWidth = 0 }
$file = (Resolve-Path $filename).Path
$content = [IO.File]::ReadAllText($file)
$parsed = [System.Management.Automation.PsParser]::Tokenize(
$content, [ref] $null) | Sort StartLine,StartColumn
function WriteFormattedLine($formatString, [int] $line)
{
if($excludeLineNumbers) { return }
$hColor = "DarkGray"
$separator = "|"
if($highlightRange -contains $line)
{
$hColor = $highlightColor
$separator = $highlightCharacter
}
$text = $formatString -f $line,$separator
Write-Host -NoNewLine -Fore $hColor -Back White $text
}
function CompleteLine($column)
{
$lineRemaining = $host.UI.RawUI.WindowSize.Width -
$column - $highlightWidth + 1
if($lineRemaining -lt 0)
{
$lineRemaining += $host.UI.RawUI.WindowSize.Width
}
Write-Host -NoNewLine -Back White (" " * $lineRemaining)
}
Write-Host
WriteFormattedLine "{0:D3} {1} " 1
$column = 1
foreach($token in $parsed)
{
$color = "Gray"
$color = $replacementColours[[string]$token.Type]
if(-not $color) { $color = "Gray" }
if(($token.Type -eq "NewLine") -or ($token.Type -eq "LineContinuation"))
{
CompleteLine $column
WriteFormattedLine "{0:D3} {1} " ($token.StartLine + 1)
$column = 1
}
else
{
if($column -lt $token.StartColumn)
{
$text = " " * ($token.StartColumn - $column)
Write-Host -Back White -NoNewLine $text
$column = $token.StartColumn
}
$tokenEnd = $token.Start + $token.Length - 1
if(
(($token.Type -eq "String") -or
($token.Type -eq "Comment")) -and
($token.EndLine -gt $token.StartLine))
{
$lineCounter = $token.StartLine
$stringLines = $(
-join $content[$token.Start..$tokenEnd] -split "`n")
foreach($stringLine in $stringLines)
{
$stringLine = $stringLine.Trim()
if($lineCounter -gt $token.StartLine)
{
CompleteLine $column
WriteFormattedLine "{0:D3} {1} " $lineCounter
$column = 1
}
Write-Host -NoNewLine -Fore $color -Back White $stringLine
$column += $stringLine.Length
$lineCounter++
}
}
else
{
$text = (-join $content[$token.Start..$tokenEnd])
Write-Host -NoNewLine -Fore $color -Back White $text
}
$column = $token.EndColumn
}
}
CompleteLine $column
Write-Host For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
No comments yet
Add a comment