Display custom message while installation using powershell - winforms

i am unable to figure out the mistake that i am doing.The following script will call a bunch of batch files and while the batch files are doing there job, the progress will be shown in a progress bar along with the name of the script. What i want to achieve is to display a custom message during the installation process. I am not able to find out the mistake, any help is deeply appreciated.
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
Set-Location $PSScriptRoot
#Call scripts for installation
$ScriptsHome = Get-Item '.\test\forPS\*'
#Define Form
# Init Form
$Form = New-Object System.Windows.Forms.Form
$Form.width = 1000
$Form.height = 200
$Form.Text = "** Installation in Progress-PLEASE DO NOT CLOSE THIS WINDOW**"
$Form.Font = New-Object System.Drawing.Font("Times New Roman" ,12, [System.Drawing.FontStyle]::Regular)
$Form.MinimizeBox = $False
$Form.MaximizeBox = $False
$Form.WindowState = "Normal"
$Form.StartPosition = "CenterScreen"
$Form.Opacity = .8
$Form.BackColor = "Gray"
#Define ICON for Form
$Icon = New-Object System.Drawing.Graphics (".\ICON.jpg")
$Form.Icon = $Icon
# Init ProgressBar
$ProgressBar = New-Object System.Windows.Forms.ProgressBar
$ProgressBar.Maximum = $ScriptsHome.Count
$ProgressBar.Minimum = 0
$ProgressBar.Location = new-object System.Drawing.Size(10,70)
$ProgressBar.size = new-object System.Drawing.Size(967,10)
$Form.Controls.Add($ProgressBar)
$Form.Controls.Add($Messages)
#Running Script Name
$Label = New-Object System.Windows.Forms.Label
$Label.AutoSize = $true
$Label.Location = New-Object System.Drawing.Point(10,50)
$Form.Controls.Add($Label)
#Define Array messages
#Array
$Messages = #("Preparing to install patch set..Preparing to stop all related processes",
"Upgrading the application",
"Copying the missing folder",
"Applying the patch",
"Starting all previously stopped Services",
"Checkcing healthyness of the system after the installation this is may take up to half an hour...",
"Rebooting Server"
)
$Messages = New-Object System.Windows.Forms.Label
$Messages.AutoSize = $true
$Messages.Location = New-Object System.Drawing.Point(10,50)
$Form.Controls.Add($Messages)
# Add_Shown action
$ShownFormAction = {
$Form.Activate()
foreach ($script in $ScriptsHome) {
$ProgressBar.Increment(1)
#$Messages.Text = $Messages[$Messages]
$Label.Text = "$($script.Name)"
Start-Process $script.FullName -Wait -WindowStyle Hidden
}
$Form.Dispose()
}
$Form.Add_Shown($ShownFormAction)
# Show Form
$Form.ShowDialog()
Thanks in advance.

You're reusing the same variable name for the list of messages and the label showing the message itself:
$Messages = #("Preparing to install patch set..Preparing to stop all related processes",
"Upgrading the application",
"Copying the missing folder",
"Applying the patch",
"Starting all previously stopped Services",
"Checkcing healthyness of the system after the installation this is may take up to half an hour...",
"Rebooting Server"
)
$Messages = New-Object System.Windows.Forms.Label
$Messages.AutoSize = $true
$Messages.Location = New-Object System.Drawing.Point(10,50)
Rename one of them (ie. use $MessageLabel for the label):
$MessageLabel = New-Object System.Windows.Forms.Label
$MessageLabel.AutoSize = $true
$MessageLabel.Location = New-Object System.Drawing.Point(10,50)
Since you increment the ProgressBar by 1 every step, you can reuse the progress bar value to index into the $Messages array:
foreach ($script in $ScriptsHome) {
$ProgressBar.Increment(1)
$MessageLabel.Text = $Messages[$ProgressBar.Value - 1]
$Label.Text = "$($script.Name)"
Start-Process $script.FullName -Wait -WindowStyle Hidden
}

Related

Load WinForm Object from CliXml PS

