PoshCode Archive  Artifact [65454314fc]

Artifact 65454314fceca133407aa5903a390af58aaba932c27c4309381edc10fd57ff21:

  • File Anmin-Launcher-v2.ps1 — part of check-in [11d159c35f] at 2018-06-10 14:06:29 on branch trunk — I fixed a few major problems I noticed in the original version. (user: Geoff Guynn size: 48523)

# encoding: ascii
# api: powershell
# title: Anmin Launcher (v2!)
# description: I fixed a few major problems I noticed in the original version. 
# version: 2.0
# type: script
# author: Geoff Guynn
# license: CC0
# function: Add-Program
# x-poshcode-id: 6030
# x-archived: 2016-09-08T23:14:02
# x-published: 2016-09-26T00:41:00
#
# 1. The launcher couldn’t properly elevate AND run as a different user when passed credentials, so I completely rewrote the event_Click_ProgramButton function to fix this.
# 2. The domain information was not properly automatically gathering in some enterprise configurations, so I modified that function to do a more thorough check and added better error handling.
# 3. I refactored the code to be more friendly when you press Ctrl+M in ISE. This should make it much easier to get around and see functions, objects, etc at a glance if you’re thinking of modifying things.
#
<# Begin ---- Global Variables - Script Limited Scope ---- #>
#Script Author Information
$script:ProgramName = "Admin Launcher"
$script:ProgramDate = "4 Oct 2011"
$script:ProgramAuthor = "Geoffrey Guynn"
$script:ProgramAuthorEmail = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String("Z2VvZmZyZXlAZ3V5bm4ub3Jn"))

#Configuration Variables
$script:xmlConfig = New-Object System.Data.DataSet("AdminLauncher")
$script:xmlConfigFile = "$Env:AppData\AdminLauncher\config.xml"

#Script Information
$script:WorkingFileName = $MyInvocation.MyCommand.Definition
$script:WorkingDirectory = Split-Path $script:WorkingFileName -Parent
$script:ScriptVersion = "2.0.1.0"

#Password Container (to keep plaintext passwords out of memory)
$script:SecurePassword = New-Object System.Security.SecureString
<# End ---- Global Variables - Script Limited Scope ---- #>

<# Begin ---- Debugging Options ---- #>
#DebugPreference - SilentlyContinue is the default.
$script:DebugPreference = "SilentlyContinue"
#ErrorActionPreference - Continue is the default.
$Script:ErrorActionPreference = "Continue"
<# End ---- Debugging Options ---- #>

