Start-Demo: Help doing demos using PowerShell

A couple a weeks ago I got a call from BillG’s Technical Assistant (TA) telling me that Bill and Ray (Ozzie) wanted to get a demo of the new stuff we were doing with PowerShell. The setting was Bill’s conference room that would be organized with a number of Demo stations. You’d get called in and were allowed to bring one other person but then it would be Bill and Ray sitting next to you getting the demo – so you pretty much have their full attention. That’s great except for the fact that I am a bad typist and make mistakes all the time. That is why PowerShell supports wildcards, parameter disambiguation and tab completion – you benefit from my flaws. J Still, this was all the new stuff and had never been demo (to anyone) before and wasn’t even to ready for alpha testing so I was taking a risk in showing it.


In the past, I dealt with this sort of situation by having a text file which contained all the things I wanted to type in it. Doing a cut-n-paste from notepad during a demo looks REALLY bad so what I would do is to paste the entire sequence into a PowerShell session. This would execute all the commands and then I would use the CMD.EXE command history feature and use the UP arrow to get back to the first command and then just type ENTER and DOWNARROW to rerun the demo one command at a time. This allows you to focus on WHAT you want to say instead of putting all your mental energy into typing things correctly. This is what I did when I did a demo at Bob Muglia’s keynote address in Barcelona last year.


Let me be clear on this point – this technique doesn’t work very well! It’s a toss up as to whether it is better or worse they just typing things in from scratch. The big problem in both Barcelona and in the BillG/RayO demo was STATE. When you run a command, it can change the state of the system so you might not be able to just go to the top of a demo and run it all over again because the state might be changed. The other issue is that the CMD.exe mechanism is pretty fragile. If you change the command in any way shape or form, it decides that it is a new command and thus it resets your position in the command history to be at the end. ARRRGGG! I’ll spare you the horror stories and get to the punch line. I decided I had had enough of that nonsense and decided to write a script which would run a demo for me. I’ve called this function: Start-Demo. I’ve attached it to this blog entry. I have to say that I’m pretty please with how it came out.