How can one save and load the properties of GUI objects from a CliXml file? Saving mwe is below, with failed attempt to load commented out. directly importing changes object type from System.Windows.Forms.Button to System.Management.Automation.PSObject. Attempts to loop over the saved properties failed.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
Remove-Variable * -ErrorAction SilentlyContinue
$form = New-Object system.Windows.Forms.Form
$form.ClientSize = New-Object System.Drawing.Point(100,100)
$form.text = "Form"
$form.TopMost = $false
$form.FormBorderStyle = "FixedSingle"
$form.MaximizeBox = $false
$TestButton = New-Object system.Windows.Forms.Button
$TestButton.text = "Test"
$TestButton.width = 85
$TestButton.height = 30
$TestButton.location = New-Object System.Drawing.Point(0,0)
$TestButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12)
$TestButton.Enabled = $true
$path = "./vars/test.xml"
$TestButton | Export-CliXml $path
$TestButton.Enabled = $false
#$TestButton.GetType().FullName #For comp below
#$TestButton = Import-CliXml $path #Import the saved properties - intention is that this enables button again
#$TestButton.GetType() #The import changes the type. How to load saved properties and avoid this?
$form.Controls.Add($TestButton)
[void]$form.ShowDialog()
Why not "serialize" and save it as a PowerShell expression:
{
New-Object system.Windows.Forms.Button -Property #{
text = "Test"
width = 85
height = 30
location = New-Object System.Drawing.Point(0,0)
Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12)
Enabled = $true
}
} | Out-File .\Vars\Test.ps1
Note: using the outer curly brackets will validate the expression, but you might also just use (here) quotes
And then load it using dot-sourcing:
$TestButton = . .\Vars\Test.ps1

Powershell - how do I change a variable from a listbox item to a combobox item?