<# Begin ---- Startup Checks ---- #>
Function IsAdmin{ #Is the current user an administrator
    $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object System.Security.Principal.WindowsPrincipal($id)
    return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
Function RestartScript{ #Restart the script.
    param(
		[string]$Verb = "open"
	)
    $ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo
    $ProcessStartInfo.UseShellExecute = $True
    $ProcessStartInfo.WorkingDirectory = $script:WorkingDirectory
    $ProcessStartInfo.FileName =  "powershell.exe"
    $ProcessStartInfo.Arguments = "-version `"2.0`" -sta -NonInteractive -noLogo -WindowStyle `"Hidden`" -ExecutionPolicy `"Unrestricted`" -file `"$script:WorkingFileName`""
    $ProcessStartInfo.Verb = $verb
    $ProcessStartInfo.WindowStyle = "Hidden"
    
    #Debug Options - Disable under normal execution.
    if($script:DebugPreference -ne "SilentlyContinue"){
		$ProcessStartInfo.Arguments = "-version `"2.0`" -sta -ExecutionPolicy `"Unrestricted`" -file `"$script:WorkingFileName`""
		$ProcessStartInfo.WindowStyle = "Normal"
	}	
	
	try{
        $Process = [System.Diagnostics.Process]::Start($ProcessStartInfo)
    }
    catch [System.ComponentModel.Win32Exception]{
    }
}
Function LoadCheck{
    $verb = $Null
    #The GUI requires a single thread apartmentstate configuration.
    if($host.Runspace.ApartmentState -ne [System.Threading.ApartmentState]::STA){
        $Verb = "open"
    }
	#The script could break under powershell version 3.
	if ($host.version.major -ne 2){ #Restart powershell with version 2.0
		$Verb = "open"
	}
    #Check to see if the launcher is running under an elevated token. This is a UAC check.
    if (-not(IsAdmin)){
        $Verb = "runas"
    }
    #If one of the conditions above was true, re-launch the application to fix the problem.
    if ($Verb -ne $Null){
        RestartScript $Verb
        #Terminate the old script, so it doesn't load another launcher.
        break
    }
}
#Run the loadcheck function.
LoadCheck
<# End ---- Startup Checks ---- #>

<# Begin ---- Import Global Objects ---- #>
#The old "LoadWithPartialName" can break in the future, Add-Type is the recommended best practice.
#[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
#[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
Add-Type -AssemblyName "System.Windows.Forms"
Add-Type -AssemblyName "System.Drawing"
<# End ---- Import Global Objects ---- #>

<# Begin ---- Windows Forms Object Declarations - Main Form ---- #>
$form_MainLauncher = New-Object System.Windows.Forms.Form
$tlp_Main = New-Object System.Windows.Forms.TableLayoutPanel
$cb_Main_Domain = New-Object System.Windows.Forms.ComboBox
#$ep_Main = New-Object System.Windows.Forms.ErrorProvider
$gb_Main_Programs = New-Object System.Windows.Forms.GroupBox
$gb_Main_RunAs = New-Object System.Windows.Forms.GroupBox
$lbl_Main_Domain = New-Object System.Windows.Forms.Label
$lbl_Main_Password = New-Object System.Windows.Forms.Label
$lbl_Main_UserName = New-Object System.Windows.Forms.Label
$ms_Main_MenuStrip = New-Object System.Windows.Forms.MenuStrip
$mtb_Main_Password = New-Object System.Windows.Forms.MaskedTextBox
$ss_Main_StatusStrip = New-Object System.Windows.Forms.StatusStrip
$tb_Main_UserName = New-Object System.Windows.Forms.TextBox
$tsmi_Main_Configure = New-Object System.Windows.Forms.ToolStripMenuItem
$tsmi_Main_Exit = New-Object System.Windows.Forms.ToolStripMenuItem
$tsmi_Main_Export = New-Object System.Windows.Forms.ToolStripMenuItem
$tsmi_Main_File = New-Object System.Windows.Forms.ToolStripMenuItem
$tsmi_Main_Import = New-Object System.Windows.Forms.ToolStripMenuItem
$tsmi_Main_Reset = New-Object System.Windows.Forms.ToolStripMenuItem
$tssl_Main_StatusStripLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
$tt_Main_Tooltip = New-Object System.Windows.Forms.Tooltip
<# End ---- Windows Forms Object Declarations - Main Form ---- #>

<# Begin ---- Windows Forms Object Declarations - Config Form ---- #>
$form_Config = New-Object System.Windows.Forms.Form
$tlp_Config = New-Object System.Windows.Forms.TableLayoutPanel
$btn_Config_Done = New-Object System.Windows.Forms.Button
$gb_Config_Programs = New-Object System.Windows.Forms.GroupBox

#Programs Datagridview
$dgv_Config_Programs = New-Object System.Windows.Forms.DataGridView
$dgc_Config_Arguments = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
$dgc_Config_Command = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
$dgc_Config_Name = New-Object System.Windows.Forms.DataGridViewTextBoxColumn
<# End ---- Windows Forms Object Declarations - Config Form ---- #>

<# Begin ---- Windows Forms Object Declarations - Dialogs ---- #>
$dlg_OpenFile = New-Object System.Windows.Forms.OpenFileDialog
$dlg_SaveFile = New-Object System.Windows.Forms.SaveFileDialog
<# End ---- Windows Forms Object Declarations - Dialogs ---- #>

<# Begin ---- Windows Form Objects - Suspend Layouts ---- #>
$form_MainLauncher.SuspendLayout()
$form_Config.SuspendLayout()
$tlp_Main.SuspendLayout()
$tlp_Config.SuspendLayout()
$gb_Main_Programs.SuspendLayout()
$gb_Main_RunAs.SuspendLayout()
$gb_Config_Programs.SuspendLayout()
$ss_Main_StatusStrip.SuspendLayout()
$ms_Main_MenuStrip.SuspendLayout()
<# End ---- Windows Form Objects - Suspend Layouts ---- #>

<# Begin ---- Object Definitions - Main Form ---- #>
$form_MainLauncher | % {
    $_.AutoScaleDimensions = New-Object System.Drawing.SizeF(6, 13)
    $_.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Font
    $_.Autosize = $True
    $_.BackColor = [System.Drawing.SystemColors]::Control
    $_.Controls.Add($tlp_Main)
    $_.Controls.Add($ss_Main_StatusStrip)
    $_.Controls.Add($ms_Main_MenuStrip)
    $_.MainMenuStrip = $ms_Main_MenuStrip
    $_.MaximumSize = New-Object System.Drawing.Size(275, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height)
    $_.MinimumSize = New-Object System.Drawing.Size(275, 194)
    $_.Name = "form_MainLauncher"
    $_.Size = New-Object System.Drawing.Size(275, 194)
    $_.Text = "Admin Launcher"
    $_.TopMost = $True
}
$tlp_Main | % {
	$_.Autosize = $True
	$_.AutosizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink
	$_.BackColor = [System.Drawing.SystemColors]::GradientInactiveCaption
	$_.ColumnCount = 1
	$_.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
	$_.Controls.Add($gb_Main_Programs, 0, 1)
	$_.Controls.Add($gb_Main_RunAs, 0, 0)
	$_.Dock = [System.Windows.Forms.DockStyle]::Fill
	$_.Location = New-Object System.Drawing.Point(0, 24)
	$_.Name = "tlp_Main"
	$_.RowCount = 2;
	$_.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 109))) | Out-Null
	$_.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
	$_.Size = New-Object System.Drawing.Size(259, 295)
	$_.TabIndex = 9
}
$cb_Main_Domain | % {
	$tt_Main_Tooltip.SetToolTip($_, "Select a domain from the list.") | Out-Null
	$_.Anchor =([System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right)
	$_.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
	$_.FormattingEnabled = $True
	$_.Location = New-Object System.Drawing.Point(58, 71)
	$_.Name = "cb_Main_Domain"
	$_.Size = New-Object System.Drawing.Size(189, 21)
	$_.TabIndex = 4
}
$gb_Main_Programs | % {
	$_.Autosize = $True
	$_.AutosizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink
	$_.Dock = [System.Windows.Forms.DockStyle]::Fill
	$_.Location = New-Object System.Drawing.Point(3, 112)
	$_.Name = "gb_Main_Programs"
	$_.Size = New-Object System.Drawing.Size(253, 180)
	$_.TabIndex = 9
	$_.TabStop = $False
	$_.Text = "Programs"
}
$gb_Main_RunAs | % {
	$_.Controls.Add($lbl_Main_Domain)
	$_.Controls.Add($cb_Main_Domain)
	$_.Controls.Add($mtb_Main_Password)
	$_.Controls.Add($tb_Main_UserName)
	$_.Controls.Add($lbl_Main_Password)
	$_.Controls.Add($lbl_Main_UserName)
	$_.Dock = [System.Windows.Forms.DockStyle]::Fill
	$_.Location = New-Object System.Drawing.Point(3, 3)
	$_.Name = "gb_Main_RunAs"
	$_.Size = New-Object System.Drawing.Size(253, 103)
	$_.TabIndex = 10
	$_.TabStop = $False
	$_.Text = "Run-As Credentials"
}
$lbl_Main_Domain | % {
	$_.Location = New-Object System.Drawing.Point(6, 74)
	$_.Name = "lbl_Main_Domain"
	$_.Size = New-Object System.Drawing.Size(46, 13)
	$_.TabIndex = 5
	$_.Text = "Domain"
}
$lbl_Main_Password | % {
	$_.Location = New-Object System.Drawing.Point(6, 48)
	$_.Name = "lbl_Main_Password"
	$_.Size = New-Object System.Drawing.Size(56, 13)
	$_.TabIndex = 1
	$_.Text = "Password:"
}
$lbl_Main_UserName | % {
	$_.Location = New-Object System.Drawing.Point(6, 22)
	$_.Name = "lbl_Main_UserName"
	$_.Size = New-Object System.Drawing.Size(60, 13)
	$_.TabIndex = 0
	$_.Text = "UserName:"
}
$ms_Main_MenuStrip | % {
	$_.BackColor = [System.Drawing.SystemColors]::Control
	$_.Items.Add($tsmi_Main_File) | Out-Null
	$_.Location = New-Object System.Drawing.Point(0, 0)
	$_.Name = "ms_Main_MenuStrip"
	$_.RenderMode = [System.Windows.Forms.ToolStripRenderMode]::System
	$_.Size = New-Object System.Drawing.Size(259, 24)
	$_.TabIndex = 3
	$_.Text = $Null
}
$mtb_Main_Password | % {
	$tt_Main_Tooltip.SetToolTip($_, "Enter a password.") | Out-Null
	$_.Anchor =([System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right)
	$_.Location = New-Object System.Drawing.Point(68, 45)
	$_.Name = "mtb_Main_Password"
	$_.PasswordChar = '*'
	$_.Size = New-Object System.Drawing.Size(179, 20)
	$_.TabIndex = 3
}
$ss_Main_StatusStrip | % {
	$_.BackColor = [System.Drawing.SystemColors]::Control
	$_.Items.Add($tssl_Main_StatusStripLabel) | Out-Null
	$_.Location = New-Object System.Drawing.Point(0, 319)
	$_.Name = "ss_Main_StatusStrip"
	$_.Size = New-Object System.Drawing.Size(259, 22)
	$_.TabIndex = 2
	$_.Text = $Null
}
$tb_Main_UserName | % {
	$tt_Main_Tooltip.SetToolTip($_, "Enter a username.") | Out-Null
	$_.Anchor =([System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right)
	$_.Location = New-Object System.Drawing.Point(72, 19)
	$_.Name = "tb_Main_UserName"
	$_.Size = New-Object System.Drawing.Size(175, 20)
	$_.TabIndex = 2

}
$tsmi_Main_Configure | % {
	$_.Name = "tsmi_Main_Configure"
	$_.Size = New-Object System.Drawing.Size(155, 22)
	$_.Text = "Configure"
}
$tsmi_Main_Exit | % {
	$_.Name = "tsmi_Main_Exit"
	$_.Size = New-Object System.Drawing.Size(155, 22)
	$_.Text = "Exit"
}
$tsmi_Main_Export | % {
	$_.Name = "tsmi_Main_Export"
	$_.Size = New-Object System.Drawing.Size(155, 22)
	$_.Text = "Export Settings"
}
$tsmi_Main_File | % {
	$_.DropDownItems.AddRange([System.Windows.Forms.ToolStripItem[]](
		$tsmi_Main_Configure,
		$tsmi_Main_Import,
		$tsmi_Main_Export,
		$tsmi_Main_Reset,
		$tsmi_Main_Exit
	))
	$_.Name = "fileToolStripMenuItem"
	$_.Size = New-Object System.Drawing.Size(37, 20)
	$_.Text = "File"
}
$tsmi_Main_Import | % {
	$_.Name = "tsmi_Main_Import"
	$_.Size = New-Object System.Drawing.Size(155, 22)
	$_.Text = "Import Settings"
}
$tsmi_Main_Reset | % {
	$_.Name = "tsmi_Main_Reset"
	$_.Size = New-Object System.Drawing.Size(155, 22)
	$_.Text = "Reset Default Settings"
}
$tssl_Main_StatusStripLabel | % {
	$_.BackColor = [System.Drawing.SystemColors]::Control
	$_.Name = "tssl_Main_StatusStripLabel"
	$_.Size = New-Object System.Drawing.Size(118, 17)
	$_.Text = $Null
}
<# End ---- Object Definitions - Main Form ---- #>

<# Begin ---- Object Definitions - Config Form ---- #>
$form_Config | % {
	$_.AutoScaleDimensions = New-Object System.Drawing.SizeF(6, 13)
	$_.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Font
	$_.ClientSize = New-Object System.Drawing.Size(472, 375)
	$_.Controls.Add($tlp_Config)
	$_.MinimumSize = New-Object System.Drawing.Size(825, 450)
	$_.Name = "form_Config"
	$_.Text = "Config - The script will save new rows and update the launcher if the entry has a name and command."
	$_.TopMost = $True
}
$tlp_Config | % {
	$_.BackColor = [System.Drawing.SystemColors]::GradientInactiveCaption
	$_.ColumnCount = 4
	$_.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
	$_.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 75))) | Out-Null
	$_.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 75))) | Out-Null
	$_.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 75))) | Out-Null
	$_.Controls.Add($gb_Config_Programs, 0, 0)
	$_.Controls.Add($btn_Config_Done, 3, 1)
	$_.Dock = [System.Windows.Forms.DockStyle]::Fill
	$_.Location = New-Object System.Drawing.Point(0, 0)
	$_.Name = "tlp_Config"
	$_.RowCount = 2
	$_.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
	$_.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 27))) | Out-Null
	$_.Size = New-Object System.Drawing.Size(472, 375)
	$_.TabIndex = 0
}
$btn_Config_Done | % {
	$_.Anchor = [System.Windows.Forms.AnchorStyles]::Left
	$_.BackColor = [System.Drawing.SystemColors]::Control
	$_.Location = New-Object System.Drawing.Point(400, 351)
	$_.Name = "btn_Config_Done"
	$_.Size = New-Object System.Drawing.Size(69, 21)
	$_.TabIndex = 1
	$_.Text = "Done"
	$_.UseVisualStyleBackColor = $True
}
$gb_Config_Programs | % {
	$tlp_Config.SetColumnSpan($_, 4)
	$_.Controls.Add($dgv_Config_Programs)
	$_.Dock = [System.Windows.Forms.DockStyle]::Fill
	$_.Location = New-Object System.Drawing.Point(3, 3)
	$_.Name = "gb_Config_Programs"
	$_.Size = New-Object System.Drawing.Size(1, 1)
	$_.TabIndex = 0
	$_.TabStop = $False
	$_.Text = "Programs"
}

#Programs Datagridview
$dgv_Config_Programs | % {
	$_.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
	$_.BackgroundColor = [System.Drawing.SystemColors]::GradientInactiveCaption
	$_.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::AutoSize
	$_.Columns.AddRange([System.Windows.Forms.DataGridViewColumn[]]($dgc_Config_Name,$dgc_Config_Command, $dgc_Config_Arguments)) | Out-Null
	$_.Dock = [System.Windows.Forms.DockStyle]::Fill
	$_.Location = New-Object System.Drawing.Point(3, 16)
	$_.Name = "dgv_Config_Programs"
	$_.RowHeadersVisible = $False
	$_.SelectionMode = [System.Windows.Forms.DataGridViewSelectionMode]::FullRowSelect
	$_.Size = New-Object System.Drawing.Size(850, 400)
	$_.TabIndex = 0
}
$dgc_Config_Arguments | % {
	$_.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
	$_.DataPropertyName = "Arguments"
	$_.FillWeight = 35
	$_.HeaderText = "Arguments"
	$_.Name = "dgc_Config_Arguments"
}
$dgc_Config_Command | % {
	$_.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
	$_.DataPropertyName = "Command"
	$_.FillWeight = 60
	$_.HeaderText = "Command"
	$_.Name = "dgc_Config_Command"
}
$dgc_Config_Name | % {
	$_.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
	$_.DataPropertyName = "Name"
	$_.FillWeight = 20
	$_.HeaderText = "Name"
	$_.Name = "dgc_Config_Name"
}
<# End ---- Object Definitions - Config Form ---- #>

<# Begin ---- Object Definitions - Dialogs ---- #>
$dlg_OpenFile | % {
	$_.InitialDirectory = "$env:USERPROFILE\Documents"
	$_.Filter = "Xml Document(*.xml)|*.xml"
}
$dlg_SaveFile | % {
	$_.InitialDirectory = "$env:USERPROFILE\Documents"
	$_.Filter = "Xml Document(*.xml)|*.xml"
	$_.FileName = "AdminLauncher_Settings.xml"
}
<# End ---- Object Definitions - Dialogs ---- #>

<# Begin ---- Admin Launcher Helper Functions ---- #>
Function Add-Program{
    param(
        [System.Data.DataSet]$DataSet
        , #The name of the program
        [String]$Name
        , #The command for the program
        [string]$Command = $Null
        , #Any arguments required by the program
        [string]$Arguments = $Null
    )
    $htProgram = @{
        DataSet = $DataSet
        TableName = "Programs"
        RowData = @{
            Name = $Name
            Command = $Command
            Arguments = $Arguments
        }
    }
    
    Add-Row @htProgram
}
Function Add-ProgramButtons{
	#Suspend layouts so the screen doesn't flicker as each element is removed and then added.
    $form_MainLauncher.SuspendLayout()
    $gb_Main_Programs.SuspendLayout()
	
    while ($gb_Main_Programs.haschildren){ #Remove all old programs. Disposing them avoids a memory leak as the objects are recreated.
        $gb_Main_Programs.controls | % {
            $_.Dispose()
            $gb_Main_Programs.Controls.Remove($_)
        }
    }
    #Sort the list of programs by name, then create a button for each one.
    foreach ($Program in $script:xmlconfig.Tables["Programs"].Select($Null, "Name Asc")){
        if ($gb_Main_Programs.HasChildren){#As we add buttons, we need to find the lowest one and properly offset by 22 from it. We'll sort the buttons by their y coordinate and calculate the lowest on each iteration.
            $yCoordinate = (@($gb_Main_Programs.Controls | Sort-Object @{Expression={$_.location.Y}} -descending)[0].Location.Y) + 22
        }
        else{ #The offset we'd use for our first button.
			$yCoordinate = 16
		}
		
        #Generate tooltip information.
        $tt_Main_TooltipString = "Launch $($Program.Name)"
        if (![DBNull]::Value.Equals($Program.Command) -and $Program.Command){$tt_Main_TooltipString += "`r  Command: $($Program.Command)"}
        if (![DBNull]::Value.Equals($Program.Arguments) -and $Program.Arguments){$tt_Main_TooltipString += "`r  Arguments: $($Program.Arguments)"}
        
		#Create the button at our calculated coordinates.
        $TempButton = New-Object System.Windows.Forms.Button
		$TempButton | % {
			$_.Anchor =([System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right)
			$_.Autosize = $True
			$_.AutosizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink
			$_.BackColor = [System.Drawing.SystemColors]::Control
			$_.Location = New-Object System.Drawing.Point(6, $yCoordinate)
			$_.Name = ("btn_" + ($Program.Name -Replace " ","_")) #Replace all spaces with underlines. (This avoids errors.)
			$_.Size = New-Object System.Drawing.Size(241, 23)
			$_.TabIndex = 0
			$_.Text = $Program.Name
			$_.UseVisualStyleBackColor = $True
			
			#Event Handler to launch the program properly.
			$_.Add_Click({event_Click_ProgramButton $this $_})
			
			#Connect the tooltip to the button.
			$tt_Main_Tooltip.SetToolTip($_, $tt_Main_TooltipString)
			
			#Add the button to our form.
			$gb_Main_Programs.Controls.Add($_)			
		}
    }
    $gb_Main_Programs.PerformLayout()
    $form_MainLauncher.ResumeLayout($False)
    $form_MainLauncher.PerformLayout()
    if ($form_MainLauncher.WindowState -ne [System.Windows.Forms.FormWindowState]::Maximumized){
        AutoSize-Form $form_MainLauncher
    }
}
Function Add-Row{
    param([System.Data.DataSet]$DataSet, [string]$TableName, [hashtable]$RowData)
    $NewRow = $DataSet.Tables[$TableName].NewRow()
    $RowData.keys | % {$NewRow.$_ = $RowData.$_}
    $DataSet.Tables[$TableName].Rows.Add($NewRow)
}
Function Add-Table{
    param([System.Data.Dataset]$DataSet, [string]$TableName, [array]$Columns)
    $dtTable = New-Object System.Data.DataTable($TableName)
    $dtTable.Columns.AddRange(@($Columns))
    $DataSet.Tables.Add($dtTable)
}
Function Apply-Config{
    Add-ProgramButtons
}
Function Autosize-Form{
    param(
		[System.Windows.Forms.Form]$Form
	)
	
    $Form.AutosizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink
    $Form.AutosizeMode = [System.Windows.Forms.AutoSizeMode]::GrowOnly
}
Function Create-Config{
    #Create a brand new dataset.
    $script:xmlConfig = New-Object System.Data.DataSet("AdminLauncher")
    #Determine program files location for 32bit/64bit apps.
    switch ([intptr]::Size -eq 8){
        $True{$ProgramFiles = (Get-Item "${env:ProgramFiles(x86)}").FullName}
        $False{$ProgramFiles = (Get-Item "${env:ProgramFiles}").FullName}
    }
    #Define table structures for the config tables.
    $htSettings = @{
        DataSet=$script:xmlConfig
        TableName="Settings"
        Columns=@("Program", "ProgramDate", "Version", "Author", "AuthorEmail", "UserName", "UserDomain")
    }
    $htPrograms = @{
        DataSet=$script:xmlConfig
        TableName="Programs"
        Columns=@("Name", "Command", "Arguments")
    }
    #Add base configuration tables to the dataset.
    Add-Table @htSettings
    Add-Table @htPrograms
    #Capture configuration data.
	if ("$env:USERDNSDOMAIN"){
		$UserDomain = "$env:USERDNSDOMAIN"
	}
	else{
		$UserDomain = "$env:COMPUTERNAME"
	}
	
    $htSettings = @{
        DataSet=$script:xmlConfig
        TableName="Settings"
        RowData = @{
            Program=$script:ProgramName
			ProgramDate=$script:ProgramDate
            Version=$script:ScriptVersion
            Author=$script:ProgramAuthor
			AuthorEmail =$script:ProgramAuthorEmail
            UserName="$env:USERNAME"
            UserDomain=$UserDomain
        }
    }
    #Add configuration data to the dataset.
    Add-Row @htSettings
    #Define each default program and add it to the dataset.
    Add-Program -DataSet $script:xmlConfig -Name "Active Directory" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\dsa.msc"
    Add-Program -DataSet $script:xmlConfig -Name "ADSI Editor" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\adsiedit.msc"
    Add-Program -DataSet $script:xmlConfig -Name "Computer Management" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\compmgmt.msc /s"
    Add-Program -DataSet $script:xmlConfig -Name "DHCP Configuration" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\dhcpmgmt.msc"
    Add-Program -DataSet $script:xmlConfig -Name "DNS Configuration" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\dnsmgmt.msc /s"
    Add-Program -DataSet $script:xmlConfig -Name "Group Policy Management" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\gpmc.msc"
    Add-Program -DataSet $script:xmlConfig -Name "Powershell Console" -Command "$pshome\powershell.exe" -Arguments "-STA"
    Add-Program -DataSet $script:xmlConfig -Name "Powershell ISE" -Command "$pshome\powershell_ISE.exe"
    Add-Program -DataSet $script:xmlConfig -Name "Print Management" -Command "$env:WINDIR\system32\mmc.exe" -Arguments "$env:WINDIR\system32\printmanagement.msc"
    Add-Program -DataSet $script:xmlConfig -Name "Remote Desktop" -Command "$env:WINDIR\system32\mstsc.exe"
}
Function Get-DomainInfo{
    #Get the current forest.
    try{ #This can fail if we're not on a domain. Let's make sure to catch exceptions gracefully.
        $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
        $MemberDomains = @()
        $ForestSites = @($Forest.Sites)
        
        if ($ForestSites){
            $ForestSites | % {
                if ($_.Domains){
                    $_.Domains | % {
                        if ($MemberDomains -notcontains $_.Name){
                            $MemberDomains += $_.Name.ToUpper()
                        }
                    }
                }
            }
        }
        else{ #Get other domains from forest trusts.
            $ForestTrusts = @($Forest.GetAllTrustRelationships())
            if ($ForestTrusts){
                $DomainNames = $ForestTrusts |
                    ? {$_.TrustType -eq [System.DirectoryServices.ActiveDirectory.TrustType]::Forest} |
                    % {$_.TrustedDomainInformation |
                    % {$_.dnsName}}
                if ($DomainNames){
                    $DomainNames | % {
                        $MemberDomains += $_.ToUpper()
                    }
                }
            }
        }
    }
    catch{ #We don't need to do anything if this fails. The array will be generated regardless.
    }
    finally{
		if ($MemberDomains){
			#Add the user's current domain to the array if not already present.
			if ($MemberDomains -notcontains $env:USERDNSDOMAIN){
				$MemberDomains += $env:USERDNSDOMAIN
			}
            
			#Add the domain information to the dropdown list.
			$cb_Main_Domain.Items.AddRange(($MemberDomains | Sort-Object -unique))
			
			if ($script:xmlConfig.Tables["Cache"]){ #Use cached information if we have any.
				if (![DBNull]::Value.Equals($script:xmlConfig.Tables["Cache"].Rows[0].Domain) -and $script:xmlConfig.Tables["Cache"].Rows[0].Domain){
					$cb_Main_Domain.Text = $script:xmlConfig.Tables["Cache"].Rows[0].Domain
				}
			}
            elseif ($env:USERDNSDOMAIN){ #Use domain info if available.
                $cb_Main_Domain.Text = $env:USERDNSDOMAIN
            }
            else{ #Use item 1 from the list.
                $cb_Main_Domain.Text = $cb_Main_Domain.Items[0]
            }
		}
        elseif ($env:USERDNSDOMAIN){ #Use domain info if available.
            $cb_Main_Domain.Items.Add($env:USERDNSDOMAIN)
            $cb_Main_Domain.Text = $env:USERDNSDOMAIN
        }
		else{ #There is no domain membership, use local computer name.
			$cb_Main_Domain.Items.Add($env:COMPUTERNAME)
			$cb_Main_Domain.Text = $env:COMPUTERNAME
		}
    }
}
Function Load-Config{
    if (Test-Path $script:xmlConfigFile){ #The config exists, read it.
        $script:xmlConfig.ReadXml($script:xmlConfigFile) | Out-Null
        $script:xmlConfig.AcceptChanges()
    }
    else{ #The config doesn't exist, create it.
        Create-Config
        if (!(Test-Path (Split-Path -Path $script:xmlConfigFile -Parent))){
            mkdir (Split-Path -Path $script:xmlConfigFile -Parent)
        }
        [void]$script:xmlConfig.WriteXml($script:xmlConfigFile)
        $script:xmlConfig.AcceptChanges()
    }
    #Validate the config file according to our schema.
    if (Validate-Config $script:xmlConfig){
        if ($script:xmlConfig.Tables["Cache"]){
            if (![DBNull]::Value.Equals($script:xmlConfig.Tables["Cache"].Rows[0].Name) -and $script:xmlConfig.Tables["Cache"].Rows[0].Name){
                $tb_Main_UserName.Text = $script:xmlConfig.Tables["Cache"].Rows[0].Name
            }
            if (![DBNull]::Value.Equals($script:xmlConfig.Tables["Cache"].Rows[0].Domain) -and $script:xmlConfig.Tables["Cache"].Rows[0].Domain){
                $cb_Main_Domain.Text = $script:xmlConfig.Tables["Cache"].Rows[0].Domain
            }
        }
        Apply-Config
    }
    #Something is wrong with the config file, offer to recreate it or exit if it is not re-created.
    else{
        $htPrompt = @{
            Caption="Invalid Configuration File!"
            Message="An incompatible configuration file exists!`nWould you like to re-build one from the default settings?"
            DialogOptions="YesNo"
        }
        $DialogResult = [System.Windows.Forms.MessageBox]::Show($htPrompt.Message, $htPrompt.Caption, $htPrompt.DialogOptions)
        if ($DialogResult -eq [System.Windows.Forms.DialogResult]::Yes){
            rm -force $script:xmlConfigFile -EA "SilentlyContinue"
            Load-Config
        }
        else{$form_MainLauncher.Close()}
    }
}
Function Validate-Config{
    param([System.Data.DataSet]$DataSet)
    try{
		#Read settings table from the XML dataset.
		$SettingsInfo = $DataSet.Tables["Settings"].Rows[0]
		
        if ($SettingsInfo.Program -eq $script:ProgramName){ #ProgramName matches
			if ($SettingsInfo.Author -eq $script:ProgramAuthor){ #ProgramAuthor matches
				return $True
			}
			else{
				return $False
			}
		}
		else{
			return $False
		}
	}
    catch{ #Any exceptions will read as a failed validation.
		return $False
	}
}
<# End ---- Admin Launcher Helper Functions ---- #>

<# Begin ---- Admin Launcher Event Handler Functions ---- #>
Function event_Load_form_MainLauncher{
    param(
        $object,
        $e
    )
    #Populate data fields.
    $tssl_Main_StatusStripLabel.Text = "Launcher Ready"
    #Load the configuration
    Load-Config
    Get-DomainInfo
}
Function event_Click_tsmi_Main_Import{
    param(
        $object,
        $e
    )
	
    $DialogResult = $dlg_OpenFile.ShowDialog()
    try{
        if ($DialogResult -eq [System.Windows.Forms.DialogResult]::OK){
            $ImportData = New-Object System.Data.DataSet
            [void]$ImportData.ReadXml($dlg_OpenFile.FileName)
            if (Validate-Config $ImportData){
                $htPrompt = @{
                    Caption="Import Settings"
                    Message="You have chosen to import settings, this will erase all existing settings.`r`n`r`nAre you sure you want to do this?"
                    DialogOptions="YesNo"
                }
                $DialogResult = [System.Windows.Forms.MessageBox]::Show($htPrompt.Message, $htPrompt.Caption, $htPrompt.DialogOptions)
                if ($DialogResult -eq [System.Windows.Forms.DialogResult]::Yes){
                    $ImportData.Tables.Remove("Settings")
                    $ImportData.AcceptChanges()
                    $htSettings = @{
                        DataSet=$ImportData
                        TableName="Settings"
                        Columns=@("Program", "Version", "Author", "UserName", "UserDomain")
                    }
                    Add-Table @htSettings
                    $htSettings = @{
                        DataSet=$ImportData
                        TableName="Settings"
                        RowData = @{
                            Program=$script:ProgramName
                            Version=$script:ScriptVersion
                            Author=$script:ProgramAuthor
                            UserName="$env:USERNAME"
                            UserDomain="$env:USERDNSDOMAIN"
                        }
                    }
                    Add-Row @htSettings
                    $script:xmlConfig = $ImportData.Copy()
                    Apply-Config
                    $tssl_Main_StatusStripLabel.Text = "Import successful!"
                }
            }
            else {Throw "Invalid configuration file!"}
        }
    }
    catch{
        $htError = @{
            Caption="Import Settings"
            Message=("Your settings could not be imported! The following error was reported.`r`n`r`n" + $error[0].exception.message)
            DialogOptions="OkCancel"
        }
        [System.Windows.Forms.MessageBox]::Show($htError.Message, $htError.Caption, $htError.DialogOptions)
    }
}
Function event_Click_tsmi_Main_Export{
    param(
        $object,
        $e
    )
	
    if ($dlg_SaveFile.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){
        try{
            $xmlSave = $script:xmlConfig.Copy()
            if ($xmlSave.Tables["Settings"].Rows[0].Username -ne $Null) {$xmlSave.Tables["Settings"].Rows[0].Username = $Null}
            if ($xmlSave.Tables["Settings"].Rows[0].UserDomain -ne $Null) {$xmlSave.Tables["Settings"].Rows[0].UserDomain = $Null}
            if ($xmlSave.Tables["Cache"] -ne $Null){
                try{
                    $xmlSave.Tables["Cache"].Rows[0].Name = $Null
                    $xmlSave.Tables["Cache"].Rows[0].Domain = $Null
                }
                catch{}
            }
            [void]$xmlSave.WriteXml($dlg_SaveFile.FileName)
        }
        catch{}
    }
}
Function event_Click_tsmi_Main_Reset{
    param(
        $object,
        $e
    )
	
    $htPrompt = @{
        Caption="Reset all settings"
        Message="You have chosen to reset all settings to default. This will remove ALL changes you have made. Are you sure you want to do this?"
        DialogOptions="YesNo"
    }
    $DialogResult = [System.Windows.Forms.MessageBox]::Show($htPrompt.Message, $htPrompt.Caption, $htPrompt.DialogOptions)
    if ($DialogResult -eq [System.Windows.Forms.DialogResult]::Yes){
        if (Test-Path $script:xmlConfigFile){
            Remove-Item  -Path $script:xmlConfigFile -Force
        }
		Load-Config
		Autosize-Form $form_MainLauncher
    }
}
Function event_Activated_form_Config{
    param(
        $object,
        $e
    )
	
    #Sort the table by program name. (So each time config opens, they're alphabetical)
    $script:xmlConfig.Tables["Programs"].DefaultView.Sort = "Name"
    
    #Bind the gridview to the config file's programs table.
    $dgv_Config_Programs.DataSource = $script:XmlConfig.Tables["Programs"]   
}
Function event_Deactivate_form_Config{
    param(
        $object,
        $e
    )

    if ($object.DialogResult -eq [System.Windows.Forms.DialogResult]::OK){ #If the user pressed the done button, commit the changes to our main config.
        
        #Make sure to remove any programs that do not have a name.
        foreach ($Row in $script:xmlConfig.Tables["Programs"].Rows){
            if ([DBNull]::Value.Equals($Row.Name) -or !$Row.Name){
                $Row.Delete()
            }
        }
        if ($script:xmlConfig.HasChanges()){
            $script:xmlConfig.Tables["Programs"].AcceptChanges()
            [void]$script:xmlConfig.WriteXml($script:xmlConfigFile)
            Add-ProgramButtons $script:xmlConfig
        }
    }
    else{
        if ($script:xmlConfig.HasChanges()){
            $script:xmlConfig.Tables["Programs"].RejectChanges()
        }
    }
}
Function event_Click_ProgramButton{
    param(
		$object,
		$e
	)
    
    #Reference the xml program that this button represents.
    $CurrentProgram = $script:xmlconfig.Tables["Programs"] | ? {$object.Text -eq $_.Name}
    
    $tssl_Main_StatusStripLabel.Text = "$($CurrentProgram.Name) starting..."

    #Build the command string based on what parameters we have.
    if (![DBNull]::Value.Equals($CurrentProgram.Command) `
        -and $CurrentProgram.Command
    ){
        $sb_StartElevatedProcess = {
            param(
                $CurrentProgram
            )

            $ProcInfo = New-Object System.Diagnostics.ProcessStartInfo($CurrentProgram.Command)

            if ($CurrentProgram.Arguments){
                $ProcInfo.Arguments = $CurrentProgram.Arguments
            }

            $ProcInfo.UseShellExecute = $True
            $ProcInfo.Verb = "RunAs"
            try{
                $Proc = [Diagnostics.Process]::Start($ProcInfo)
            }
            catch{
                throw $_
            }
        }
        try{
            $job_params = @{
                ScriptBlock = $sb_StartElevatedProcess
                ArgumentList = $CurrentProgram
            }
            
            if ($cb_Main_Domain.Text -and $tb_Main_UserName.Text -and $mtb_Main_Password.Text){
                $Credential = New-Object System.Management.Automation.PSCredential("$($cb_Main_Domain.Text)\$($tb_Main_UserName.Text)",$SecurePassword)
                $job_params.Credential = $Credential
            }            

            $Job = Start-Job @job_params
            Wait-Job $Job
            if ($Job.State -ne "Completed"){throw}
            $tssl_Main_StatusStripLabel.Text = "$($CurrentProgram.Name) launched."
        }
        catch{
            $tssl_Main_StatusStripLabel.Text = "$($CurrentProgram.Name) failed!"
            $htError = @{
                Caption="Program Button"
                Message=("The program failed to launch!`r`n`r`n" + $error[0].exception.message)
                DialogOptions="OkCancel"
            }
            [System.Windows.Forms.MessageBox]::Show($htError.Message, $htError.Caption, $htError.DialogOptions)
        }
    }
    #The program did not have a command defined, change the status bar to reflect this and exit the event handler.
    else{$tssl_Main_StatusStripLabel.Text = "Program: $($CurrentProgram.Name) has no command!"}
}
Function event_KeyUp_tb_Main_UserName{
    param(
		$object, 
		$e
	)

    #Update the config in memory with the new username
    if ($tb_Main_UserName.Text -and $cb_Main_Domain.Text){
        $script:xmlConfigHasChanged = $True
        if ($script:xmlConfig.Tables["Cache"]-eq $Null){
            $htCache = @{
                DataSet=$script:xmlconfig
                TableName="Cache"
                Columns=@("Name", "Domain")
            }
            $htRow = @{
                DataSet=$script:xmlconfig
                TableName="Cache"
                RowData=@{
                    Name=$tb_Main_UserName.Text
                    Domain=$cb_Main_Domain.Text
                }
            }
            Add-Table @htCache
            Add-Row @htRow
        }
        else{
            $script:xmlConfig.Tables["Cache"].Rows[0].Name = $tb_Main_UserName.Text
            $script:xmlConfig.Tables["Cache"].Rows[0].Domain = $cb_Main_Domain.Text
        }
    }
}
Function event_KeyDown_tb_Main_UserName{
    param(
		$object, 
		[System.Windows.Forms.KeyEventArgs]$e
	)

    $enumKeys = [System.Windows.Forms.Keys]

    $e.Handled = $True
    $e.SuppressKeyPress = $True
    $SelectionStart = $object.SelectionStart
    switch ($e.KeyCode){
        $EnumKeys::Enter {
            $form_MainLauncher.SelectNextControl($object, $true, $true, $true, $true)
        }
        default{
            $e.Handled = $False
            $e.SuppressKeyPress = $False
        }
    }
}
Function event_KeyDown_mtb_Main_Password{ #Catch special keys on the password box.
    param(
		$object, 
		[System.Windows.Forms.KeyEventArgs]$e
	)

    $enumKeys = [System.Windows.Forms.Keys]

    $e.Handled = $True
    $e.SuppressKeyPress = $True
    $SelectionStart = $object.SelectionStart

    switch ($e.KeyCode){
        $enumKeys::Enter {
            #Make the enter key act like a tab key.
            $form_MainLauncher.SelectNextControl($object, $true, $true, $true, $true)
        }
        $enumKeys::Back {
            #They want to delete a selection of text.
            #Loop through each letter in their selection and perform a delete for each one.
            if ($object.SelectionLength -gt 0){
                foreach($Letter in [char[]]$object.SelectedText){
                    $SecurePassword.RemoveAt($SelectionStart)
                    $object.Text = $object.Text.Remove($SelectionStart, 1)
                }
            }
            #They have no selection and are not at the beginning of the string.
            #Determine where they are and delete the preceding character.
            elseif ($object.SelectionStart -gt 0){
                $SecurePassword.RemoveAt(($object.SelectionStart - 1))
                $object.Text = $object.Text.Remove($object.SelectionStart - 1, 1)
            }
            #They are at the beginning of the string with no selection
            #Do nothing.
            else{
                return
            }
        }
        $enumKeys::Delete {
            #They are at the end of the string.
            #Do nothing
            if ($object.SelectionStart -ge $SecurePassword.Length){
                return
            }
            #They want to delete a selection of text.
            #Loop through each letter in their selection and perform a delete for each one.
            elseif ($object.SelectionLength -gt 0){
                foreach($Letter in [char[]]$object.SelectedText){
                    $SecurePassword.RemoveAt($SelectionStart)
                    $object.Text = $object.Text.Remove($SelectionStart, 1)
                }
            }
            #They have no selection and are somewhere before the end of the string.
            #Delete the letter at their location.
            else{
                $SecurePassword.RemoveAt($object.SelectionStart)
                $object.Text = $object.Text.Remove($object.SelectionStart, 1)
            }
        }
        #Control+V clipboard paste
        {$e.Modifiers -eq $enumKeys::Control -and $e.KeyCode -eq $EnumKeys::V}{
            if ([System.Windows.ClipBoard]::GetText()){ #Only paste if there is data on the clipboard.
                if ($object.SelectionStart -ge $SecurePassword.Length){ #Only paste onto the end or onto a blank password box.
                    foreach ($Letter in [char[]][System.Windows.Clipboard]::GetText()){
                        $SecurePassword.AppendChar($Letter)
                        [string]$object.Text += "*"
                    }
                    $SelectionStart = $SecurePassword.Length
                }
            }
        }
        #They pressed some other key.
        #Tell the system to handle it with the keypress event.
        default{
            $e.Handled = $False
            $e.SuppressKeyPress = $False
        }
    }
    #Reset the selection data.
    $object.SelectionStart = $SelectionStart
}
Function event_KeyPress_mtb_Main_Password{ #Catch all other keypresses on the password box.
    param(
		$object, 
		[System.Windows.Forms.KeyPressEventArgs]$e
	)
	
    $e.Handled = $True
    $SelectionStart = $object.SelectionStart
    #They are at the end of the string.
    #Append each character they enter.
    if ($object.SelectionStart -ge $SecurePassword.Length){
        $SecurePassword.AppendChar($e.KeyChar.ToString())
        [string]$object.Text += "*"
        $SelectionStart++
    }
    #They have a selection of characters.
    #Delete the selection and insert from their location.
    elseif($object.SelectionLength -gt 0){
        #Delete the selection
        foreach($Letter in [char[]]$object.SelectedText){
            $SecurePassword.RemoveAt($SelectionStart)
            $object.Text = $object.Text.Remove($SelectionStart, 1)
        }
        #Insert the single character
        $SecurePassword.InsertAt($SelectionStart, $e.KeyChar.ToString())
        $object.Text = [string]$object.Text.Insert($SelectionStart, "*")
        $SelectionStart++
    }
    #They are somewhere in the string.
    #Insert at their location.
    else{
        #Insert the single character
        $SecurePassword.InsertAt($SelectionStart, $e.KeyChar.ToString())
        $object.Text = [string]$object.Text.Insert($SelectionStart, "*")
        $SelectionStart++
    }
    #Reset the selection data.
    $object.SelectionStart = $SelectionStart
    $object.SelectionLength = 0
}
Function event_FormClosing_form_MainLauncher{
    param(
		$object, 
		$e
	)
	
    if ($script:xmlConfig.HasChanges() -eq $True){
        [void]$script:xmlConfig.WriteXml($script:xmlConfigFile)
    }
}
Function event_Click_tsmi_Main_Configure{
    param(
        $object,
        $e
    )
	
	$form_Config.ShowDialog()
}
Function event_Click_btn_Config_Done{
    param(
        $object,
        $e
    )
	
	$form_Config.DialogResult = [System.Windows.Forms.DialogResult]::OK
}
Function event_Click_tsmi_Main_Exit{
    param(
        $object,
        $e
    )
	
	$form_MainLauncher.Close()
}
<# End ---- Admin Launcher Event Handler Functions ---- #>

<# Begin ---- Admin Launcher Event Handler Definitions ---- #>
#Main Form
$form_MainLauncher.Add_Load({event_Load_form_MainLauncher $this $_})
$form_MainLauncher.Add_FormClosing({event_FormClosing_form_MainLauncher $this $_})
$cb_Main_Domain.Add_SelectionChangeCommitted({event_KeyUp_Tb_Main_UserName $this $_})
$mtb_Main_Password.Add_KeyDown({event_KeyDown_mtb_Main_Password $this $_})
$mtb_Main_Password.Add_KeyPress({event_KeyPress_mtb_Main_Password $this $_})
$tb_Main_UserName.Add_KeyUp({event_KeyUp_tb_Main_UserName $this $_})
$tb_Main_UserName.Add_KeyDown({event_KeyDown_tb_Main_UserName $this $_})
$tsmi_Main_Configure.Add_Click({event_Click_tsmi_Main_Configure $this $_})
$tsmi_Main_Import.Add_Click({event_Click_tsmi_Main_Import $this $_})
$tsmi_Main_Export.Add_Click({event_Click_tsmi_Main_Export $this $_})
$tsmi_Main_Reset.Add_Click({event_Click_tsmi_Main_Reset $this $_})
$tsmi_Main_Exit.Add_Click({event_Click_tsmi_Main_Exit $this $_})

#Config Form
$form_Config.Add_Activated({event_Activated_form_Config $this $_})
$form_Config.Add_Deactivate({event_Deactivate_form_Config $this $_})
$btn_Config_Done.Add_Click({event_Click_btn_Config_Done $this $_})
<# End ---- Admin Launcher Event Handler Definitions ---- #>

<# Begin ---- Admin Launcher layout calls ---- #>
$tlp_Config.ResumeLayout($False)
$gb_Main_Programs.ResumeLayout($False)
$form_Config.ResumeLayout($False)
$ss_Main_StatusStrip.ResumeLayout($False)
$ss_Main_StatusStrip.PerformLayout()
$ms_Main_MenuStrip.ResumeLayout($False)
$ms_Main_MenuStrip.PerformLayout()
$tlp_Main.ResumeLayout($False)
$tlp_Main.PerformLayout()
$gb_Main_Programs.ResumeLayout($False)
$gb_Main_RunAs.ResumeLayout($False)
$gb_Main_RunAs.PerformLayout()
$gb_Config_Programs.ResumeLayout($False)
$gb_Config_Programs.PerformLayout()
$form_MainLauncher.ResumeLayout($False)
$form_MainLauncher.PerformLayout()
<# End ---- Admin Launcher layout calls ---- #>

<# This is the last line of the script, nothing after it will execute until the form is closed. #>
$form_MainLauncher.ShowDialog() | Out-Null

<# Dispose the secure password after we're done using launcher. #>
$SecurePassword.Dispose()