I strongly encourage you to use this if you ever have to give a demo – it totally rocks! It completely transforms the experience of giving a demo, allowing you to focus on your messages instead of typing. It is REAL in the sense that the commands REALLY run, the script just eliminates your typing. I put in all sort’s of “grace notes” including:



  • You can specify which file you want to demo (it defaults to “.\demo.txt”)

  • You can specify which command to start with (it defaults to 0)

  • It shows you the command (both at the prompt and in the Window Title [for the folks at the back of the room) and waits for input. If your input is <CR>, it runs the command.


  • You can provide other input and it will do other actions. You can:


    • Ask for help using “?”

    • Quit at any point

    • Dump the list of commands in the demo. It produces a red line above your current point in the demo

    • Run another command or Suspend the demo and enter into a nested prompt to explore a topic

    • Go to a specified command in the demo

    • Find all the commands in the demo using a regular expression

    • Check your time. It displays how many minutes and seconds since the start of the demo. This information is also displayed in the Window title on an ongoing basis.

    I think you’ll find some pretty useful techniques in the script itself so even if you do use it for your demos, you might want to review the script:


    function Start-Demo
    {
        param($file=”.\demo.txt”, [int]$command=0)
        Clear-Host

        $_lines = Get-Content $file
        $_starttime = [DateTime]::now
        Write-Host -for Yellow “<Demo [$file] Started>”


        # We use a FOR and an INDEX ($_i) instead of a FOREACH because
        # it is possible to start at a different location and/or jump
        # around in the order.
        for ($_i = $Command; $_i -lt $_lines.count; $_i++)
        {    
            $_SimulatedLine = $(“`n[$_i]PS> ” + $($_Lines[$_i]))
            Write-Host -NoNewLine $_SimulatedLine

            # Put the current command in the Window Title along with the demo duration
            $_Duration = [DateTime]::Now – $_StartTime
            $Host.UI.RawUI.WindowTitle = “[{0}m, {1}s]        {2}” -f [int]$_Duration.TotalMinutes, [int]$_Duration.Seconds, $($_Lines[$_i])
            if ($_lines[$_i].StartsWith(“#”))
            {
                continue
            }
            $_input=[System.Console]::ReadLine()
            switch ($_input)
            {
                “?”    
                        {
                            Write-Host -ForeGroundColor Yellow “Running demo: $file`n(q) Quit (!) Suspend (#x) Goto Command #x (fx) Find cmds using X`n(t) Timecheck (s) Skip (d) Dump demo”
                            $_i -= 1
                        }
                “q”    
                        {
                            Write-Host -ForeGroundColor Yellow “<Quit demo>”
                            return                    
                        }
                “s”
                        {
                            Write-Host -ForeGroundColor Yellow “<Skipping Cmd>”
                        }
                “d”
                        {
                            for ($_ni = 0; $_ni -lt $_lines.Count; $_ni++)
                            {
                                 if ($_i -eq $_ni)
                                 {     Write-Host -ForeGroundColor Red (“*” * 80)
                                 }
                                 Write-Host -ForeGroundColor Yellow (“[{0,2}] {1}” -f $_ni, $_lines[$_ni])
                            }
                            $_i -= 1
                        }
                “t”    
                        {
                             $_Duration = [DateTime]::Now – $_StartTime
                             Write-Host -ForeGroundColor Yellow $(“Demo has run {0} Minutes and {1} Seconds” -f [int]$_Duration.TotalMinutes, [int]$_Duration.Seconds)
                             $_i -= 1
                        }
                {$_.StartsWith(“f”)}
                        {
                            for ($_ni = 0; $_ni -lt $_lines.Count; $_ni++)
                            {
                                 if ($_lines[$_ni] -match $_.SubString(1))
                                 {
                                        Write-Host -ForeGroundColor Yellow (“[{0,2}] {1}” -f $_ni, $_lines[$_ni])
                                 }
                            }
                            $_i -= 1
                        }
                {$_.StartsWith(“!”)}
                        {
                             if ($_.Length -eq 1)
                             {
                                     Write-Host -ForeGroundColor Yellow “<Suspended demo – type ‘Exit’ to resume>”
                                     $host.EnterNestedPrompt()
                             }else
                             {
                                     trap [System.Exception] {Write-Error $_;continue;}
                                     Invoke-Expression $($_.SubString(1) + “| out-host”)
                             }
                             $_i -= 1
                        }
                {$_.StartsWith(“#”)}    
                        {
                             $_i = [int]($_.SubString(1)) – 1
                             continue
                        }
                default
                        {
                             trap [System.Exception] {Write-Error $_;continue;}
                             Invoke-Expression $($_lines[$_i] + “| out-host”)
                             $_Duration = [DateTime]::Now – $_StartTime
                             $Host.UI.RawUI.WindowTitle = “[{0}m, {1}s]        {2}” -f [int]$_Duration.TotalMinutes, [int]$_Duration.Seconds, $($_Lines[$_i])
                             [System.Console]::ReadLine()
                        }
            }
        }
        $_Duration = [DateTime]::Now – $_StartTime
        Write-Host -ForeGroundColor Yellow $(“<Demo Complete {0} Minutes and {1} Seconds>” -f [int]$_Duration.TotalMinutes, [int]$_Duration.Seconds)
        Write-Host -ForeGroundColor Yellow $([DateTime]::now)
    }


Let’s see it in action.  NOTE – I’ve gone back and colored my INPUT red so that you can see it more clearly.  It will just be normal when you run it. 



PS> Start-Demo
<Demo [.\demo.txt] Started>

[0]PS> # ****** PowerShell is a SHELL *****
[1]PS> hostname?
Running demo: .\demo.txt
(q) Quit (!) Suspend (#x) Goto Command #x (fx) Find cmds using X
(t) Timecheck (s) Skip (d) Dump demo

[1]PS> hostname
jpsvista1



[2]PS> net use
New connections will be remembered.


Status Local Remote Network

——————————————————————————-
Unavailable Y: \\axp-test\builds\vistartm
Microsoft Windows Network
Unavailable Z: \\powershell\public Microsoft Windows Network
The command completed successfully.




[3]PS> notepad#2

[2]PS> net useS
<Skipping Cmd>

[3]PS> notepadd
[ 0] # ****** PowerShell is a SHELL *****
[ 1] hostname
[ 2] net use
********************************************************************************
[ 3] notepad
[ 4] # ******* Structured Commands ******
[ 5] Get-Process -ProcessName lsass
[ 6] gps -p lsass
[ 7] gps l*s
[ 8] gps |Where {$_.Handles -ge 500} |Sort Handles |ft Handles,Name,Desc* -Auto
[ 9] gps [b-t]*[c-r] |stop-process -whatif

[3]PS> notepad#6

[6]PS> gps -p lsassT
Demo has run 1 Minutes and 52 Seconds

[6]PS> gps -p lsass

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
1377 21 8872 5168 71 111.97 592 lsass





[7]PS> gps l*s!GPS *S

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
755 6 2276 3844 91 13.38 456 csrss
762 13 7076 23340 211 291.06 516 csrss
1369 20 8872 5168 71 111.97 592 lsass
778 4 3020 2920 40 0.05 508 psxss
299 8 5416 6744 43 20.14 568 services
32 1 248 536 4 0.11 388 smss



[7]PS> gps l*s!
<Suspended demo – type ‘Exit’ to resume>
PS> gps c*s

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
751 6 2276 3844 91 13.38 456 csrss
760 13 7076 23340 211 291.19 516 csrss


PS> exit

[7]PS> gps l*s

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
1369 20 8872 5168 71 111.97 592 lsass





[8]PS> gps |Where {$_.Handles -ge 500} |Sort Handles |ft Handles,Name,Desc* -AutoQ
<Quit demo>


Enjoy!  
 


Jeffrey Snover [MSFT]
Windows PowerShell/MMC Architect
Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Start-Demo.ps1