I'm attempting to clean up my GUI by removing listboxes of items that users can select to a constrained combobox (using DropDownList to lock user-input) in Powershell. Thus far, I haven't been able to get the variable to reflect the combobox item that is chosen.
Ideally, I want to get $Env to equal the text string chosen in the Combobox replacing $listbox
I attempted trying to follow powershell combobox items to variable to no avail as I don't understand how to use the "SelectedIndexChanged" event...I may just not fully understand the syntax on how to use this...a code example would be awesome.
The current code I have:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Select an environment"
$form.Size = New-Object System.Drawing.Size(190,250)
$form.StartPosition = "CenterScreen"
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(10,180)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Please select an environment:"
$form.Controls.Add($label)
$listBox = New-Object System.Windows.Forms.ListBox
$listBox.Location = New-Object System.Drawing.Point(10,40)
$listBox.Size = New-Object System.Drawing.Size(150,20)
$listBox.Height = 140
[void] $listBox.Items.AddRange(#("PROD", "QA1", "QA2", "TR"))
$form.Controls.Add($listBox)
$form.Topmost = $True
do
{
$result = $form.ShowDialog()
if ($ListBox.SelectedIndices.Count -lt 1 -and $result -eq [System.Windows.Forms.DialogResult]::OK)
{
Write-Warning 'Nothing was selected, please select a server.'
}
}
until (($result -eq [System.Windows.Forms.DialogResult]::OK -and $listBox.SelectedIndices.Count -ge 1) -or $result -ne [System.Windows.Forms.DialogResult]::OK)
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$Env -eq $listBox.SelectedItem
}
Here is a quickguid for using events in powershell:
Use | Get-Member -MemberType Event on your WinForms-object to get a list of available events > $OKButton | Get-Member -MemberType Event.
Add a scriptblock that shall be executed, once the event triggers. > $OKButton.add_Click({$ScriptGoesHere}). Watch out for the scope of the scriptblock (PS> help about_scopes)
I reworked your script with events instead of a loop and added comments at the important parts. Events are a lot easier to handle, if you have multiple things going on. But the scopes are kind of a drawback.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Select an environment"
$form.Size = New-Object System.Drawing.Size(380,250)
$form.StartPosition = "CenterScreen"
$form.Topmost = $True
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(10,180)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
#$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK #Moved to the event
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Please select an environment:"
$form.Controls.Add($label)
$listBox = New-Object System.Windows.Forms.ListBox
$listBox.Location = New-Object System.Drawing.Point(10,40)
$listBox.Size = New-Object System.Drawing.Size(150,140) # the second number is the height
#$listBox.Height = 140
[void] $listBox.Items.AddRange(#("PROD", "QA1", "QA2", "TR"))
$form.Controls.Add($listBox)
$comboBox = New-Object System.Windows.Forms.ComboBox
$comboBox.Location = New-Object System.Drawing.Point(190,40)
$comboBox.Size = New-Object System.Drawing.Size(150,20)
[void] $comboBox.Items.AddRange(#("PROD", "QA1", "QA2", "TR"))
$form.Controls.Add($comboBox)
#region Events
#register some events that trigger actions
$listBox.add_SelectedIndexChanged({
#The event is actually $listBox.SelectedIndexChanged but we want to ADD an action. add_SelectedIndexChanged is not listed in get-member.
$ListSelected = $listBox.SelectedItem
Write-Host "ListSelected = $ListSelected"
# You will note that $ListSelected is not available outside the event yet. It is in the scope of the scriptblock.
})
$comboBox.add_SelectedIndexChanged({
#To prevent trouble with the scope, define the variables in a higher scope
$script:SelectedItem = $comboBox.SelectedItem
$global:SelectedIndex = $comboBox.SelectedItem
write-host "SelectedItem = $SelectedItem"
})
$script:OKScript = {
# or define the whole ScriptBlock in a higher scope...
if( $comboBox.SelectedIndex -ge 0){
$Env = $comboBox.SelectedItem #Be carefull $env is the beginning of environmental variables like $env:path
$form.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.close()
}else{
Write-Warning 'Nothing was selected, please select a server.'
}
}
# ...and DotSource it to execute it in the same scope:
$OKButton.add_Click({. $OKScript})
#endregion
$result = $form.ShowDialog()
<# This would work but we use events
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$SelectedItem = $comboBox.SelectedItem
$SelectedIndex = $comboBox.SelectedItem
}
#>
write-host "`r`nform is closed`r`nhere are the results:"
"ListSelected = $ListSelected"
"result = $result"
"env = $env"
"SelectedIndex = $SelectedIndex"
"SelectedItem = $SelectedItem"

Intermittent error (index into null) with detection of SelectedIndex change in Windows Forms

I'm getting an intermittent error with this method of changing a forms text label according to the selected item in a Listview box.
Example code as below, changing the entry will intermittently give:
Cannot index into a null array.
At C:\temp\test.ps1:62 char:5
+ $SelectedPath.Text = $VMsListBox.SelectedItems.SubItems[1].Text
# Import namespaces
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Demo'
$form.Size = '580,545'
$form.StartPosition = 'CenterScreen'
$form.FormBorderStyle = 'FixedSingle'
$form.MaximizeBox = $false
# Listview box to display found open files
$VMsListBox = New-Object System.Windows.Forms.ListView
$VMsListBox.View = [System.Windows.Forms.View]::Details
$VMsListBox.Location = '15,120'
$VMsListBox.size = '435,10'
$VMsListBox.Height = 250
$VMsListBox.Columns.Add('Name') | Out-Null
$VMsListBox.Columns.Add('Path') | Out-Null
$VMsListBox.FullRowSelect = $true
$VMsListBox.MultiSelect = $false
# Selected file label
$SelectedFnameLbl = New-Object System.Windows.Forms.Label
$SelectedFnameLbl.Location = '10,25'
$SelectedFnameLbl.Size = '80,19'
$SelectedFnameLbl.Text = 'File Name:'
# Selected file name
$SelectedFname = New-Object System.Windows.Forms.Label
$SelectedFname.Location = '100,25'
$SelectedFname.Size = '300,19'
$SelectedFname.Text = 'n/a'
$SelectedFname.AutoEllipsis = $true
# Path Label
$SelectedFileLbl = New-Object System.Windows.Forms.Label
$SelectedFileLbl.Location = '10,45'
$SelectedFileLbl.Size = '80,19'
$SelectedFileLbl.Text = 'File Path:'
# Selected filepath
$SelectedPath = New-Object System.Windows.Forms.Label
$SelectedPath.Location = '100,45'
$SelectedPath.Size = '300,19'
$SelectedPath.Text = 'n/a'
$SelectedPath.AutoEllipsis = $true
$form.Controls.AddRange(#($VMsListBox,$SelectedFileLbl,$SelectedPath,$SelectedFnameLbl,$SelectedFname))
# Populate ListView
$Files = Get-ChildItem -Path 'c:\temp' -File
$Files | ForEach-Object {
$Entry = New-Object System.Windows.Forms.ListViewItem($_.Name) -ErrorAction Stop
$Entry.SubItems.Add($_.FullName) | Out-Null
$VMsListBox.Items.Add($Entry) | Out-Null
}
$VMsListBox_SelectedIndexChanged={
$SelectedFname.Text = $VMsListBox.SelectedItems.Text
$SelectedPath.Text = $VMsListBox.SelectedItems.SubItems[1].Text
Write-Host "Entry changed"
}
$VMsListBox.Add_SelectedIndexChanged($VMsListBox_SelectedIndexChanged)
# Show form
$form.ShowDialog() | Out-Null
$form.Dispose()
Can anyone point me where I'm going wrong please? Or is there a better way of doing this
Ok, I found this
Apparently retained legacy behaviour and the fix is to check for null:
$VMsListBox_SelectedIndexChanged={
If($VMsListbox.SelectedItems -ne $Null){
$SelectedFname.Text = $VMsListBox.SelectedItems.Text
$SelectedPath.Text = $VMsListBox.SelectedItems.SubItems[1].Text
Write-Host "Entry changed"
}
}

How to prevent null/whitespace value in Windows Form textbox (using Powershell)?

I made a Windows Form (this is the last portion of it) that allows the user to enter a search term in a textbox. The whole script checks a server for log files, and downloads them to the user's machine. The textbox data contains a string (date, account #, etc.)...and if left blank it is treated as a wildcard, downloading every log file in the folder chosen. I'm not sure if I can disable the "ok" button until data is entered or give a pop-up/message box prompting the user to enter a search term? I left the prior code/variables out of this example, as it has no bearing on the issue. I appreciate any help in advance!
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Enter search criteria"
$form.Size = New-Object System.Drawing.Size(300,200)
$form.StartPosition = "CenterScreen"
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Enter search term"
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($textBox)
$form.Topmost = $True
$form.Add_Shown({$textBox.Select()})
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$Search = $textBox.Text
$Search
}
$UserPath = "C:\GetFiles\getfiles"
& cmd /c $UserPath" "$Search
Here would be one way you could handle it.
$okbutton.enabled = $false # make this a default
if(![string]::IsNullOrEmpty($textbox.text)) #only enable when you have text in the text box
{
$okbutton.enabled = $true
}
You'll have to use an event from the textbox to change the value of the $okbutton to enabled once text has been entered. I believe this is the event you'll need:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.textchanged?view=netframework-4.7.2
Depending on what you are doing you may need some of the other events as well.
I figured it out. Thank you to Thom Schumacher for helping me with the syntax necessary to complete this:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Enter search criteria"
$form.Size = New-Object System.Drawing.Size(300,200)
$form.StartPosition = "CenterScreen"
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Enter loan number or date to search"
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($textBox)
$form.Topmost = $True
$form.Add_Shown({$textBox.Select()})
do
{
$result = $form.ShowDialog()
if ([string]::IsNullOrEmpty($textbox.text))
{
Write-Warning 'Please enter search term.'
}
}
until(![string]::IsNullOrEmpty($textbox.text))
{
}
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$Search = $textBox.Text
$Search
}

