#Requires -version 2.0 # ----------------------------------------------------------------------------- # Script: Start-TypedDemo.ps1 # Version: 2.0 # Author: Jeffery Hicks # http://jdhitsolutions.com/blog # http://twitter.com/JeffHicks # http://www.ScriptingGeek.com # Date: 5/27/2011 # Keywords: # Comments: # # "Those who forget to script are doomed to repeat their work." # # **************************************************************** # * 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. * # **************************************************************** # ----------------------------------------------------------------------------- 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 in the ISE. 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. 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 200 respectively. If you want a static or consisent interval, then use the -Pause parameter. The recommended value is 100. 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. .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 200. .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. .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 : 2.0 LAST UPDATED: 5/27/2011 AUTHOR : Jeffery Hicks .LINK http://jdhitsolutions.com/blog/2011/05/friday-fun-start-typeddemo-v2/ .LINK Write-Host Invoke-Command Start-Sleep Get-Random .INPUTS None .OUTPUTS 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=100, [Parameter(ParameterSetName="Random")] [ValidateScript({$_ -gt 0})] [int]$RandomMinimum=50, [Parameter(ParameterSetName="Random")] [ValidateScript({$_ -gt 0})] [int]$RandomMaximum=200, [Parameter(ParameterSetName="Random")] [switch]$IncludeTypo, [string]$Transcript, [switch]$NoExecute, [switch]$NewSession ) #this is an internal function so I'm not worried about the name Function PauseIt { Param() #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" } } } 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 session #define a set of coordinates $z= new-object System.Management.Automation.Host.Coordinates 0,0 $header=@" Windows PowerShell Copyright (C) 2009 Microsoft Corporation. All rights reserved. `r "@ Write-Host $header } #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 #$commands=Get-Content -Path $file | Where {-NOT $_.StartsWith("#") -AND $_ -match "\w"} $commands=Get-Content -Path $file | Where {-NOT $_.StartsWith("#") -AND $_ -match "\w|::|{|}|\(|\)"} $count=0 #write a prompt using your current prompt function Write-Host $(prompt) -NoNewline $NoMultiLine=$True $StartMulti=$False #define a scriptblock to get typing interval $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 $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-random) " -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 #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 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) { 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-host $command[$i] -NoNewline } #insert a pause to simulate typing Start-sleep -Milliseconds $(&$Interval) &$PipeCheck } #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 } } #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 #add the command to the multiline variable $multi+=$command 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 } } #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 } #add the command to the multiline variable $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