Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Simulating a PowerShell Demo

Posted on July 13, 2015

A number of years ago I published my version of a Start-Demo script. In my version, I can create a text file with all of the commands I want to run. It might look like this:

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Get-Date
get-ciminstance win32_logicaldisk | select DeviceID,Size,Freespace
::
get-service |
where {$_.status -eq 'running'} |
Select Name,Status |
Format-Table -autosize
::
Get-eventlog -list -computername chi-dc04

The :: indicate the beginning and end of a multi-line command. This will simulate the >> nested prompt. My function reads this file and "types" it out so that it looks like you are typing it. You can control the speed and even introduce typos if you are so inclined. I used this a lot in my Pluralsight courses since I can't seem to talk and type at the same time.

I was asked about this function so I dusted it off and made a few minor changes. The main change is that you can specify what PowerShell version to display if you want to simulate starting a brand new PowerShell session. The default is your current version.

Here's the updated function.

Function Start-TypedDemo {

<#
.SYNOPSIS
Simulate a PowerShell session
.DESCRIPTION
This command simulates an interactive PowerShell session. It will process a text file of PowerShell commands. The function will insert your prompt and "type" out each command when you press any key. At the end of the typed command or whenever a pipe character is inserted, the script will pause. Press Enter or any key to continue. 
If it is the end of the command pressing Enter will execute the command. Use the -NoExecute parameter to run through the demo without executing any commands. Commented lines will be skipped. Press 'q' or ESC at any pause to quit the demo.

This function will NOT run properly in the ISE.

VARIABLE NAMES
Do not use any variables in your script file that are also used in this script. These are the variables you most likely need to avoid:

 $file
 $i
 $running
 $key
 $command
 $count

MULITLINE COMMANDS
To use a multiline command, in your demo file, put a :: before the first line and :: after the last line:


Get-Date
::
get-wmiobject win32_logicaldisk -filter "Drivetype=3" | 
Select Caption,VolumeName,Size,Freespace | 
format-table -autosize
::
Get-Process


The function will simulate a nested prompt. Press any key after the last >> to execute. Avoid using the line continuation character.

TIMING
By default the function will insert a random pause interval between characters. This is a random value in milliseconds between the -RandomMinimum and -RandomMaximum parameters which have default values of 50 and 140 respectively. 
If you want a static or consisent interval, then use the -Pause parameter. The recommended value is 80.

TYPOS
The -IncludeTypo parameter will introduce a typo at random intervals, then backspace over it and insert the correct text. You should not use this parameter with the -Transcript parameter. The transcript will have control characters for every backspace. 

It is not recommended to use -IncludeTypo when running any sort of transcript.

SCOPE
All the commands in your demo script are executed in the context of the Start-TypedDemo function. This means you have to be very aware of scope. While you can access items in the global scope like PSDrives, anything you create in the demo script will not persist. This also means that none of the commands in your demo script will show up in PowerShell history.

COMMENTS
Any line that begins with # will be treated as a comment and skipped. If you have a multi-line comment you will need to put a # at the begininng of each line. You can't use PowerShell's block comment characters.


.PARAMETER File
The file name of PowerShell commands to execute
.PARAMETER RandomMinimum
The minimum time interval between characters in milliseconds. The default is 50.
.PARAMETER RandomMaximum
The maximum time interval between characters in milliseconds. The default is 140.
.PARAMETER IncludeTypo
When specified, the function will simulate typing mistakes. It is not recommended to use this parameter with -Transcript.
.PARAMETER Pause
The typing speed interval between characters in milliseconds. The recommended value is 100.
.PARAMETER Transcript
The file name and path for a transcript session file. Existing files will be overwritten. It is not recommended to use this parameter with -IncludeTypo.
.PARAMETER NoExecute
Do not execute any of the commands. 
.PARAMETER NewSession
Simulate a new PowerShell session with the copyright header and a prompt. This works best if you start your demo from the C: drive.
.PARAMETER PSVersion
Use this to when simulating a new PowerShell session to depict a specific PowerShell version

. The default is your current version.
.EXAMPLE
PS C:\> Start-TypedDemo c:\work\demo.txt
Run the commands in c:\work\demo.txt using the random defaults
.EXAMPLE
PS C:\> Start-TypedDemo c:\work\demo.txt -pause 100 -NoExecute
Run the commands in c:\work\demo.txt using a static interval of 100 milliseconds. The function will only type the commands. They will not be executed.
.EXAMPLE
PS C:\> Start-TypedDemo c:\work\demo.txt -transcript c:\work\demotrans.txt
Run the commands in c:\work\demo.txt using the random defaults and create a transcript file.
.NOTES
NAME        :  Start-TypedDemo
VERSION     :  5.0
LAST UPDATED:  7/13/2015
AUTHOR      :  Jeffery Hicks

This function was first published at https://jdhitsolutions.com/blog/scripting/1473/friday-fun-start-typeddemo-v2/

Learn more about PowerShell:
Essential PowerShell Learning Resources
**************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .LINK Write-Host Invoke-Command Start-Sleep Get-Random .INPUTS None .OUTPUTS None. This only writes to the host console, not the pipeline. #> [cmdletBinding(DefaultParameterSetName="Random")] Param( [Parameter(Position=0,Mandatory=$True,HelpMessage="Enter the name of a text file with your demo commands")] [ValidateScript({Test-Path $_})] [string]$File, [ValidateScript({$_ -gt 0})] [Parameter(ParameterSetName="Static")] [int]$Pause=80, [Parameter(ParameterSetName="Random")] [ValidateScript({$_ -gt 0})] [int]$RandomMinimum=50, [Parameter(ParameterSetName="Random")] [ValidateScript({$_ -gt 0})] [int]$RandomMaximum=140, [Parameter(ParameterSetName="Random")] [switch]$IncludeTypo, [string]$Transcript, [switch]$NoExecute, [switch]$NewSession, [ValidateSet(2,3,4,5)] [Int]$PSVersion = $PSVersionTable.PsVersion.MajorVersion ) #this is an internal function so I'm not worried about the name Function PauseIt { [cmdletbinding()] Param() Write-Verbose "PauseIt" #wait for a key press $Running=$true #keep looping until a key is pressed While ($Running) { if ($host.ui.RawUi.KeyAvailable) { $key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyDown") if ($key) { $Running=$False #check the value and if it is q or ESC, then bail out if ($key -match "q|27") { Write-Host "`r" Return "quit" } #if match q|27 } #if $key } #if key available Start-Sleep -millisecond 100 } #end While } #PauseIt function #abort if running in the ISE if ($host.name -match "PowerShell ISE") { Write-Warning "This will not work in the ISE. Use the PowerShell console host." Return } Clear-Host if ($NewSession) { #simulate a new v3 session #define a set of coordinates $z= new-object System.Management.Automation.Host.Coordinates 0,0 Switch ($PSVersion) { 2 {$year = 2009} 3 {$year = 2012} 4 {$year = 2013} 5 {$year = 2015} } $header=@" Windows PowerShell Copyright (C) $YEAR Microsoft Corporation. All rights reserved. `r "@ Write-Host $header } #if new session #Start a transcript if requested $RunningTranscript=$False if ($Transcript) { Try { Start-Transcript -Path $Transcript -ErrorAction Stop | Out-Null $RunningTranscript=$True } Catch { Write-Warning "Could not start a transcript. One may already be running." } } #strip out all comments and blank lines Write-Verbose "Getting commands from $file" $commands = Get-Content -Path $file | Where {$_ -notmatch "#" -AND $_ -match "\w|::|{|}|\(|\)"} $count=0 #write a prompt using your current prompt function Write-Verbose "prompt" Write-Host $(prompt) -NoNewline $NoMultiLine=$True $StartMulti=$False #define a scriptblock to get typing interval Write-Verbose "Defining interval scriptblock" $interval={ if ($pscmdlet.ParameterSetName -eq "Random") { #get a random pause interval Get-Random -Minimum $RandomMinimum -Maximum $RandomMaximum } else { #use the static pause value $Pause } } #end Interval scriptblock #typo scriptblock Write-Verbose "Defining typo scriptblock" $Typo={ #an array of characters to use for typos $matrix="a","s","d","f","x","q","w","e","r","z","j","t","x","c","v","b" #Insert a random bad character Write-host "$($matrix | .\Get-RandomPassword.ps1) " -nonewline Start-Sleep -Milliseconds 500 #simulate backspace Write-host `b -NoNewline start-sleep -Milliseconds 300 Write-host `b -NoNewline start-sleep -Milliseconds 200 Write-Host $command[$i] -NoNewline } #end Typo Scriptblock Write-Verbose "Defining PipeCheck Scriptblock" #define a scriptblock to pause at a | character in case an explanation is needed $PipeCheck={ if ($command[$i] -eq "|") { If ((PauseIt) -eq "quit") {Return} } } #end PipeCheck scriptblock Write-Verbose "Processing commands" foreach ($command in $commands) { #trim off any spaces $command=$command.Trim() $count++ #pause until a key is pressed which will then process the next command if ($NoMultiLine) { If ((PauseIt) -eq "quit") {Return} } #SINGLE LINE COMMAND if ($command -ne "::" -AND $NoMultiLine) { Write-Verbose "single line command" for ($i=0;$i -lt $command.length;$i++) { #simulate errors if -IncludeTypo if ($IncludeTypo -AND ($(&$Interval) -ge ($RandomMaximum-5))) { &$Typo } #if includetypo else { #write the character Write-Verbose "Writing character $($command[$i])" Write-Host $command[$i] -NoNewline } #insert a pause to simulate typing Start-sleep -Milliseconds $(&$Interval) &$PipeCheck } #remove the backtick line continuation character if found if ($command.contains('`')) { $command=$command.Replace('`',"") } #Pause until ready to run the command If ((PauseIt) -eq "quit") {Return} Write-host "`r" #execute the command unless -NoExecute was specified if (-NOT $NoExecute) { Invoke-Expression $command | Out-Default } else { Write-Host $command -ForegroundColor Cyan } } #IF SINGLE COMMAND #START MULTILINE #skip the :: elseif ($command -eq "::" -AND $NoMultiLine) { $NoMultiLine=$False $StartMulti=$True #define a variable to hold the multiline expression [string]$multi="" } #elseif #FIRST LINE OF MULTILINE elseif ($StartMulti) { for ($i=0;$i -lt $command.length;$i++) { if ($IncludeTypo -AND ($(&$Interval) -ge ($RandomMaximum-5))) { &$Typo } else { write-host $command[$i] -NoNewline} #else start-sleep -Milliseconds $(&$Interval) #only check for a pipe if we're not at the last character #because we're going to pause anyway if ($i -lt $command.length-1) { &$PipeCheck } } #for $StartMulti=$False #remove the backtick line continuation character if found if ($command.contains('`')) { $command=$command.Replace('`',"") } #add the command to the multiline variable $multi+=" $command" # if (!$command.Endswith('{')) { $multi += ";" } if ($command -notmatch ",$|{$|}$|\|$|\($") { $multi += " ; " } If ((PauseIt) -eq "quit") {Return} } #elseif #END OF MULTILINE elseif ($command -eq "::" -AND !$NoMultiLine) { Write-host "`r" Write-Host ">> " -NoNewline $NoMultiLine=$True If ((PauseIt) -eq "quit") {Return} #execute the command unless -NoExecute was specified Write-Host "`r" if (-NOT $NoExecute) { Invoke-Expression $multi | Out-Default } else { Write-Host $multi -ForegroundColor Cyan } } #elseif end of multiline #NESTED PROMPTS else { Write-Host "`r" Write-Host ">> " -NoNewLine If ((PauseIt) -eq "quit") {Return} for ($i=0;$i -lt $command.length;$i++) { if ($IncludeTypo -AND ($(&$Interval) -ge ($RandomMaximum-5))) { &$Typo } else { Write-Host $command[$i] -NoNewline } Start-Sleep -Milliseconds $(&$Interval) &$PipeCheck } #for #remove the backtick line continuation character if found if ($command.contains('`')) { $command=$command.Replace('`',"") } #add the command to the multiline variable and include the line break #character $multi+=" $command" # if (!$command.Endswith('{')) { $multi += ";" } if ($command -notmatch ",$|{$|\|$|\($") { $multi+=" ; " #$command } } #else nested prompts #reset the prompt unless we've just done the last command if (($count -lt $commands.count) -AND ($NoMultiLine)) { Write-Host $(prompt) -NoNewline } } #foreach #stop a transcript if it is running if ($RunningTranscript) { #stop this transcript if it is running Stop-Transcript | Out-Null } } #function #uncomment if you want to use the alias #Set-Alias -Name std -Value Start-TypedDemo

Have fun.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

3 thoughts on “Simulating a PowerShell Demo”

  1. Luis Yax says:
    July 13, 2015 at 9:20 pm

    JEFFERY HICKS, you are a ninja! Great gem you have here.

  2. Victor Vogelpoel says:
    July 14, 2015 at 2:45 am

    I haven’t found a use or need for this script yet, but I would opt to comment out the multiline marker to make the script file runnable in either Start-TypedDemo or by itself.

    Get-Date
    get-ciminstance win32_logicaldisk | select DeviceID,Size,Freespace
    #::
    get-service |
    where {$_.status -eq ‘running’} |
    Select Name,Status |
    Format-Table -autosize
    #::
    Get-eventlog -list -computername chi-dc04

    1. Jeffery Hicks says:
      July 14, 2015 at 8:31 am

      There is definitely a special use case for this script. I never want to run the demo file as a script as I typically am using a set of commands to teach something. The primary driver for the script is when I create training videos so that I can walk through the commands without having to type them, but it *looks* like I am typing.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d