Creating a progress bar for a search function

I am trying to make a progress bar in a powershell generated GUI that will show the progress of a search as the system caches all the groups in AD (there are about 12,000 so this just otherwise causes the system to hang for a short while)
I managed to build the bar etc but I cannot get it to fill the bar as the system adds the groups. Here is what I have so far:
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
$Form = New-Object System.Windows.Forms.Form
$Form.width = 345
$Form.height = 345
$Form.StartPosition = "CenterScreen"
$Form.ShowInTaskbar = $True
$Form.FormBorderStyle = 'FixedToolWindow'
$ExitButt = new-object System.Windows.Forms.Button
$ExitButt.Location = new-object System.Drawing.Size(235,5)
$ExitButt.Size = new-object System.Drawing.Size(100,20)
$ExitButt.Text = "Exit"
$Form.Controls.Add($ExitButt)
$ExitButt.Add_Click({$Form.Close()})
$Prog = New-Object System.Windows.Forms.ProgressBar
$Prog.Maximum = 10000
$Prog.Minimum = 0
$Prog.Location = new-object System.Drawing.Size(50,50)
$Prog.size = new-object System.Drawing.Size(100,50)
$Form.Controls.Add($Prog)
$Button = new-object System.Windows.Forms.Button
$Button.Location = new-object System.Drawing.Size(120,100)
$Button.Size = new-object System.Drawing.Size(100,30)
$Button.Text = "Start Progress"
$Form.Controls.Add($Button)
$Button.add_click(
{
$GroupsList = Get-ADGroup -Server "server" -Filter *
$Count = ($GroupsList | Measure-Object).Count
$prog.Value = $Count
}
)
$form.ShowDialog() | Out-Null
To update a WinForms ProgressBar just set its Value property.
Eg.
# Initialisation...
$myProgBar.Minimum = 1;
$myProgBar.Maximum = 100;
# When something happens...
$myProgBar.Value = 50;
will set it to half way.

Resources