About PowerShell Host Wizard

Background

I've been developing my own PowerShell Host Wizards for over 5 years now. But things came together recently to inspire me to release this project on CodePlex.

I have worked on several projects that use Wizards to guide the user/administrator through some questions, and then launch PowerShell scripts to perform the backend processing. Additionally, you can then pipe the output from PowerShell back to the Wizard, and it looks like one integrated process. One of these projects is the Microsoft Deployment Toolkit (example: Add an Application). The model works well, but you have to manually create each wizard page. I wondered if there was a more generic way to prompt and ask the user for input, like using Prompt and PromptForChoice.

I really got inspired when I went to the Microsoft MVP summit in Nov 2014. During a presentation of a Windows Server component., the PM mentioned that they were adding PowerShell cmdlets for their product. Then one of the attendees said that he would prefer wizards rather than PowerShell cmdlets. I couldn't disagree more, if the development team had limited resources, I would prefer that they spent the time on the PowerShell cmdlets of course. :^)

There always seems to be a battle between those who write PowerShell scripts, and the users who consume them. The motivators that drive the developers may not be the same that inspire the users. Should a developer spend more time stabilizing and enhancing the scripts, or more time developing front end user interfaces to make the scripts easier to use. You could spend a lot of time developing wizards for every scenario.

Finally I saw the PS2Exe program recently, and I liked the concept of a PowerShell script to automatically embed and build a .exe project. In the past most of my projects were just Visual Studio projects. I didn't embed everything into a single .ps1 file, but I did create a PowerShell script to auto-build an .exe from your script file.

Hello World Example


write-host "Hello world!"


Easy enough, when using Powershell.exe it will route the call to the console host.

However in the PowerShell Host Wizard, we can intercept the write calls with our own custom host, and instead write the output as a C# .Net Label on the Windows Form.


this.Controls.Add( new Label() { text = NewText } );


It get's more complex when we add in colors, verbose, and word wrapping, but you get the idea.

Write-Host

If you have not already read these posts, I do recommend reading them:
http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful
http://windowsitpro.com/blog/what-do-not-do-powershell-part-1

It's important to acknowledge a couple of important points here about output and redirection. Why is write host considered bad? Because it sends output to the host, which may not be the same thing as StdOut, or the pipeline. If your intent is to actually output to the console, then yes, Write-Host is the correct function, however if your goal is to pass information from the script or function to the next thing in the pipeline, using Write-Host will not let the information pass along.

That being said, we want to use Write-Host in our PowerShell Wizard Host, and we want to be aware of what cmdlets will drive user input and output in our Host.

Note that all output in your script not sent to Write-Host but instead left to contine to the pipeline, will be sent by PS2Exe by default to "out-string -stream" to process any object and prepare for output to the PS2Wiz console.

Walkthrough

Let's go through a basic example included in the source code package: Demo-Basic.ps1


Write-Host "Hello World!"

Write-Host "What is your Name:"
$Name = Read-Host
Write-Host "Hello $Name"

$Cred = Get-Credential -UserName $Name -Message "Don't Enter Real Credentials"
write-Host "Hello $($Cred.UserName)"


Write-Host works just as in our "Hello World" example.

Read-Host works by creating a TextBox on the screen and waiting for the Next button or Enter.

Get-Credential - When called, rather than popup a new dialog box (Which I thought would look ugly), PS2Wiz will re-package the request as a Power Host Prompt() call with a PSCredential return object.

getcred.png

Calling Functions


function Copy-MyItem
(
	[parameter(Mandatory=$true,HelpMessage="Source File")]
	[System.IO.FileInfo] $Path,

	[parameter(Mandatory=$true,HelpMessage="Destination Folder")]
	[System.IO.DirectoryInfo] $Destination
)
{
	write-Verbose "Copy $($Path.FUllName) to $($Destination.FullName)"
	copy-Item @PSBoundParameters -confirm
}

Write-Host "Copy a File..."

Copy-MyItem @PSBoundParameters


This is where PS2Wiz get's interesting...

Here in this demo, we make a call to the Copy-MyItem function, which has two Mandatory parameters. If you don't pass in the paramaters via @PSBoundParameters, then the Powershell System will automatically gather up the requirements and generate a call to teh Host Prompt() function requesting input

Additionally PS2Wiz understands the String.IO.FileInfo and String.IO.DirectoryInfo types, so we can construct a file and directory input buttons to go along with the input boxes.

prompt.png

Within the function we make a call to Copy-Item cmdlet with the -confirm switch. The Confirm switch will force the call to PS2Wiz with the PromptForChoice() function requesting confirmation.

promptchoice.png

Finally we perform a simple pause to ensure that the program does not exit until the user confirms it's OK to continue


Write-Host "Press Any Key To Continue..."
$host.ui.RawUI.ReadKey() | out-null


You can actually see a (nearly) direct call made to our PowerShell host with the ReadKey() function.

Interestingly the ReadKey() Method of the RawUI class is not implemented in the PowerShellISE.exe host. You knew that PowerShellISE.exe was just another host right?

See my note below about calling your script with -verbose. Might be better than ReadKey().

Features

Output

!!!Implemented

Write-Host -BackgroundColor <Color> -ForegroundColor<Color>
Write-Verbose
Write-Debug
Write-Error
Write-Warning
Clear-Host

!!!Notes

Calling Clear-Host may change the background and foreground colors.

Although Write-Host supports writing to screen different background and foreground colors, you can not write a single line that contains more than one color type.

Progress

!!!Implemented

