I was on Twitter the other day and saw that @juneb_get_help tweeted asking people to talk about things they had built with Powershell Studio. I responded, but I thought it really deserved a blog post to talk about the app and the kinds of things you can do with it.
The Business Problem
The company I work for installs software inside their clients’ networks. We provide the client with a detailed spec of what we would like the servers they give us to look like, but it is very common for servers to be just a little bit wrong. Since we use Puppet for configuration management, it’s important to verify that servers are in the correct state before we accept them.
The app I wrote is dropped onto a single server in the environment, and when provided a list of servers to test, invokes a series of remote jobs that instruct each box to test itself for correct state and report back the results.
Why Powershell Studio
As we will see shortly, Powershell studio is a good solution for this because it allows you take advantage of the convenience of Powershell for administering servers, while also taking advantage of event based programming and easily wrapping scripts in a GUI that suits the needs of slightly less technical users.
The App
In the screenshot above you can see the design view for the app. The app consists of a main form, a tab control with tabs for the stages of testing, with sub controls for data. In the tab you see here I have a couple text boxes for Active Directory account names. Before the machines are tested I use the app to ensure that the Active Directory accounts we asked for have been created.
Notice on the right side the name says textBoxSQLAccount. That is going to be the name of a variable created by the Studio to reference that text box.
In this screenshot we are adding an event handler to the lower text box. When you click ok you get a code block for the object’s event handler.
If you’re familiar with Powershell syntax you will recognize a variable with a code block. In the background, when Powershell Studio builds the app, it ensures that code is executed when that event is fired just as the name implies. To keep things neat, the code is factored out into a function and I simply pass the textbox, since both boxes need to run the same check when the Leave event fires.
One of the cool things about Powershell Studio is how the really great auto complete incentivises you to write good code. The function Validate-Textbox was defined with a parameter of type System.Windows.Forms.Textbox. Powershell Studio knows it, so when auto complete comes up, it only shows me the variables of the correct type.
The code in Validate-TextBox is as follows:
function Verify-ADObject
{
param (
[ string ] $name
)
if ((([ ADSISearcher]"Name= $( $name ) " ) .FindOne() , ([ ADSISearcher]"SAMAccountName= $( $name ) " ) .FindOne() -ne $NULL )[ 0])
{
Write-Output $true
}
else
{
Write-Output $false
}
}
function Validate-TextBox
{
param (
[ System.Windows.Forms.TextBox]$textBox
)
if ( Verify-ADObject -name $textBox .Text)
{
$textBox .BackColor = 'LimeGreen'
}
else
{
$textBox .BackColor = 'Red'
}
}
With these event handlers in place, every time my cursor leaves the text box the Validate-TextBox function is called with the current text box as the parameter, which then passes the text value to Verify-ADObject. If an object is returned we know it exists and the textbox turns green, and if not we get a red box. Since this is a demo app this is enough, but in reality we would want some checks in place to ensure such things as that a value actually exists in the text box in case someone just clicked on the text box by accident and then left.
Some of you at this point may be wagging your fingers at me, and believe me I know. I shouldn’t be making network calls on the UI thread. I don’t disagree, but I’m also lazy, and this check happens very quickly so I’m not worried about it for these text boxes. In the next tab though I’m going to do it right.
This tab is going to install the roles we need on the server. In reality Puppet can take care of this for us, but this is a decent way to demo the next concept I want to cover. Combining Powershell jobs and event driven programming we can spin off long running processes into a background thread that keeps our UI from blocking while the servers go about their business.
The IIS tab consists of little more than a DataviewGrid control, a progress bar and a button. After we add servers to the list in the dataview control we handle the button’s Click event to start installing the Web Server roles that we need.
$buttonInstall_Click ={
Begin -Install $datagridview1 -bar $progressbar1
}
Earlier in the form code that you don’t see here I created a timer object, but didn’t start it. In the next code section I’ll get the list of servers from the DataGridview control, spin off the jobs to the background, and then add a code block to the timer’s tick event before starting the timer. The effect is that the invocation of Begin-Install completes and the UI thread is unblocked. In the background however, the timer is still running and with each tick it calls a code block that checks on the status of the jobs we spun off in the background. The Get-JobStatus function that gets called knows how to find the Datagridview control and update the appropriate rows as the servers report their status back to the background job. Here’s the code.
function Begin -Install
{
$scriptBlock = {
Add-WindowsFeature Web-WebServer, Web-Mgmt-Console, Web-App-Dev, Web-Asp-Net45, Web-Mgmt-Console
}
$style = New-Object System.Windows.Forms.DataGridViewCellStyle
$style .BackColor = 'Yellow'
$datagridview1 .Rows | Where-Object { $_ .Cells[0].Value.length -gt 0} | ForEach -Object{ $_ .Cells[1, 2] } | ForEach -Object{ $_ .style = $style }
$progressbar1 .Style = 'Marquee'
$progressbar1 .Visible = $true
foreach ( $server in ( $datagridview1 .Rows | Where-Object { $_ .Cells[0].value.length -gt 0 } | ForEach -Object{ $_ .Cells[0].Value }))
{
Invoke-Command -ScriptBlock $scriptBlock -ComputerName $server -AsJob -JobName "installRoles_ $server "
}
$timer .add_Tick({ Get-JobStatus })
$timer .Start()
}
function Get-JobStatus
{
if ( $jobs = Get-Job | where state -NE 'running' )
{
foreach ( $job in $jobs )
{
$results = Receive-Job $job
Remove-Job $job
$row = $datagridview1 .Rows | Where-Object { $_ .Cells[0].Value -eq $results .PScomputername }
$style = New-Object System.Windows.Forms.DataGridViewCellStyle
if ( $results .success)
{
$style .BackColor = 'LimeGreen'
$row .cells[1].style = $style
$row .cells[1].value = 1
}
else
{
$style .BackColor = 'Red'
$row .cells[1].style = $style
}
}
}
else
{
if ( !( Get-Job))
{
$timer .Stop()
$timer .Dispose()
$progressbar1 .Visible = $False
}
}
}
In the real version the process of just finding the list of servers and spinning off the jobs can take a noticeable amount of time, so really the entire operation should be in the background, but for a demo, just spinning off the remote portion makes it easier to follow what’s happening.
Troubleshooting
One of the advantages to using Powershell studio forms apps like this is that it allows me to export the entire app not as an executable finished product, but just as a big long script. The reason this can be nice is if there are bugs with the program, I don’t have to install the entire Powershell Studio on a client machine to debug. I export the script, set my break points in Powershell ISE, and I can debug on the clients machines using only the tools freely available on any Windows Server. With that in mind, the entire demo app is pasted below as a script. It’s very very rough, just thrown together over the course of a few hours to make this blog post happen, so please don’t there are bugs, and there’s no error handling, etc, I know. but if you want to let me know of any better ways to do this stuff, or just have general thoughts, don’t hesitate to let me know. You can find me on twitter @RandomNoun7
#------------------------------------------------------------------------
# Source File Information (DO NOT MODIFY)
# Source ID: 1d9b2aea-ddc6-4129-a0a5-07da6d38202b
# Source File: C:\Users\bhurt\Documents\SAPIEN\PowerShell Studio 2015\Projects\Tabbed App Demo\Tabbed App Demo.psproj
#------------------------------------------------------------------------
<#
.NOTES
--------------------------------------------------------------------------------
Code generated by: SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.99
Generated on: 3/7/2016 10:25 AM
Generated by:
Organization:
--------------------------------------------------------------------------------
.DESCRIPTION
Script generated by PowerShell Studio 2015
#>
#region Source: Startup.pss
#----------------------------------------------
#region Import Assemblies
#----------------------------------------------
[ void][Reflection.Assembly]::Load( 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][Reflection.Assembly]::Load( 'System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][Reflection.Assembly]::Load( 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][Reflection.Assembly]::Load( 'System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][Reflection.Assembly]::Load( 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
[ void][Reflection.Assembly]::Load( 'System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][Reflection.Assembly]::Load( 'System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
[ void][Reflection.Assembly]::Load( 'System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][Reflection.Assembly]::Load( 'System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
#endregion Import Assemblies
#Define a Param block to use custom parameters in the project
#Param ($CustomParameter)
function Main {
<#
.SYNOPSIS
The Main function starts the project application.
.PARAMETER Commandline
$Commandline contains the complete argument string passed to the script packager executable.
.NOTES
Use this function to initialize your script and to call GUI forms.
.NOTES
To get the console output in the Packager (Forms Engine) use:
$ConsoleOutput (Type: System.Collections.ArrayList)
#>
Param ([ String ] $Commandline )
#--------------------------------------------------------------------------
#TODO: Add initialization script here (Load modules and check requirements)
#--------------------------------------------------------------------------
if (( Call-MainForm_psf) -eq 'OK' )
{
}
$global :ExitCode = 0 #Set the exit code for the Packager
}
#endregion Source: Startup.pss
#region Source: MainForm.psf
function Call-MainForm_psf
{
#----------------------------------------------
#region Import the Assemblies
#----------------------------------------------
[ void][reflection.assembly]::Load( 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][reflection.assembly]::Load( 'System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][reflection.assembly]::Load( 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][reflection.assembly]::Load( 'System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][reflection.assembly]::Load( 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
[ void][reflection.assembly]::Load( 'System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][reflection.assembly]::Load( 'System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
[ void][reflection.assembly]::Load( 'System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
[ void][reflection.assembly]::Load( 'System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
#endregion Import Assemblies
#----------------------------------------------
#region Generated Form Objects
#----------------------------------------------
[ System.Windows.Forms.Application]::EnableVisualStyles()
$MainForm = New-Object 'System.Windows.Forms.Form'
$TabControl = New-Object 'System.Windows.Forms.TabControl'
$ADTab = New-Object 'System.Windows.Forms.TabPage'
$tablelayoutpanel1 = New-Object 'System.Windows.Forms.TableLayoutPanel'
$textBoxSQLAccount = New-Object 'System.Windows.Forms.TextBox'
$textBoxAppPool = New-Object 'System.Windows.Forms.TextBox'
$labelSQLServiceAccount = New-Object 'System.Windows.Forms.Label'
$labelAppPoolServiceAccoun = New-Object 'System.Windows.Forms.Label'
$IISTab = New-Object 'System.Windows.Forms.TabPage'
$buttonInstall = New-Object 'System.Windows.Forms.Button'
$progressbar1 = New-Object 'System.Windows.Forms.ProgressBar'
$datagridview1 = New-Object 'System.Windows.Forms.DataGridView'
$ServerName = New-Object 'System.Windows.Forms.DataGridViewTextBoxColumn'
$IISInstalled = New-Object 'System.Windows.Forms.DataGridViewCheckBoxColumn'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
#endregion Generated Form Objects
#----------------------------------------------
# User Generated Script
#----------------------------------------------
$timer = New-Object System.Windows.Forms.Timer
$MainForm_Load ={
#TODO: Initialize Form Controls here
}
$MainForm_Shown ={
}
$textBoxSQLAccount_Leave ={
Validate-TextBox -textBox $textBoxSQLAccount
}
$textBoxAppPool_Leave ={
Validate-TextBox -textBox $textBoxAppPool
}
#region Control Helper Functions
function Load-DataGridView
{
<#
.SYNOPSIS
This functions helps you load items into a DataGridView.
.DESCRIPTION
Use this function to dynamically load items into the DataGridView control.
.PARAMETER DataGridView
The DataGridView control you want to add items to.
.PARAMETER Item
The object or objects you wish to load into the DataGridView's items collection.
.PARAMETER DataMember
Sets the name of the list or table in the data source for which the DataGridView is displaying data.
#>
Param (
[ ValidateNotNull()]
[ Parameter( Mandatory = $true )]
[ System.Windows.Forms.DataGridView]$DataGridView ,
[ ValidateNotNull()]
[ Parameter( Mandatory = $true )]
$Item ,
[ Parameter( Mandatory = $false )]
[ string ] $DataMember
)
$DataGridView .SuspendLayout()
$DataGridView .DataMember = $DataMember
if ( $Item -is [ System.ComponentModel.IListSource]`
-or $Item -is [ System.ComponentModel.IBindingList] -or $Item -is [ System.ComponentModel.IBindingListView] )
{
$DataGridView .DataSource = $Item
}
else
{
$array = New-Object System.Collections.ArrayList
if ( $Item -is [ System.Collections.IList])
{
$array .AddRange( $Item )
}
else
{
$array .Add( $Item )
}
$DataGridView .DataSource = $array
}
$DataGridView .ResumeLayout()
}
function ConvertTo-DataTable
{
<#
.SYNOPSIS
Converts objects into a DataTable.
.DESCRIPTION
Converts objects into a DataTable, which are used for DataBinding.
.PARAMETER InputObject
The input to convert into a DataTable.
.PARAMETER Table
The DataTable you wish to load the input into.
.PARAMETER RetainColumns
This switch tells the function to keep the DataTable's existing columns.
.PARAMETER FilterWMIProperties
This switch removes WMI properties that start with an underline.
.EXAMPLE
$DataTable = ConvertTo-DataTable -InputObject (Get-Process)
#>
[ OutputType([ System.Data.DataTable])]
param (
[ ValidateNotNull()]
$InputObject ,
[ ValidateNotNull()]
[ System.Data.DataTable]$Table ,
[ switch ] $RetainColumns ,
[ switch ] $FilterWMIProperties )
if ( $Table -eq $null )
{
$Table = New-Object System.Data.DataTable
}
if ( $InputObject -is [ System.Data.DataTable])
{
$Table = $InputObject
}
else
{
if ( -not $RetainColumns -or $Table .Columns.Count -eq 0)
{
#Clear out the Table Contents
$Table .Clear()
if ( $InputObject -eq $null ){ return } #Empty Data
$object = $null
#find the first non null value
foreach ( $item in $InputObject )
{
if ( $item -ne $null )
{
$object = $item
break
}
}
if ( $object -eq $null ) { return } #All null then empty
#Get all the properties in order to create the columns
foreach ( $prop in $object .PSObject.Get_Properties())
{
if ( -not $FilterWMIProperties -or -not $prop .Name.StartsWith( '__' )) #filter out WMI properties
{
#Get the type from the Definition string
$type = $null
if ( $prop .Value -ne $null )
{
try { $type = $prop .Value.GetType() } catch {}
}
if ( $type -ne $null ) # -and [System.Type]::GetTypeCode($type) -ne 'Object')
{
[ void]$table .Columns.Add( $prop .Name, $type )
}
else #Type info not found
{
[ void]$table .Columns.Add( $prop .Name)
}
}
}
if ( $object -is [ System.Data.DataRow])
{
foreach ( $item in $InputObject )
{
$Table .Rows.Add( $item )
}
return @( ,$Table )
}
}
else
{
$Table .Rows.Clear()
}
foreach ( $item in $InputObject )
{
$row = $table .NewRow()
if ( $item )
{
foreach ( $prop in $item .PSObject.Get_Properties())
{
if ( $table .Columns.Contains( $prop .Name))
{
$row .Item( $prop .Name) = $prop .Value
}
}
}
[ void]$table .Rows.Add( $row )
}
}
return @( ,$Table )
}
#endregion
$buttonInstall_Click ={
Begin -Install $datagridview1 -bar $progressbar1
}
#region AD Functions
function Verify-ADObject
{
param (
[ string ] $name
)
if ((([ ADSISearcher]"Name= $( $name ) " ) .FindOne() , ([ ADSISearcher]"SAMAccountName= $( $name ) " ) .FindOne() -ne $NULL )[ 0])
{
Write-Output $true
}
else
{
Write-Output $false
}
}
function Validate-TextBox
{
param (
[ System.Windows.Forms.TextBox]$textBox
)
if ( Verify-ADObject -name $textBox .Text)
{
$textBox .BackColor = 'LimeGreen'
}
else
{
$textBox .BackColor = 'Red'
}
}
#endregion
#region Install Tab Functions
function Begin -Install
{
$scriptBlock = {
Add-WindowsFeature Web-WebServer, Web-Mgmt-Console, Web-App-Dev, Web-Asp-Net45, Web-Mgmt-Console
}
$style = New-Object System.Windows.Forms.DataGridViewCellStyle
$style .BackColor = 'Yellow'
$datagridview1 .Rows | Where-Object { $_ .Cells[0].Value.length -gt 0} | ForEach -Object{ $_ .Cells[1, 2] } | ForEach -Object{ $_ .style = $style }
$progressbar1 .Style = 'Marquee'
$progressbar1 .Visible = $true
foreach ( $server in ( $datagridview1 .Rows | Where-Object { $_ .Cells[0].value.length -gt 0 } | ForEach -Object{ $_ .Cells[0].Value }))
{
Invoke-Command -ScriptBlock $scriptBlock -ComputerName $server -AsJob -JobName "installRoles_ $server "
}
$timer .add_Tick({ Get-JobStatus })
$timer .Start()
}
function Get-JobStatus
{
if ( $jobs = Get-Job | where state -NE 'running' )
{
foreach ( $job in $jobs )
{
$results = Receive-Job $job
Remove-Job $job
$row = $datagridview1 .Rows | Where-Object { $_ .Cells[0].Value -eq $results .PScomputername }
$style = New-Object System.Windows.Forms.DataGridViewCellStyle
if ( $results .success)
{
$style .BackColor = 'LimeGreen'
$row .cells[1].style = $style
$row .cells[1].value = 1
}
else
{
$style .BackColor = 'Red'
$row .cells[1].style = $style
}
}
}
else
{
if ( !( Get-Job))
{
$timer .Stop()
$timer .Dispose()
$progressbar1 .Visible = $False
}
}
}
#endregion
# --End User Generated Script--
#----------------------------------------------
#region Generated Events
#----------------------------------------------
$Form_StateCorrection_Load =
{
#Correct the initial state of the form to prevent the .Net maximized form issue
$MainForm .WindowState = $InitialFormWindowState
}
$Form_StoreValues_Closing =
{
#Store the control values
$script :MainForm_textBoxSQLAccount = $textBoxSQLAccount .Text
$script :MainForm_textBoxAppPool = $textBoxAppPool .Text
$script :MainForm_datagridview1 = $datagridview1 .SelectedCells
}
$Form_Cleanup_FormClosed =
{
#Remove all event handlers from the controls
try
{
$textBoxSQLAccount .remove_Leave( $textBoxSQLAccount_Leave )
$textBoxAppPool .remove_Leave( $textBoxAppPool_Leave )
$buttonInstall .remove_Click( $buttonInstall_Click )
$MainForm .remove_Load( $MainForm_Load )
$MainForm .remove_Shown( $MainForm_Shown )
$MainForm .remove_Load( $Form_StateCorrection_Load )
$MainForm .remove_Closing( $Form_StoreValues_Closing )
$MainForm .remove_FormClosed( $Form_Cleanup_FormClosed )
}
catch [ Exception]
{ }
}
#endregion Generated Events
#----------------------------------------------
#region Generated Form Code
#----------------------------------------------
$MainForm .SuspendLayout()
$TabControl .SuspendLayout()
$ADTab .SuspendLayout()
$tablelayoutpanel1 .SuspendLayout()
$IISTab .SuspendLayout()
#
# MainForm
#
$MainForm .Controls.Add( $TabControl )
$MainForm .ClientSize = '476, 452'
$MainForm .Name = 'MainForm'
$MainForm .StartPosition = 'CenterScreen'
$MainForm .Text = 'Tabbed App Demo'
$MainForm .UseWaitCursor = $True
$MainForm .add_Load( $MainForm_Load )
$MainForm .add_Shown( $MainForm_Shown )
#
# TabControl
#
$TabControl .Controls.Add( $ADTab )
$TabControl .Controls.Add( $IISTab )
$TabControl .Location = '12, 35'
$TabControl .Name = 'TabControl'
$TabControl .SelectedIndex = 0
$TabControl .Size = '452, 405'
$TabControl .TabIndex = 0
#
# ADTab
#
$ADTab .Controls.Add( $tablelayoutpanel1 )
$ADTab .Location = '4, 22'
$ADTab .Name = 'ADTab'
$ADTab .Padding = '3, 3, 3, 3'
$ADTab .Size = '444, 379'
$ADTab .TabIndex = 0
$ADTab .Text = 'Active Directory'
$ADTab .UseVisualStyleBackColor = $True
#
# tablelayoutpanel1
#
$tablelayoutpanel1 .Controls.Add( $textBoxSQLAccount , 1, 0)
$tablelayoutpanel1 .Controls.Add( $textBoxAppPool , 1, 1)
$tablelayoutpanel1 .Controls.Add( $labelSQLServiceAccount , 0, 0)
$tablelayoutpanel1 .Controls.Add( $labelAppPoolServiceAccoun , 0, 1)
$tablelayoutpanel1 .ColumnCount = 2
$System_Windows_Forms_ColumnStyle_1 = New-Object 'System.Windows.Forms.ColumnStyle' ( 'Percent' , 50)
[ void]$tablelayoutpanel1 .ColumnStyles.Add( $System_Windows_Forms_ColumnStyle_1 )
$System_Windows_Forms_ColumnStyle_2 = New-Object 'System.Windows.Forms.ColumnStyle' ( 'Percent' , 50)
[ void]$tablelayoutpanel1 .ColumnStyles.Add( $System_Windows_Forms_ColumnStyle_2 )
$tablelayoutpanel1 .Location = '6, 6'
$tablelayoutpanel1 .Name = 'tablelayoutpanel1'
$tablelayoutpanel1 .RowCount = 2
$System_Windows_Forms_RowStyle_3 = New-Object 'System.Windows.Forms.RowStyle' ( 'Percent' , 50)
[ void]$tablelayoutpanel1 .RowStyles.Add( $System_Windows_Forms_RowStyle_3 )
$System_Windows_Forms_RowStyle_4 = New-Object 'System.Windows.Forms.RowStyle' ( 'Percent' , 50)
[ void]$tablelayoutpanel1 .RowStyles.Add( $System_Windows_Forms_RowStyle_4 )
$tablelayoutpanel1 .Size = '432, 373'
$tablelayoutpanel1 .TabIndex = 0
#
# textBoxSQLAccount
#
$textBoxSQLAccount .Anchor = 'Bottom, Left'
$textBoxSQLAccount .Location = '219, 163'
$textBoxSQLAccount .Name = 'textBoxSQLAccount'
$textBoxSQLAccount .Size = '210, 20'
$textBoxSQLAccount .TabIndex = 0
$textBoxSQLAccount .add_Leave( $textBoxSQLAccount_Leave )
#
# textBoxAppPool
#
$textBoxAppPool .Location = '219, 189'
$textBoxAppPool .Name = 'textBoxAppPool'
$textBoxAppPool .Size = '210, 20'
$textBoxAppPool .TabIndex = 1
$textBoxAppPool .add_Leave( $textBoxAppPool_Leave )
#
# labelSQLServiceAccount
#
$labelSQLServiceAccount .Anchor = 'Bottom, Right'
$labelSQLServiceAccount .Location = '3, 163'
$labelSQLServiceAccount .Name = 'labelSQLServiceAccount'
$labelSQLServiceAccount .Size = '210, 23'
$labelSQLServiceAccount .TabIndex = 2
$labelSQLServiceAccount .Text = 'SQL Server Service Account'
$labelSQLServiceAccount .TextAlign = 'MiddleRight'
#
# labelAppPoolServiceAccoun
#
$labelAppPoolServiceAccoun .Anchor = 'Top, Right'
$labelAppPoolServiceAccoun .Location = '3, 186'
$labelAppPoolServiceAccoun .Name = 'labelAppPoolServiceAccoun'
$labelAppPoolServiceAccoun .Size = '210, 23'
$labelAppPoolServiceAccoun .TabIndex = 3
$labelAppPoolServiceAccoun .Text = 'App Pool Service Account'
$labelAppPoolServiceAccoun .TextAlign = 'MiddleRight'
#
# IISTab
#
$IISTab .Controls.Add( $buttonInstall )
$IISTab .Controls.Add( $progressbar1 )
$IISTab .Controls.Add( $datagridview1 )
$IISTab .Location = '4, 22'
$IISTab .Name = 'IISTab'
$IISTab .Padding = '3, 3, 3, 3'
$IISTab .Size = '444, 379'
$IISTab .TabIndex = 1
$IISTab .Text = 'IIS'
$IISTab .UseVisualStyleBackColor = $True
#
# buttonInstall
#
$buttonInstall .Location = '7, 207'
$buttonInstall .Name = 'buttonInstall'
$buttonInstall .Size = '75, 23'
$buttonInstall .TabIndex = 2
$buttonInstall .Text = 'Install'
$buttonInstall .UseVisualStyleBackColor = $True
$buttonInstall .add_Click( $buttonInstall_Click )
#
# progressbar1
#
$progressbar1 .Location = '7, 311'
$progressbar1 .Name = 'progressbar1'
$progressbar1 .Size = '431, 23'
$progressbar1 .TabIndex = 1
$progressbar1 .Visible = $False
#
# datagridview1
#
$System_Windows_Forms_DataGridViewCellStyle_5 = New-Object 'System.Windows.Forms.DataGridViewCellStyle'
$System_Windows_Forms_DataGridViewCellStyle_5 .Alignment = 'MiddleCenter'
$System_Windows_Forms_DataGridViewCellStyle_5 .BackColor = 'Control'
$System_Windows_Forms_DataGridViewCellStyle_5 .Font = 'Microsoft Sans Serif, 8.25pt'
$System_Windows_Forms_DataGridViewCellStyle_5 .ForeColor = 'WindowText'
$System_Windows_Forms_DataGridViewCellStyle_5 .SelectionBackColor = 'Highlight'
$System_Windows_Forms_DataGridViewCellStyle_5 .SelectionForeColor = 'HighlightText'
$System_Windows_Forms_DataGridViewCellStyle_5 .WrapMode = 'True'
$datagridview1 .ColumnHeadersDefaultCellStyle = $System_Windows_Forms_DataGridViewCellStyle_5
$datagridview1 .ColumnHeadersHeightSizeMode = 'AutoSize'
[ void]$datagridview1 .Columns.Add( $ServerName )
[ void]$datagridview1 .Columns.Add( $IISInstalled )
$datagridview1 .Location = '6, 32'
$datagridview1 .Name = 'datagridview1'
$datagridview1 .ScrollBars = 'None'
$datagridview1 .Size = '432, 150'
$datagridview1 .TabIndex = 0
#
# ServerName
#
$ServerName .HeaderText = 'Server Name'
$ServerName .Name = 'ServerName'
#
# IISInstalled
#
$IISInstalled .AutoSizeMode = 'Fill'
$IISInstalled .HeaderText = 'Roles Installed'
$IISInstalled .Name = 'IISInstalled'
$IISInstalled .ReadOnly = $True
$IISTab .ResumeLayout()
$tablelayoutpanel1 .ResumeLayout()
$ADTab .ResumeLayout()
$TabControl .ResumeLayout()
$MainForm .ResumeLayout()
#endregion Generated Form Code
#----------------------------------------------
#Save the initial state of the form
$InitialFormWindowState = $MainForm .WindowState
#Init the OnLoad event to correct the initial state of the form
$MainForm .add_Load( $Form_StateCorrection_Load )
#Clean up the control events
$MainForm .add_FormClosed( $Form_Cleanup_FormClosed )
#Store the control values when form is closing
$MainForm .add_Closing( $Form_StoreValues_Closing )
#Show the Form
return $MainForm .ShowDialog()
}
#endregion Source: MainForm.psf
#region Source: Globals.ps1
#--------------------------------------------
# Declare Global Variables and Functions here
#--------------------------------------------
#Sample function that provides the location of the script
function Get-ScriptDirectory
{
<#
.SYNOPSIS
Get-ScriptDirectory returns the proper location of the script.
.OUTPUTS
System.String
.NOTES
Returns the correct path within a packaged executable.
#>
[ OutputType([ string ])]
param ()
if ( $hostinvocation -ne $null )
{
Split-Path $hostinvocation .MyCommand.path
}
else
{
Split-Path $script :MyInvocation.MyCommand.Path
}
}
#Sample variable that provides the location of the script
[ string ] $ScriptDirectory = Get-ScriptDirectory
#endregion Source: Globals.ps1
#Start the application
Main ( $CommandLine )