# encoding: utf-8 # api: powershell # title: Wizard template # description: This is a template intended for creating a wizard with PowerShell code from scratch. The sample consists of two sections, the template itself and the wxample of its use. # version: 1.5 # type: script # author: Alexander Petrovskiy # license: CC0 # function: Enable-FinishButton # x-poshcode-id: 2699 # x-archived: 2017-04-30T09:37:28 # x-published: 2012-05-27T15:52:00 # # The 562th line (’# Step 1: Welcome’) is now a border. All the above is the template, the remainder is how to use the template. Good luck, guys! # ####################################################################################################################### # File: wizard_template.ps1 # # Author: Alexander Petrovskiy # # Publisher: Alexander Petrovskiy, PowerShellDevTools.WordPress.Com # # Copyright: © 2010-2011 Alexander Petrovskiy, PowerShellDevTools.WordPress.Com. All rights reserved. # # Usage: To run this example no preparations needed. # # There also are the following settings you may be intersted in: # # 1. the wizard caption # # 2. default size of the wizard # # 3. default size of the wizard's buttons # # 4. button labels # # 5. right-to-left layout # # All the settings mentioned are placed inside the 'adjustable settings' region # # Please provide feedback in the PowerShellDevTools.WordPress.Com blog. # ####################################################################################################################### cls Set-StrictMode -Version 2 #region host preparations if ($Host.Name -eq 'ConsoleHost') { Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; } #endregion host preparations #region the resulting wizard #region adjustable settings #region controls settings # Form size and caption [string]$script:constWizardInitialCaption = ` 'This is a sample wizard'; [int]$script:constWizardWidth = ` [System.Windows.Forms.SystemInformation]::VirtualScreen.Width / 2; [int]$script:constWizardHeight = ` [System.Windows.Forms.SystemInformation]::VirtualScreen.Height / 2; # Buttons Next, Back, Cancel, Finish [int]$script:constButtonHeight = 23; [int]$script:constButtonWidth = 75; [int]$script:constButtonAreaHeight = ` $script:constButtonHeight * 2; [string]$script:constButtonNextName = '&Next'; [string]$script:constButtonBackName = '&Back'; [string]$script:constButtonFinishName = '&Finish'; [string]$script:constButtonCancelName = '&Cancel'; # Step display name and description [int]$script:constLabelStepDisplayNameLeft = 5; [int]$script:constLabelStepDisplayNameTop = 0; [float]$script:constLabelStepDisplayNameFontSize = 16; [int]$script:constLabelStepDescriptionLeft = 5; [int]$script:constLabelStepDescriptionTop = 30; # Form properties [bool]$script:constWizardRigthToLeft = $false; # Initial step number [int]$script:currentStep = 0; #endregion controls settings #endregion adjustable settings #region mandatory settings #region Initialization of the SplitContainer controls # The outer split container [System.Windows.Forms.SplitContainer]$script:splitOuter = ` New-Object System.Windows.Forms.SplitContainer; $script:splitOuter.Dock = [System.Windows.Forms.DockStyle]::Fill; $script:splitOuter.IsSplitterFixed = $true; $script:splitOuter.Orientation = ` [System.Windows.Forms.Orientation]::Horizontal; $script:splitOuter.SplitterWidth = 5; # The inner split container [System.Windows.Forms.SplitContainer]$script:splitInner = ` New-Object System.Windows.Forms.SplitContainer; $script:splitInner.Dock = [System.Windows.Forms.DockStyle]::Fill; $script:splitInner.IsSplitterFixed = $true; $script:splitInner.Orientation = ` [System.Windows.Forms.Orientation]::Horizontal; $script:splitInner.SplitterWidth = 5; $script:splitOuter.Panel1.Controls.Add($script:splitInner); # The labels for the curent step name and description [System.Windows.Forms.Label]$script:lblStepDisplayName = ` New-Object System.Windows.Forms.Label; $script:lblStepDisplayName.Left = ` $script:constLabelStepDisplayNameLeft; $script:lblStepDisplayName.Top = ` $script:constLabelStepDisplayNameTop; [System.Drawing.Font]$private:font = ` $script:lblStepDisplayName.Font; $private:font = ` New-Object System.Drawing.Font($private:font.Name, ` $script:constLabelStepDisplayNameFontsize, ` $private:font.Style, $private:font.Unit, ` $private:font.GdiCharSet, $private:font.GdiVerticalFont ); $script:lblStepDisplayName.Font = $private:font; [System.Windows.Forms.Label]$script:lblStepDescription = ` New-Object System.Windows.Forms.Label; $script:lblStepDescription.Left = ` $script:constLabelStepDescriptionLeft; $script:lblStepDescription.Top = ` $script:constLabelStepDescriptionTop; $script:splitInner.Panel1.Controls.AddRange(($script:lblStepDisplayName, ` $script:lblStepDescription)); #endregion Initialization of the SplitContainer controls #region buttons functions function Enable-FinishButton { $script:btnNext.Enabled = $false; $script:btnNext.Visible = $false; $script:btnFinish.Enabled = $true; $script:btnFinish.Visible = $true; } function Enable-NextButton { $script:btnNext.Enabled = $true; $script:btnNext.Visible = $true; $script:btnFinish.Enabled = $false; $script:btnFinish.Visible = $false; } function Enable-BackButton { $script:btnBack.Enabled = $true; $script:btnBack.Visible = $true; $script:btnCancel.Enabled = $false; $script:btnCancel.Visible = $false; } function Enable-CancelButton { $script:btnBack.Enabled = $false; $script:btnBack.Visible = $false; $script:btnCancel.Enabled = $true; $script:btnCancel.Visible = $true; } #endregion buttons functions #region the Next and Back buttons [System.Windows.Forms.Button]$script:btnNext = ` New-Object System.Windows.Forms.Button; $script:btnNext.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor ` [System.Windows.Forms.AnchorStyles]::Right -bor ` [System.Windows.Forms.AnchorStyles]::Top; $script:btnNext.Text = $script:constButtonNextName; [System.Windows.Forms.Button]$script:btnBack = ` New-Object System.Windows.Forms.Button; $script:btnBack.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor ` [System.Windows.Forms.AnchorStyles]::Right -bor ` [System.Windows.Forms.AnchorStyles]::Top; $script:btnBack.Text = $script:constButtonBackName; $script:splitOuter.Panel2.Controls.AddRange(($script:btnBack, $script:btnNext)); #endregion the Next and Back buttons #region the Finish and the Cancel buttons [System.Windows.Forms.Button]$script:btnFinish = ` New-Object System.Windows.Forms.Button; $script:btnFinish.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor ` [System.Windows.Forms.AnchorStyles]::Right -bor ` [System.Windows.Forms.AnchorStyles]::Top; $script:btnFinish.Text = $script:constButtonFinishName; [System.Windows.Forms.Button]$script:btnCancel = ` New-Object System.Windows.Forms.Button; $script:btnCancel.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor ` [System.Windows.Forms.AnchorStyles]::Right -bor ` [System.Windows.Forms.AnchorStyles]::Top; $script:btnCancel.Text = $script:constButtonCancelName; Enable-CancelButton; Enable-NextButton; $script:splitOuter.Panel2.Controls.AddRange(($script:btnCancel, $script:btnFinish)); #endregion the Finish and the Cancel buttons #region Initialization of the main form $script:frmWizard = $null; [System.Windows.Forms.Form]$script:frmWizard = ` New-Object System.Windows.Forms.Form; $script:frmWizard.Controls.Add($script:splitOuter); # left-to-right and right-to-left if ($script:constWizardRigthToLeft) { $script:frmWizard.RightToLeft = ` [System.Windows.Forms.RightToLeft]::Yes; $script:frmWizard.RightToLeftLayout = $true; } else { $script:frmWizard.RightToLeft = ` [System.Windows.Forms.RightToLeft]::No; $script:frmWizard.RightToLeftLayout = $false; } $script:frmWizard.Text = $script:constWizardInitialCaption; #endregion Initialization of the main form #endregion mandatory settings #region the Wizard steps [System.Collections.ArrayList]$script:wzdSteps = ` New-Object System.Collections.ArrayList; # Here we create an 'enumeration' (PSObject) # and begin filling ArrayList $script:wzdSteps with Panel controls [System.EventHandler]$script:hndlRunControlsAdd = ` {try{$script:splitInner.Panel2.Controls.Add($script:wzdSteps[$script:currentStep]);} catch{Write-Debug $Error[0]; Write-Debug $global:Error[0];}}; #region function New-WizardStep function New-WizardStep { param( [string]$StepName, [string]$StepDisplayName, [string]$StepDescription = '' ) # Storing parameters in step arrays Add-Member -InputObject $script:steps -MemberType NoteProperty ` -Name $StepName -Value $script:wzdSteps.Count; $null = $script:stepDisplayNames.Add($StepDisplayName); $null = $script:stepDescriptions.Add($StepDescription); # Adding a fake scriptblock to keep counters $null = $script:stepscriptblocks.Add({}); # Create and add the new step's panel to the array [System.Windows.Forms.Panel]$private:panel = ` New-Object System.Windows.Forms.Panel; $null = $script:wzdSteps.Add($private:panel); $script:currentStep = $script:wzdSteps.Count - 1; $script:splitInner.Panel2.Controls.Add($script:wzdSteps[$script:currentStep]); $script:wzdSteps[$script:currentStep].Dock = ` [System.Windows.Forms.DockStyle]::Fill; # To restore initial state for this code running before the user accesses the wizard. $script:currentStep = 0; } #endregion function New-WizardStep #region function Add-ControlToStep function Add-ControlToStep # Adds a control of selected type to a wizard step { param( [Parameter(Mandatory=$true)] [string]$StepNumber, [Parameter(Mandatory=$true)] [string]$ControlType, [Parameter(Mandatory=$true)] [string]$ControlName, [int]$ControlTop, [int]$ControlLeft, [int]$ControlHeight, [int]$ControlWidth, [string]$ControlData ) $private:ctrl = $null; try{ $private:ctrl = New-Object $ControlType; }catch{Write-Error "Unable to create a control of $($ControlType) type";} try{ $private:ctrl.Name = $ControlName; }catch{Write-Error "Unable to set the Name property with value $($ControlName) to a control of the $($ControlType) type";} try{ $private:ctrl.Top = $ControlTop; }catch{Write-Error "Unable to set the Top property with value $($ControlTop) to a control of the $($ControlType) type";} try{ $private:ctrl.Left = $ControlLeft; }catch{Write-Error "Unable to set the Left property with value $($ControlLeft) to a control of the $($ControlType) type";} try{ $private:ctrl.Height = $ControlHeight; }catch{Write-Error "Unable to set the Height property with value $($ControlHeight) to a control of the $($ControlType) type";} try{ $private:ctrl.Width = $ControlWidth; }catch{Write-Error "Unable to set the Width property with value $($ControlWidth) to a control of the $($ControlType) type";} try{ $private:ctrl.Text = $ControlData; }catch{Write-Error "Unable to set the Text property with value $($ControlData) to a control of the $($ControlType) type";} try{ $wzdSteps[$StepNumber].Controls.Add($private:ctrl); }catch{Write-Error "Unable to add a control of $($ControlType) type to the step $($StepNumber)";} } #endregion function Add-ControlToStep #region function Add-CodeToStep function Add-CodeToStep # Adds a scriptblock to a wizard step { param( [Parameter(Mandatory=$true)] [string]$StepNumber, [Parameter(Mandatory=$true)] [scriptblock]$StepCode ) $script:stepScriptblocks[$StepNumber] = $StepCode; } #endregion function Add-CodeToStep #region Step data arrays [psobject]$script:steps = New-Object psobject; [System.Collections.ArrayList]$script:stepDisplayNames = ` New-Object System.Collections.ArrayList; [System.Collections.ArrayList]$script:stepDescriptions = ` New-Object System.Collections.ArrayList; [System.Collections.ArrayList]$script:stepScriptblocks = ` New-Object System.Collections.ArrayList; #endregion Step data arrays #endregion the Wizard steps #region events of the wizard controls #region resizing #region button sizes function Set-NextButtonLeft { param([int]$Left) $script:btnNext.Left = $Left; $script:btnFinish.Left = $Left; } function Set-NextButtonTop { param([int]$Top) $script:btnNext.Top = $Top; $script:btnFinish.Top = $Top; } function Set-BackButtonLeft { param([int]$Left) $script:btnBack.Left = $Left; $script:btnCancel.Left = $Left; } function Set-BackButtonTop { param([int]$Top) $script:btnBack.Top = $Top; $script:btnCancel.Top = $Top; } #endregion button sizes #region function Initialize-WizardControls function Initialize-WizardControls <# .SYNOPSIS The Initialize-WizardControls function sets the wizard common controls to predefined positions. .DESCRIPTION The Initialize-WizardControls function does the following: - sets Top, Left, Width and Height properties of the wizard buttons - positions of the step labels - sets the SplitterDistance property of both Splitcontainer controls .EXAMPLE PS C:\> Initialize-WizardControls This example shows how to step the wizard forward. .INPUTS No input .OUTPUTS No output #> { # Set sizes of buttons $script:btnNext.Height = $script:constButtonHeight; $script:btnNext.Width = $script:constButtonWidth; $script:btnBack.Height = $script:constButtonHeight; $script:btnBack.Width = $script:constButtonWidth; $script:btnFinish.Height = $script:btnNext.Height; $script:btnFinish.Width = $script:btnNext.Width; $script:btnCancel.Height = $script:btnBack.Height; $script:btnCancel.Width = $script:btnBack.Width; # SplitterDistance of the outer split container # in other words, the area where Next and Back buttons are placed $script:splitOuter.SplitterDistance = ` $script:splitOuter.Height - ` $script:constButtonAreaHeight; # Placements of the buttons if ($script:constWizardRigthToLeft) { Set-NextButtonLeft 10; Set-BackButtonLeft ($script:constButtonWidth + 20); } else { Set-NextButtonLeft ($script:splitOuter.Width - ` $script:constButtonWidth - 10); Set-BackButtonLeft ($script:splitOuter.Width - ` $script:constButtonWidth - ` $script:constButtonWidth - 20); } Set-NextButtonTop (($script:constButtonAreaHeight - $script:constButtonHeight) / 2); Set-BackButtonTop (($script:constButtonAreaHeight - $script:constButtonHeight) / 2); # SplitterDistance of the inner split container # this is the place where step name is placed $script:splitInner.SplitterDistance = ` $script:constButtonAreaHeight * 1.5; # Step Display Name and Description labels $script:lblStepDisplayName.Width = ` $script:splitInner.Panel1.Width - ` $script:constLabelStepDisplayNameLeft * 2; $script:lblStepDescription.Width = ` $script:splitInner.Panel1.Width - ` $script:constLabelStepDescriptionLeft * 2; # Refresh after we have changed placements of the controls [System.Windows.Forms.Application]::DoEvents(); } #endregion function Initialize-WizardControls [System.EventHandler]$script:hndlFormResize = {Initialize-WizardControls;} [System.EventHandler]$script:hndlFormLoad = {Initialize-WizardControls;} #[System.Windows.Forms.MouseEventHandler]$script:hndlSplitMouseMove = ` # {Initialize-WizardControls;} # Initial arrange on Load form. $script:frmWizard.add_Load($script:hndlFormLoad); #$script:frmWizard.add_Resize($script:hndlFormResize); $script:splitOuter.add_Resize($script:hndlFormResize); #endregion resizing #region steps #region function Invoke-WizardStep function Invoke-WizardStep <# .SYNOPSIS The Invoke-WizardStep function sets active panel on the wizard form. .DESCRIPTION The Invoke-WizardStep function does the following: - changes internal variable $script:currentStep - sets/resets .Enabled property of btnNext and btnBack - changes .Dock and .Left properties of every panel .PARAMETER Forward The optional parameter Forward is used to point out the direction the wizard goes. .EXAMPLE PS C:\> Invoke-WizardStep -Forward $true This example shows how to step the wizard forward. .INPUTS System.Boolean .OUTPUTS No output #> { [CmdletBinding()] param( [Parameter(Mandatory=$false)] [bool]$Forward = $true ) Begin{} Process{ # run the step code try{ $script:stepScriptblocks[$script:currentStep].Invoke(); } catch {return;} #Cancel step if ($Forward) { Enable-BackButton; if ($script:currentStep -lt ($script:wzdSteps.Count - 1)) {$script:currentStep++;} if ($script:currentStep -lt ($script:wzdSteps.Count - 1)) { # step forward but not the last step Enable-NextButton; } else { # the last step Enable-FinishButton; } } else { # go backward Enable-NextButton; if ($script:currentStep -gt 0) {$script:currentStep--;} if ($script:currentStep -gt 0) { # step backward but not the first one Enable-BackButton; } else { # the first step Enable-CancelButton; } } for($private:i = 0; $private:i -lt $script:wzdSteps.Count; $private:i++) { if ($private:i -ne $script:currentStep) { $script:wzdSteps[$private:i].Dock = ` [System.Windows.Forms.DockStyle]::None; $script:wzdSteps[$private:i].Left = 10000; } else { $script:wzdSteps[$private:i].Dock = ` [System.Windows.Forms.DockStyle]::Fill; $script:wzdSteps[$private:i].Left = 0; } } $script:lblStepDisplayName.Text = ` $script:stepDisplayNames[$script:currentStep]; $script:lblStepDescription.Text = ` $script:stepDescriptions[$script:currentStep]; } End{} } #endregion function Invoke-WizardStep #region function Initialize-WizardStep function Initialize-WizardStep # This is the selfsufficient function doing all the necessary # calculations for controls on each panel. # Also from the code can be seen how to address the panel you are interesting in # using the 'enumeration' created earlier # for example, $script:wzdSteps[$script:steps.Welcome] { $script:lblStepDisplayName.Text = ` $script:stepDisplayNames[$script:currentStep]; $script:lblStepDescription.Text = ` $script:stepDescriptions[$script:currentStep]; } #endregion function Initialize-WizardStep #region $hndlStepForward [System.EventHandler]$hndlStepForward = { # serve controls' data Initialize-WizardStep; # switch panels Invoke-WizardStep $true; } #endregion $hndlStepForward #region $hndlStepBack [System.EventHandler]$hndlStepBack = { # switch panels Invoke-WizardStep $false; } #endregion $hndlStepBack #region $hndlFinish [System.EventHandler]$hndlFinish = { # ask for exit [System.Windows.Forms.DialogResult]$private:res = ` [System.Windows.Forms.MessageBox]::Show("Are you sure?", ` "Question on exit", ` [System.Windows.Forms.MessageBoxButtons]::YesNo); if ($res -eq [System.Windows.Forms.DialogResult]::Yes){ # close the wizard $frmWizard.Close(); } } #endregion $hndlFinish #region $hndlCancel [System.EventHandler]$hndlCancel = { # close the wizard $frmWizard.Close(); } #endregion $hndlCancel #region wizard buttons' clicks $script:btnNext.add_Click($script:hndlStepForward); $script:btnBack.add_Click($script:hndlStepBack); $script:btnFinish.add_Click($script:hndlFinish); $script:btnCancel.add_Click($script:hndlCancel); #endregion wizard buttons' clicks #endregion steps #endregion events of the wizard controls #region wizard initialization #region function Initialize-Wizard function Initialize-Wizard # This is one more selfsufficient function written to make # the latest preparations for the form run { #region control settings $script:frmWizard.Width = $script:constWizardWidth; $script:frmWizard.Height = $script:constWizardHeight; #endregion control settings Initialize-WizardStep; } #endregion function Initialize-Wizard #endregion wizard initialization #endregion the resulting wizard # Step 1: Welcome New-WizardStep 'Welcome' ` 'This is the first step' ` 'Welcome to the PowerShell Wizard, the our dear customer!'; # Add a label # Please note that we can use the enumeration $steps which is being created runtime # on a call of the New-WizardStep function Add-ControlToStep $steps.Welcome ` System.Windows.Forms.Label ` 'lblWelcome' 20 10 50 300 ` 'This Wizard carries you through the steps you need to collect the files from a given path'; # Step 2 New-WizardStep 'Input' ` 'Step Two' ` 'Here you type some in controls, plz'; # Add a label Add-ControlToStep $steps.Input ` System.Windows.Forms.Label ` 'lblInput' 20 10 20 300 ` 'Please type the path to a catalog'; # Add a text box Add-ControlToStep $steps.Input ` System.Windows.Forms.TextBox ` 'txtInput' 40 10 20 300 # Add the code which requires that text box was not empty Add-CodeToStep $steps.Input ` -StepCode { [string]$private:path = ` $wzdSteps[$steps.Input].Controls['txtInput'].Text; if ($private:path.Length -eq 0) { # stop the step throw; } if (-not [System.IO.Directory]::Exists($private:path)) { # stop the step throw; } } # Step 3 New-WizardStep 'Progress' ` 'The third one' ` 'Wait, please. Sip a coffee' ; # Add a progress bar Add-ControlToStep $steps.Progress ` 'System.Windows.Forms.ProgressBar' ` 'pbDir' 200 50 100 400 Add-CodeToStep $steps.Progress ` -StepCode { # set progress bar maximum $wzdSteps[$steps.Progress].Controls['pbDir'].Minimum = 0; $wzdSteps[$steps.Progress].Controls['pbDir'].Value = 0; $wzdSteps[$steps.Progress].Controls['pbDir'].Maximum = ` (Get-ChildItem $wzdSteps[$steps.Input].Controls['txtInput'].Text).Length; # clear the list box (from the next step) $wzdSteps[$steps.Output].Controls['lbxFiles'].Items.Clear(); # add file names to the list box Get-ChildItem $wzdSteps[$steps.Input].Controls['txtInput'].Text | %{ $wzdSteps[$steps.Progress].Controls['pbDir'].Value++; $wzdSteps[$steps.Output].Controls['lbxFiles'].Items.Add($_.Name); } } # Step 4 New-WizardStep 'Output' 'Fourth' ` 'Now awake and read the output'; # Add a list box Add-ControlToStep $steps.Output ` System.Windows.Forms.ListBox ` lbxFiles 50 50 300 400 # Step 5: Finish New-WizardStep 'Finish' 'Finish!' 'Bye!'; Initialize-Wizard; # Set the second step as active Invoke-WizardStep -Forward $true; $script:frmWizard.ShowDialog() | Out-Null;