Write-Progress is supported.

foreach ( $i in 1..20 )
{
	write-progress -ACtivity "Working again $i" -percentcomplete ($i * 5)
	start-sleep -Milliseconds 100
}
write-progress -Completed -Activity "Test"

!!!Notes

Be aware that you must call Write-progress with the -Completed flag to ensure that the progress bar is removed when done.

Input

!!!Implemented

Read-Host -AsSecureString
Get-Credential
$Host.UI.RawUI.ReadKey()

!!!Notes

Recomend you not use ReadKey() to keep your script open when finshed. Instead use -Verbose to support automation.

Prompt For Choice

!!!Implemented

Any routine that generates a PromptForChoice() from PowerShell, Example:

copy c:\windows\system32\ntoskrnl.exe $env:temp -confirm

See: http://technet.microsoft.com/en-us/library/ff730939.aspx
$title = "Delete Files"
$message = "Do you want to delete some files (not really, does nothing)?"

$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
    "Deletes all the files in the folder."

$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
    "Retains all the files in the folder."

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

$result = $host.ui.PromptForChoice($title, $message, $options, 0) 

switch ($result)
    {
        0 {"You selected Yes."}
        1 {"You selected No."}
    }

Prompt

PS2Wiz supports prompts for the following types:
  • Strings
    • System.String (of course)
    • System.SByte
    • System.Byte
    • System.Char
  • Numbers
    • System.Decimal
    • System.Double
    • System.Single
    • System.Int16
    • System.Int32
    • System.Int64
    • System.UInt16
    • System.UInt32
    • System.UInt64
    • System.UIntPtr
  • Displayed as CheckBoxes
    • System.Boolean
    • System.Management.Automation.SwitchParameter
  • Displayed with Password TextBoxes
    • System.Management.Automation.PSCredential
    • System.Security.SecureString
  • Unique types
    • System.DateTime
    • System.Guid
    • System.IO.FileInfo
    • System.IO.DirectoryInfo

See: http://poshcode.org/608
$fields = new-object "System.Collections.ObjectModel.Collection``1[[System.Management.Automation.Host.FieldDescription]]"

$f = New-Object System.Management.Automation.Host.FieldDescription "String Field"
$f.HelpMessage  = "This is the help for the first field"
$f.DefaultValue = "Field1"
$f.Label = "&Any Text"

$fields.Add($f)

$f = New-Object System.Management.Automation.Host.FieldDescription "Secure String"
$f.SetparameterType( [System.Security.SecureString] )
$f.HelpMessage  = "You will get a password input with **** instead of characters"
$f.DefaultValue = "Password"
$f.Label = "&Password"

$fields.Add($f)

$f = New-Object System.Management.Automation.Host.FieldDescription "Numeric Value"
$f.SetparameterType( [int] )
$f.DefaultValue = "42"
$f.HelpMessage  = "You need to type a number, or it will re-prompt"
$f.Label = "&Number"

$fields.Add($f)

$results = $Host.UI.Prompt( "Caption", "Message", $fields )

Write-Output $results

Other direct Host UI processing

!!!Implemented

$Host.UI.RawUI.ReadKey()
$Host.UI.RawUI.WIndowsTitle
$Host.UI.RawUI.SetBufferContents() - Supported only for Clear-Host
$Host.UI.RawUI.BufferSize - Read Only at 120 x 50. Supported only for Out-String

Other RawUI methods and properties have been disabled.

Most $Host and $Host.UI methods and properties are implemented.

!!!Notes

Recomend you not use ReadKey() to keep your script open when finshed. Instead use -Verbose to support automation.

Error Handling

Any errors generated by the script should be piped to the PS2Wiz host as Error output.

Any errors passed to the pipeline will be displayed as errors, including line numbers.

Pressing the cancel button during script execution should cleanly the script to terminate.

Advanced Callbacks

There are a couple of examples in the PS2Wiz demos of calls made using references to some public static assemblies directly into the PS2Wiz code. They are provided as examples of callbacks for more advanced functions.

Extract an embedded *.rtf file and display as an RichTextForm.
$RTFString = [PowerShell_Wizard_Host.PSHostCallBack]::GetFileFromResource("MS-RL.rtf")[PowerShell_Wizard_Host.PSHostCallBack]::DisplayRTF($RTFString)

Get the path to the currently executing program ( $MyInvocation may not be available ) .
$ScriptPath = Split-Path ([PowerShell_Wizard_Host.PSHostCallBack]::GetHostExe)
Write-Host "Local Path: $ScriptPath"

Display a picture (or animation):
$Animation = "http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-38-53-metablogapi/1261.progress_5F00_7A4A65F2.gif"
[PowerShell_Wizard_Host.PSHostCallBack]::DisplayImage( $ANimation)

DIsplay some text as a HyperLink:
[PowerShell_Wizard_Host.PSHostCallBack]::DisplayHyperLink("Notepad","Notepad.exe","")
[PowerShell_Wizard_Host.PSHostCallBack]::DisplayHyperLink("CodePlex","http://www.codeplex.com","")

Command Line Support

Command Line parameters passed in through the PS2Wiz Exe program are passed in to the calling script.

Additionally, if you specify -Verbose in the command line, the script will not exit upon finishing (no need to add ReadKey() at the end of every script).

Limitations

No console buffering or cursor support (See: http://bit.ly/e0Mw9w )

Release Notes

None

Future

Out-GridView -passthru

Last edited Dec 17, 2014 at 3:47 AM by KeithGa, version 8