Hello all,
A bit of introduction
I just started messing around with the world of powershell and unfortunately i am in deep water and would like your help guidance.
The problem is that i do not have skills in programming so i can only go with the trial and error method to learn for now.
The “masterplan”
is to provide a GUI to users to do multiple automated actions(e.g check free hard disk space,kill processes etc) in a pool of servers (100 and rising up fast). I have figured out how to do some basic stuff in a sequential way but because of the number of servers will rise up i have to implement a way of multitasking…
Problems experiencing using GUI
GUI freezes when a long running operation takes place and becomes unresponsive(imagine a long operation for each server)
Below i will present a very simple form utilizing powershell jobs in order to achieve multitasking(baby steps) that updates a progressbar.
I have used powershell studio and export in a ps1 format in order for you to test.
This is working OK
!! (I used the built-in functions in powershell so no big deal).
I could press both buttons almost at the same time with no GUI freezing and checking with process explorer that two powershell.exe sub-processes are created…
#----------------------------------------------
# Generated Form Function
#----------------------------------------------
function Call-powershell-job-tracker_psf {
#----------------------------------------------
#region Import the Assemblies
#----------------------------------------------
[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')
#endregion Import Assemblies
#----------------------------------------------
#region Define SAPIEN Types
#----------------------------------------------
try{
$local:type = [ProgressBarOverlay]
}
catch
{
Add-Type -ReferencedAssemblies ('System.Windows.Forms', 'System.Drawing') -TypeDefinition @"
using System;
using System.Windows.Forms;
using System.Drawing;
namespace SAPIENTypes
{
public class ProgressBarOverlay : System.Windows.Forms.ProgressBar
{
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x000F)// WM_PAINT
{
if (Style != System.Windows.Forms.ProgressBarStyle.Marquee || !string.IsNullOrEmpty(this.Text))
{
using (Graphics g = this.CreateGraphics())
{
using (StringFormat stringFormat = new StringFormat(StringFormatFlags.NoWrap))
{
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
if (!string.IsNullOrEmpty(this.Text))
g.DrawString(this.Text, this.Font, Brushes.Black, this.ClientRectangle, stringFormat);
else
{
int percent = (int)(((double)Value / (double)Maximum) * 100);
g.DrawString(percent.ToString() + "%", this.Font, Brushes.Black, this.ClientRectangle, stringFormat);
}
}
}
}
}
}
public string TextOverlay
{
get
{
return base.Text;
}
set
{
base.Text = value;
Invalidate();
}
}
}
}
"@ | Out-Null
}
#endregion Define SAPIEN Types
#----------------------------------------------
#region Generated Form Objects
#----------------------------------------------
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$progressbaroverlay2 = New-Object 'SAPIENTypes.ProgressBarOverlay'
$buttonStartJob2 = New-Object 'System.Windows.Forms.Button'
$progressbaroverlay1 = New-Object 'SAPIENTypes.ProgressBarOverlay'
$buttonStartJob = New-Object 'System.Windows.Forms.Button'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$imagelistButtonBusyAnimation = New-Object 'System.Windows.Forms.ImageList'
$timerJobTracker = New-Object 'System.Windows.Forms.Timer'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
#endregion Generated Form Objects
#----------------------------------------------
# User Generated Script
#----------------------------------------------
$form1_Load={
#TODO: Initialize Form Controls here
}
$buttonStartJob_Click={
$buttonStartJob.Enabled = $false
#Create a New Job using the Job Tracker
Add-JobTracker -Name 'JobName' `
-JobScript {
#--------------------------------------------------
#TODO: Set a script block
#Important: Do not access form controls from this script block.
Param($Argument1)#Pass any arguments using the ArgumentList parameter
for ($i = 0; $i -lt 10; $i++)
{
Start-Sleep -Milliseconds 1000
#Output Progress
$i + 1
}
#--------------------------------------------------
}`
-CompletedScript {
Param($Job)
#$results = Receive-Job -Job $Job
#Enable the Button
$buttonStartJob.ImageIndex = -1
$buttonStartJob.Enabled = $true
}`
-UpdateScript {
Param($Job)
#$results = Receive-Job -Job $Job -Keep
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int])
{
$progressbaroverlay1.Maximum = 10 - 1
$progressbaroverlay1.Value = $results
}
#Animate the Button
if($null -ne $buttonStartJob.ImageList)
{
if($buttonStartJob.ImageIndex -lt $buttonStartJob.ImageList.Images.Count - 1)
{
$buttonStartJob.ImageIndex += 1
}
else
{
$buttonStartJob.ImageIndex = 0
}
}
}`
-ArgumentList $null
}
$jobTracker_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
#Stop any pending jobs
Stop-JobTracker
}
$timerJobTracker_Tick={
Update-JobTracker
}
#region Job Tracker
$JobTrackerList = New-Object System.Collections.ArrayList
function Add-JobTracker
{
Param(
[ValidateNotNull()]
[Parameter(Mandatory=$true)]
[string]$Name,
[ValidateNotNull()]
[Parameter(Mandatory=$true)]
[ScriptBlock]$JobScript,
$ArgumentList = $null,
[ScriptBlock]$CompletedScript,
[ScriptBlock]$UpdateScript)
#Start the Job
$job = Start-Job -Name $Name -ScriptBlock $JobScript -ArgumentList $ArgumentList
if($null -ne $job)
{
#Create a Custom Object to keep track of the Job & Script Blocks
$members = @{ 'Job' = $Job;
'CompleteScript' = $CompletedScript;
'UpdateScript' = $UpdateScript}
$psObject = New-Object System.Management.Automation.PSObject -Property $members
[void]$JobTrackerList.Add($psObject)
#Start the Timer
if(-not $timerJobTracker.Enabled)
{
$timerJobTracker.Start()
}
}
elseif($null -ne $CompletedScript)
{
#Failed
Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
}
}
function Update-JobTracker
{
#Poll the jobs for status updates
$timerJobTracker.Stop() #Freeze the Timer
for($index = 0; $index -lt $JobTrackerList.Count; $index++)
{
$psObject = $JobTrackerList[$index]
if($null -ne $psObject)
{
if($null -ne $psObject.Job)
{
if ($psObject.Job.State -eq 'Blocked')
{
#Try to unblock the job
Receive-Job $psObject.Job | Out-Null
}
elseif($psObject.Job.State -ne 'Running')
{
#Call the Complete Script Block
if($null -ne $psObject.CompleteScript)
{
#$results = Receive-Job -Job $psObject.Job
Invoke-Command -ScriptBlock $psObject.CompleteScript -ArgumentList $psObject.Job
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}
elseif($null -ne $psObject.UpdateScript)
{
#Call the Update Script Block
Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
}
}
}
else
{
$JobTrackerList.RemoveAt($index)
$index-- #Step back so we don't skip a job
}
}
if($JobTrackerList.Count -gt 0)
{
$timerJobTracker.Start()#Resume the timer
}
}
function Stop-JobTracker
{
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0)
{
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}
#endregion
$buttonStartJob2_Click={
$buttonStartJob2.Enabled = $false
#Create a New Job using the Job Tracker
Add-JobTracker -Name 'JobName' `
-JobScript {
#--------------------------------------------------
#TODO: Set a script block
#Important: Do not access form controls from this script block.
Param($Argument1)#Pass any arguments using the ArgumentList parameter
for ($i = 0; $i -lt 10; $i++)
{
Start-Sleep -Milliseconds 1000
#Output Progress
$i + 1
}
#--------------------------------------------------
}`
-CompletedScript {
Param($Job)
#$results = Receive-Job -Job $Job
#Enable the Button
$buttonStartJob2.ImageIndex = -1
$buttonStartJob2.Enabled = $true
}`
-UpdateScript {
Param($Job)
#$results = Receive-Job -Job $Job -Keep
$results2 = Receive-Job -Job $Job | Select-Object -Last 1
if ($results2 -is [int])
{
$progressbaroverlay2.Maximum = 10 - 1
$progressbaroverlay2.Value = $results2
}
#Animate the Button
if($null -ne $buttonStartJob2.ImageList)
{
if($buttonStartJob2.ImageIndex -lt $buttonStartJob2.ImageList.Images.Count - 1)
{
$buttonStartJob2.ImageIndex += 1
}
else
{
$buttonStartJob2.ImageIndex = 0
}
}
}`
-ArgumentList $null
}
# --End User Generated Script--
#----------------------------------------------
#region Generated Events
#----------------------------------------------
$Form_StateCorrection_Load=
{
#Correct the initial state of the form to prevent the .Net maximized form issue
$form1.WindowState = $InitialFormWindowState
}
$Form_Cleanup_FormClosed=
{
#Remove all event handlers from the controls
try
{
$buttonStartJob2.remove_Click($buttonStartJob2_Click)
$buttonStartJob.remove_Click($buttonStartJob_Click)
$form1.remove_FormClosed($jobTracker_FormClosed)
$form1.remove_Load($form1_Load)
$timerJobTracker.remove_Tick($timerJobTracker_Tick)
$form1.remove_Load($Form_StateCorrection_Load)
$form1.remove_FormClosed($Form_Cleanup_FormClosed)
}
catch [Exception]
{ }
}
#endregion Generated Events
#----------------------------------------------
#region Generated Form Code
#----------------------------------------------
$form1.SuspendLayout()
#
# form1
#
$form1.Controls.Add($progressbaroverlay2)
$form1.Controls.Add($buttonStartJob2)
$form1.Controls.Add($progressbaroverlay1)
$form1.Controls.Add($buttonStartJob)
$form1.Controls.Add($buttonOK)
$form1.AcceptButton = $buttonOK
$form1.AutoScaleDimensions = '6, 13'
$form1.AutoScaleMode = 'Font'
$form1.ClientSize = '424, 333'
$form1.FormBorderStyle = 'FixedDialog'
$form1.MaximizeBox = $False
$form1.MinimizeBox = $False
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = 'Form'
$form1.add_FormClosed($jobTracker_FormClosed)
$form1.add_Load($form1_Load)
#
# progressbaroverlay2
#
$progressbaroverlay2.Location = '50, 193'
$progressbaroverlay2.Name = 'progressbaroverlay2'
$progressbaroverlay2.Size = '100, 23'
$progressbaroverlay2.TabIndex = 2
#
# buttonStartJob2
#
$buttonStartJob2.ImageList = $imagelistButtonBusyAnimation
$buttonStartJob2.Location = '50, 151'
$buttonStartJob2.Name = 'buttonStartJob2'
$buttonStartJob2.Size = '75, 23'
$buttonStartJob2.TabIndex = 0
$buttonStartJob2.Text = 'Start2'
$buttonStartJob2.TextImageRelation = 'ImageBeforeText'
$buttonStartJob2.UseVisualStyleBackColor = $True
$buttonStartJob2.add_Click($buttonStartJob2_Click)
#
# progressbaroverlay1
#
$progressbaroverlay1.Location = '283, 193'
$progressbaroverlay1.Name = 'progressbaroverlay1'
$progressbaroverlay1.Size = '100, 23'
$progressbaroverlay1.TabIndex = 1
#
# buttonStartJob
#
$buttonStartJob.ImageList = $imagelistButtonBusyAnimation
$buttonStartJob.Location = '283, 151'
$buttonStartJob.Name = 'buttonStartJob'
$buttonStartJob.Size = '75, 23'
$buttonStartJob.TabIndex = 0
$buttonStartJob.Text = 'Start'
$buttonStartJob.TextImageRelation = 'ImageBeforeText'
$buttonStartJob.UseVisualStyleBackColor = $True
$buttonStartJob.add_Click($buttonStartJob_Click)
#
# buttonOK
#
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '337, 298'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 0
$buttonOK.Text = '&OK'
$buttonOK.UseVisualStyleBackColor = $True
#
# imagelistButtonBusyAnimation
#
$Formatter_binaryFomatter = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
#region Binary Data
$System_IO_MemoryStream = New-Object System.IO.MemoryStream (,[byte[]][System.Convert]::FromBase64String('
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAu
MC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAA
ACZTeXN0ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkD
AAAADwMAAAB2CgAAAk1TRnQBSQFMAgEBCAEAATABAAEwAQABEAEAARABAAT/ASEBAAj/AUIBTQE2
BwABNgMAASgDAAFAAwABMAMAAQEBAAEgBgABMP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/
AP8AugADwgH/Az0B/wM9Af8DwgH/MAADwgH/A10B/wOCAf8DwgH/sAADPQH/AwAB/wMAAf8DPQH/
MAADggH/Az0B/wM9Af8DXQH/gAADwgH/Az0B/wM9Af8DwgH/IAADPQH/AwAB/wMAAf8DPQH/A8IB
/wNdAf8DggH/A8IB/xAAA8IB/wM9Af8DPQH/A8IB/wNdAf8DPQH/Az0B/wNdAf8EAAOSAf8DkgH/
A8IB/3AAAz0B/wMAAf8DAAH/Az0B/yAAA8IB/wM9Af8DPQH/A8IB/wOCAf8DPQH/Az0B/wOCAf8Q
AAM9Af8DAAH/AwAB/wM9Af8DwgH/A10B/wOCAf8DwgH/A5IB/wOCAf8DggH/A5IB/3AAAz0B/wMA
Af8DAAH/Az0B/zAAA10B/wM9Af8DPQH/A10B/xAAAz0B/wMAAf8DAAH/Az0B/xAAA5IB/wOSAf8D
kgH/A8IB/3AAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wNdAf8DggH/A8IB/xAAA8IB/wM9Af8DPQH/
A8IB/xAAA8IB/wOSAf8DkgH/A8IB/zgAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wOCAf8DXQH/A8IB
/zAAA8IB/wPCAf8DkgH/A8IB/zQAA8IB/wPCAf80AAM9Af8DAAH/AwAB/wM9Af8wAANdAf8DPQH/
Az0B/wNdAf8wAAOSAf8DggH/A4IB/wOSAf8wAAPCAf8DwgH/A8IB/wPCAf8wAAM9Af8DAAH/AwAB
/wM9Af8wAAOCAf8DPQH/Az0B/wOCAf8wAAPCAf8DggH/A5IB/wOSAf8wAAPCAf8DwgH/A8IB/wPC
Af8wAAPCAf8DPQH/Az0B/wPCAf8wAAPCAf8DggH/A10B/wPCAf8wAAPCAf8DkgH/A5IB/wPCAf80
AAPCAf8DwgH/EAADwgH/A8IB/xQAA8IB/wOCAf8DXQH/A8IB/zAAA8IB/wOSAf8DkgH/A8IB/zQA
A8IB/wPCAf9UAAPCAf8DwgH/A8IB/wPCAf8QAANdAf8DPQH/Az0B/wNdAf8wAAOSAf8DggH/A5IB
/wOSAf8wAAPCAf8DwgH/A8IB/wPCAf9QAAPCAf8DwgH/A8IB/wPCAf8DwgH/A8IB/wOSAf8DwgH/
A4IB/wM9Af8DPQH/A4IB/yQAA8IB/wPCAf8EAAPCAf8DggH/A5IB/wOSAf8wAAPCAf8DwgH/A8IB
/wPCAf9UAAPCAf8DwgH/BAADkgH/A4IB/wOCAf8DkgH/A8IB/wOCAf8DXQH/A8IB/yAAA8IB/wPC
Af8DwgH/A8IB/wPCAf8DkgH/A5IB/wPCAf80AAPCAf8DwgH/ZAADkgH/A5IB/wOSAf8DkgH/MAAD
wgH/A8IB/wPCAf8DwgH/sAADwgH/A5IB/wOSAf8DwgH/NAADwgH/A8IB/7QAA8IB/wPCAf8DkgH/
A8IB/zQAA8IB/wPCAf+0AAOSAf8DggH/A4IB/wOSAf8wAAPCAf8DwgH/A8IB/wPCAf+gAAPCAf8D
XQH/A4IB/wPCAf8DkgH/A5IB/wOSAf8DwgH/BAADwgH/A8IB/xQAA8IB/wPCAf8DkgH/A8IB/wPC
Af8DwgH/A8IB/wPCAf8kAAPCAf8DwgH/dAADggH/Az0B/wM9Af8DggH/A8IB/wOSAf8DkgH/A8IB
/wPCAf8DwgH/A8IB/wPCAf8QAAOSAf8DggH/A4IB/wOSAf8EAAPCAf8DwgH/JAADwgH/A8IB/wPC
Af8DwgH/cAADXQH/Az0B/wM9Af8DggH/EAADwgH/A8IB/wPCAf8DwgH/EAADkgH/A5IB/wOSAf8D
kgH/MAADwgH/A8IB/wPCAf8DwgH/cAADwgH/A10B/wNdAf8DwgH/FAADwgH/A8IB/xQAA8IB/wOS
Af8DkgH/A8IB/zQAA8IB/wPCAf9sAAPCAf8DPQH/Az0B/wPCAf8wAAPCAf8DXQH/A4IB/wPCAf8w
AAPCAf8DwgH/A5IB/wPCAf80AAPCAf8DwgH/NAADPQH/AwAB/wMAAf8DPQH/MAADggH/Az0B/wM9
Af8DXQH/MAADkgH/A4IB/wOCAf8DkgH/MAADwgH/A8IB/wPCAf8DwgH/MAADPQH/AwAB/wMAAf8D
PQH/MAADXQH/Az0B/wM9Af8DggH/MAADkgH/A5IB/wOSAf8DkgH/MAADwgH/A8IB/wPCAf8DwgH/
MAADwgH/Az0B/wM9Af8DwgH/MAADwgH/A10B/wNdAf8DwgH/MAADwgH/A5IB/wOSAf8DwgH/NAAD
wgH/A8IB/3wAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wNdAf8DggH/A8IB/zAAA8IB/wPCAf8DkgH/
A8IB/xAAA8IB/wM9Af8DPQH/A8IB/1AAAz0B/wMAAf8DAAH/Az0B/zAAA4IB/wM9Af8DPQH/A10B
/zAAA5IB/wOCAf8DggH/A5IB/xAAAz0B/wMAAf8DAAH/Az0B/1AAAz0B/wMAAf8DAAH/Az0B/zAA
A10B/wM9Af8DPQH/A4IB/wOSAf8DPQH/Az0B/wPCAf8gAAOSAf8DkgH/A5IB/wOSAf8DwgH/A10B
/wOCAf8DwgH/Az0B/wMAAf8DAAH/Az0B/1AAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wOCAf8DXQH/
A8IB/wM9Af8DAAH/AwAB/wM9Af8gAAPCAf8DkgH/A5IB/wPCAf8DggH/Az0B/wM9Af8DXQH/A8IB
/wM9Af8DPQH/A8IB/6AAAz0B/wMAAf8DAAH/Az0B/zAAA10B/wM9Af8DPQH/A4IB/7AAA8IB/wM9
Af8DPQH/A8IB/zAAA8IB/wOCAf8DXQH/A8IB/xgAAUIBTQE+BwABPgMAASgDAAFAAwABMAMAAQEB
AAEBBQABgAEBFgAD/4EABP8B/AE/AfwBPwT/AfwBPwH8AT8D/wHDAfwBAwHAASMD/wHDAfwBAwHA
AQMD/wHDAf8DwwP/AcMB/wPDAf8B8AH/AfAB/wHwAf8B+QH/AfAB/wHwAf8B8AH/AfAB/wHwAf8B
8AH/AfAB/wHwAf8B8AH/AfAB/wHwAf8B+QHnAcMB/wHDAf8B5wL/AsMB/wHDAf8BwwL/AcABAwH+
AUMB/wHDAv8B5AEDAfwBAwH/AecC/wH8AT8B/AE/BP8B/AE/Af4BfwT/AfwBPwH+AX8E/wH8AT8B
/AE/BP8BwAEnAcABPwHnA/8BwAEDAcIBfwHDA/8DwwH/AcMD/wHDAecBwwH/AecD/wEPAf8BDwH/
AQ8B/wGfAf8BDwH/AQ8B/wEPAf8BDwH/AQ8B/wEPAf8BDwH/AQ8B/wEPAf8BDwH/AQ8B/wGfA/8B
wwH/AcMB/wLDAv8BwwH/AcMB/wLDAv8BwwH/AcABPwHAAQMC/wHDAf8BwAE/AcABAwT/AfwBPwH8
AT8E/wH8AT8B/AE/Cw=='))
#endregion
$imagelistButtonBusyAnimation.ImageStream = $Formatter_binaryFomatter.Deserialize($System_IO_MemoryStream)
$Formatter_binaryFomatter = $null
$System_IO_MemoryStream = $null
$imagelistButtonBusyAnimation.TransparentColor = 'Transparent'
#
# timerJobTracker
#
$timerJobTracker.add_Tick($timerJobTracker_Tick)
$form1.ResumeLayout()
#endregion Generated Form Code
#----------------------------------------------
#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($Form_StateCorrection_Load)
#Clean up the control events
$form1.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
return $form1.ShowDialog()
} #End Function
#Call the form
Call-powershell-job-tracker_psf | Out-Null
PoshRSJob Implementation
The problem with powershell jobs is that there is no throttling and i believe there is a BIG overhead creating a new process for each job.This led me to chaotic world of runspaces.I found this excellent module of PoshRSJob (https://github.com/proxb/PoshRSJob that uses runspaces so you can multithread easier in Powershell.
I tried this very simplistic approach to import the PoshRSJob and replace the commands of powershell jobs with the ones of the module.(I told you i just started so cannot do much,be kind to me :)).
The result is to have the commands running BUT two problems
1.The progress bars are not updated
2.When i close the form The GUI leaves a zombie process when compiled as exe using Powershell studio
Here is the implementation of PoshRSJob insted of powershell jobs
Import-Module -Name PoshRSJob -Force
#----------------------------------------------
# Generated Form Function
#----------------------------------------------
function Call-powershell-job-tracker-PoshRSJob_psf
{
#----------------------------------------------
#region Import the Assemblies
#----------------------------------------------
[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')
#endregion Import Assemblies
#----------------------------------------------
#region Define SAPIEN Types
#----------------------------------------------
try
{
$local:type = [ProgressBarOverlay]
}
catch
{
Add-Type -ReferencedAssemblies ('System.Windows.Forms', 'System.Drawing') -TypeDefinition @"
using System;
using System.Windows.Forms;
using System.Drawing;
namespace SAPIENTypes
{
public class ProgressBarOverlay : System.Windows.Forms.ProgressBar
{
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x000F)// WM_PAINT
{
if (Style != System.Windows.Forms.ProgressBarStyle.Marquee || !string.IsNullOrEmpty(this.Text))
{
using (Graphics g = this.CreateGraphics())
{
using (StringFormat stringFormat = new StringFormat(StringFormatFlags.NoWrap))
{
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
if (!string.IsNullOrEmpty(this.Text))
g.DrawString(this.Text, this.Font, Brushes.Black, this.ClientRectangle, stringFormat);
else
{
int percent = (int)(((double)Value / (double)Maximum) * 100);
g.DrawString(percent.ToString() + "%", this.Font, Brushes.Black, this.ClientRectangle, stringFormat);
}
}
}
}
}
}
public string TextOverlay
{
get
{
return base.Text;
}
set
{
base.Text = value;
Invalidate();
}
}
}
}
"@ | Out-Null
}
#endregion Define SAPIEN Types
#----------------------------------------------
#region Generated Form Objects
#----------------------------------------------
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$progressbaroverlay2 = New-Object 'SAPIENTypes.ProgressBarOverlay'
$buttonStartJob2 = New-Object 'System.Windows.Forms.Button'
$progressbaroverlay1 = New-Object 'SAPIENTypes.ProgressBarOverlay'
$buttonStartJob = New-Object 'System.Windows.Forms.Button'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$imagelistButtonBusyAnimation = New-Object 'System.Windows.Forms.ImageList'
$timerJobTracker = New-Object 'System.Windows.Forms.Timer'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
#endregion Generated Form Objects
#----------------------------------------------
# User Generated Script
#----------------------------------------------
$form1_Load = {
#TODO: Initialize Form Controls here
}
$buttonStartJob_Click = {
$buttonStartJob.Enabled = $false
#Create a New Job using the Job Tracker
Add-JobTracker -Name 'JobName' `
-JobScript {
#--------------------------------------------------
#TODO: Set a script block
#Important: Do not access form controls from this script block.
Param ($Argument1) #Pass any arguments using the ArgumentList parameter
for ($i = 0; $i -lt 10; $i++)
{
Start-Sleep -Milliseconds 1000
#Output Progress
$i + 1
}
#--------------------------------------------------
}`
-CompletedScript {
Param ($Job)
#$results = Receive-Job -Job $Job
#Enable the Button
$buttonStartJob.ImageIndex = -1
$buttonStartJob.Enabled = $true
}`
-UpdateScript {
Param ($Job)
#$results = Receive-Job -Job $Job -Keep
$results = Receive-RSJob -Job $Job | Select-Object -Last 1
if ($results -is [int])
{
$progressbaroverlay1.Maximum = 10 - 1
$progressbaroverlay1.Value = $results
}
#Animate the Button
if ($null -ne $buttonStartJob.ImageList)
{
if ($buttonStartJob.ImageIndex -lt $buttonStartJob.ImageList.Images.Count - 1)
{
$buttonStartJob.ImageIndex += 1
}
else
{
$buttonStartJob.ImageIndex = 0
}
}
}`
-ArgumentList $null
}
$jobTracker_FormClosed = [System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
#Stop any pending jobs
Stop-JobTracker
}
$timerJobTracker_Tick = {
Update-JobTracker
}
#region Job Tracker
$JobTrackerList = New-Object System.Collections.ArrayList
function Add-JobTracker
{
Param (
[ValidateNotNull()]
[Parameter(Mandatory = $true)]
[string]$Name,
[ValidateNotNull()]
[Parameter(Mandatory = $true)]
[ScriptBlock]$JobScript,
$ArgumentList = $null,
[ScriptBlock]$CompletedScript,
[ScriptBlock]$UpdateScript)
#Start the Job
$job = Start-RSJob -Name $Name -ScriptBlock $JobScript -ArgumentList $ArgumentList
if ($null -ne $job)
{
#Create a Custom Object to keep track of the Job & Script Blocks
$members = @{
'Job' = $Job;
'CompleteScript' = $CompletedScript;
'UpdateScript' = $UpdateScript
}
$psObject = New-Object System.Management.Automation.PSObject -Property $members
[void]$JobTrackerList.Add($psObject)
#Start the Timer
if (-not $timerJobTracker.Enabled)
{
$timerJobTracker.Start()
}
}
elseif ($null -ne $CompletedScript)
{
#Failed
Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
}
}
function Update-JobTracker
{
#Poll the jobs for status updates
$timerJobTracker.Stop() #Freeze the Timer
for ($index = 0; $index -lt $JobTrackerList.Count; $index++)
{
$psObject = $JobTrackerList[$index]
if ($null -ne $psObject)
{
if ($null -ne $psObject.Job)
{
if ($psObject.Job.State -eq 'Blocked')
{
#Try to unblock the job
Receive-RSJob $psObject.Job | Out-Null
}
elseif ($psObject.Job.State -ne 'Running')
{
#Call the Complete Script Block
if ($null -ne $psObject.CompleteScript)
{
#$results = Receive-Job -Job $psObject.Job
Invoke-Command -ScriptBlock $psObject.CompleteScript -ArgumentList $psObject.Job
}
$JobTrackerList.RemoveAt($index)
Remove-RSJob -Job $psObject.Job
$index-- #Step back so we don't skip a job
}
elseif ($null -ne $psObject.UpdateScript)
{
#Call the Update Script Block
Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
}
}
}
else
{
$JobTrackerList.RemoveAt($index)
$index-- #Step back so we don't skip a job
}
}
if ($JobTrackerList.Count -gt 0)
{
$timerJobTracker.Start() #Resume the timer
}
}
function Stop-JobTracker
{
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while ($JobTrackerList.Count -gt 0)
{
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-RSJob $job
Remove-RSJob $job
}
}
#endregion
$buttonStartJob2_Click = {
$buttonStartJob2.Enabled = $false
#Create a New Job using the Job Tracker
Add-JobTracker -Name 'JobName' `
-JobScript {
#--------------------------------------------------
#TODO: Set a script block
#Important: Do not access form controls from this script block.
Param ($Argument1) #Pass any arguments using the ArgumentList parameter
for ($i = 0; $i -lt 10; $i++)
{
Start-Sleep -Milliseconds 1000
#Output Progress
$i + 1
}
#--------------------------------------------------
}`
-CompletedScript {
Param ($Job)
#$results = Receive-Job -Job $Job
#Enable the Button
$buttonStartJob2.ImageIndex = -1
$buttonStartJob2.Enabled = $true
}`
-UpdateScript {
Param ($Job)
#$results = Receive-Job -Job $Job -Keep
$results2 = Receive-RSJob -Job $Job | Select-Object -Last 1
if ($results2 -is [int])
{
$progressbaroverlay2.Maximum = 10 - 1
$progressbaroverlay2.Value = $results2
}
#Animate the Button
if ($null -ne $buttonStartJob2.ImageList)
{
if ($buttonStartJob2.ImageIndex -lt $buttonStartJob2.ImageList.Images.Count - 1)
{
$buttonStartJob2.ImageIndex += 1
}
else
{
$buttonStartJob2.ImageIndex = 0
}
}
}`
-ArgumentList $null
}
# --End User Generated Script--
#----------------------------------------------
#region Generated Events
#----------------------------------------------
$Form_StateCorrection_Load =
{
#Correct the initial state of the form to prevent the .Net maximized form issue
$form1.WindowState = $InitialFormWindowState
}
$Form_Cleanup_FormClosed =
{
#Remove all event handlers from the controls
try
{
$buttonStartJob2.remove_Click($buttonStartJob2_Click)
$buttonStartJob.remove_Click($buttonStartJob_Click)
$form1.remove_FormClosed($jobTracker_FormClosed)
$form1.remove_Load($form1_Load)
$timerJobTracker.remove_Tick($timerJobTracker_Tick)
$form1.remove_Load($Form_StateCorrection_Load)
$form1.remove_FormClosed($Form_Cleanup_FormClosed)
}
catch [Exception]
{ }
}
#endregion Generated Events
#----------------------------------------------
#region Generated Form Code
#----------------------------------------------
$form1.SuspendLayout()
#
# form1
#
$form1.Controls.Add($progressbaroverlay2)
$form1.Controls.Add($buttonStartJob2)
$form1.Controls.Add($progressbaroverlay1)
$form1.Controls.Add($buttonStartJob)
$form1.Controls.Add($buttonOK)
$form1.AcceptButton = $buttonOK
$form1.AutoScaleDimensions = '6, 13'
$form1.AutoScaleMode = 'Font'
$form1.ClientSize = '424, 333'
$form1.FormBorderStyle = 'FixedDialog'
$form1.MaximizeBox = $False
$form1.MinimizeBox = $False
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = 'Form'
$form1.add_FormClosed($jobTracker_FormClosed)
$form1.add_Load($form1_Load)
#
# progressbaroverlay2
#
$progressbaroverlay2.Location = '50, 193'
$progressbaroverlay2.Name = 'progressbaroverlay2'
$progressbaroverlay2.Size = '100, 23'
$progressbaroverlay2.TabIndex = 2
#
# buttonStartJob2
#
$buttonStartJob2.ImageList = $imagelistButtonBusyAnimation
$buttonStartJob2.Location = '50, 151'
$buttonStartJob2.Name = 'buttonStartJob2'
$buttonStartJob2.Size = '75, 23'
$buttonStartJob2.TabIndex = 0
$buttonStartJob2.Text = 'Start2'
$buttonStartJob2.TextImageRelation = 'ImageBeforeText'
$buttonStartJob2.UseVisualStyleBackColor = $True
$buttonStartJob2.add_Click($buttonStartJob2_Click)
#
# progressbaroverlay1
#
$progressbaroverlay1.Location = '283, 193'
$progressbaroverlay1.Name = 'progressbaroverlay1'
$progressbaroverlay1.Size = '100, 23'
$progressbaroverlay1.TabIndex = 1
#
# buttonStartJob
#
$buttonStartJob.ImageList = $imagelistButtonBusyAnimation
$buttonStartJob.Location = '283, 151'
$buttonStartJob.Name = 'buttonStartJob'
$buttonStartJob.Size = '75, 23'
$buttonStartJob.TabIndex = 0
$buttonStartJob.Text = 'Start'
$buttonStartJob.TextImageRelation = 'ImageBeforeText'
$buttonStartJob.UseVisualStyleBackColor = $True
$buttonStartJob.add_Click($buttonStartJob_Click)
#
# buttonOK
#
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '337, 298'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 0
$buttonOK.Text = '&OK'
$buttonOK.UseVisualStyleBackColor = $True
#
# imagelistButtonBusyAnimation
#
$Formatter_binaryFomatter = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
#region Binary Data
$System_IO_MemoryStream = New-Object System.IO.MemoryStream ( ,[byte[]][System.Convert]::FromBase64String('
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAu
MC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAA
ACZTeXN0ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkD
AAAADwMAAAB2CgAAAk1TRnQBSQFMAgEBCAEAATABAAEwAQABEAEAARABAAT/ASEBAAj/AUIBTQE2
BwABNgMAASgDAAFAAwABMAMAAQEBAAEgBgABMP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/
AP8AugADwgH/Az0B/wM9Af8DwgH/MAADwgH/A10B/wOCAf8DwgH/sAADPQH/AwAB/wMAAf8DPQH/
MAADggH/Az0B/wM9Af8DXQH/gAADwgH/Az0B/wM9Af8DwgH/IAADPQH/AwAB/wMAAf8DPQH/A8IB
/wNdAf8DggH/A8IB/xAAA8IB/wM9Af8DPQH/A8IB/wNdAf8DPQH/Az0B/wNdAf8EAAOSAf8DkgH/
A8IB/3AAAz0B/wMAAf8DAAH/Az0B/yAAA8IB/wM9Af8DPQH/A8IB/wOCAf8DPQH/Az0B/wOCAf8Q
AAM9Af8DAAH/AwAB/wM9Af8DwgH/A10B/wOCAf8DwgH/A5IB/wOCAf8DggH/A5IB/3AAAz0B/wMA
Af8DAAH/Az0B/zAAA10B/wM9Af8DPQH/A10B/xAAAz0B/wMAAf8DAAH/Az0B/xAAA5IB/wOSAf8D
kgH/A8IB/3AAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wNdAf8DggH/A8IB/xAAA8IB/wM9Af8DPQH/
A8IB/xAAA8IB/wOSAf8DkgH/A8IB/zgAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wOCAf8DXQH/A8IB
/zAAA8IB/wPCAf8DkgH/A8IB/zQAA8IB/wPCAf80AAM9Af8DAAH/AwAB/wM9Af8wAANdAf8DPQH/
Az0B/wNdAf8wAAOSAf8DggH/A4IB/wOSAf8wAAPCAf8DwgH/A8IB/wPCAf8wAAM9Af8DAAH/AwAB
/wM9Af8wAAOCAf8DPQH/Az0B/wOCAf8wAAPCAf8DggH/A5IB/wOSAf8wAAPCAf8DwgH/A8IB/wPC
Af8wAAPCAf8DPQH/Az0B/wPCAf8wAAPCAf8DggH/A10B/wPCAf8wAAPCAf8DkgH/A5IB/wPCAf80
AAPCAf8DwgH/EAADwgH/A8IB/xQAA8IB/wOCAf8DXQH/A8IB/zAAA8IB/wOSAf8DkgH/A8IB/zQA
A8IB/wPCAf9UAAPCAf8DwgH/A8IB/wPCAf8QAANdAf8DPQH/Az0B/wNdAf8wAAOSAf8DggH/A5IB
/wOSAf8wAAPCAf8DwgH/A8IB/wPCAf9QAAPCAf8DwgH/A8IB/wPCAf8DwgH/A8IB/wOSAf8DwgH/
A4IB/wM9Af8DPQH/A4IB/yQAA8IB/wPCAf8EAAPCAf8DggH/A5IB/wOSAf8wAAPCAf8DwgH/A8IB
/wPCAf9UAAPCAf8DwgH/BAADkgH/A4IB/wOCAf8DkgH/A8IB/wOCAf8DXQH/A8IB/yAAA8IB/wPC
Af8DwgH/A8IB/wPCAf8DkgH/A5IB/wPCAf80AAPCAf8DwgH/ZAADkgH/A5IB/wOSAf8DkgH/MAAD
wgH/A8IB/wPCAf8DwgH/sAADwgH/A5IB/wOSAf8DwgH/NAADwgH/A8IB/7QAA8IB/wPCAf8DkgH/
A8IB/zQAA8IB/wPCAf+0AAOSAf8DggH/A4IB/wOSAf8wAAPCAf8DwgH/A8IB/wPCAf+gAAPCAf8D
XQH/A4IB/wPCAf8DkgH/A5IB/wOSAf8DwgH/BAADwgH/A8IB/xQAA8IB/wPCAf8DkgH/A8IB/wPC
Af8DwgH/A8IB/wPCAf8kAAPCAf8DwgH/dAADggH/Az0B/wM9Af8DggH/A8IB/wOSAf8DkgH/A8IB
/wPCAf8DwgH/A8IB/wPCAf8QAAOSAf8DggH/A4IB/wOSAf8EAAPCAf8DwgH/JAADwgH/A8IB/wPC
Af8DwgH/cAADXQH/Az0B/wM9Af8DggH/EAADwgH/A8IB/wPCAf8DwgH/EAADkgH/A5IB/wOSAf8D
kgH/MAADwgH/A8IB/wPCAf8DwgH/cAADwgH/A10B/wNdAf8DwgH/FAADwgH/A8IB/xQAA8IB/wOS
Af8DkgH/A8IB/zQAA8IB/wPCAf9sAAPCAf8DPQH/Az0B/wPCAf8wAAPCAf8DXQH/A4IB/wPCAf8w
AAPCAf8DwgH/A5IB/wPCAf80AAPCAf8DwgH/NAADPQH/AwAB/wMAAf8DPQH/MAADggH/Az0B/wM9
Af8DXQH/MAADkgH/A4IB/wOCAf8DkgH/MAADwgH/A8IB/wPCAf8DwgH/MAADPQH/AwAB/wMAAf8D
PQH/MAADXQH/Az0B/wM9Af8DggH/MAADkgH/A5IB/wOSAf8DkgH/MAADwgH/A8IB/wPCAf8DwgH/
MAADwgH/Az0B/wM9Af8DwgH/MAADwgH/A10B/wNdAf8DwgH/MAADwgH/A5IB/wOSAf8DwgH/NAAD
wgH/A8IB/3wAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wNdAf8DggH/A8IB/zAAA8IB/wPCAf8DkgH/
A8IB/xAAA8IB/wM9Af8DPQH/A8IB/1AAAz0B/wMAAf8DAAH/Az0B/zAAA4IB/wM9Af8DPQH/A10B
/zAAA5IB/wOCAf8DggH/A5IB/xAAAz0B/wMAAf8DAAH/Az0B/1AAAz0B/wMAAf8DAAH/Az0B/zAA
A10B/wM9Af8DPQH/A4IB/wOSAf8DPQH/Az0B/wPCAf8gAAOSAf8DkgH/A5IB/wOSAf8DwgH/A10B
/wOCAf8DwgH/Az0B/wMAAf8DAAH/Az0B/1AAA8IB/wM9Af8DPQH/A8IB/zAAA8IB/wOCAf8DXQH/
A8IB/wM9Af8DAAH/AwAB/wM9Af8gAAPCAf8DkgH/A5IB/wPCAf8DggH/Az0B/wM9Af8DXQH/A8IB
/wM9Af8DPQH/A8IB/6AAAz0B/wMAAf8DAAH/Az0B/zAAA10B/wM9Af8DPQH/A4IB/7AAA8IB/wM9
Af8DPQH/A8IB/zAAA8IB/wOCAf8DXQH/A8IB/xgAAUIBTQE+BwABPgMAASgDAAFAAwABMAMAAQEB
AAEBBQABgAEBFgAD/4EABP8B/AE/AfwBPwT/AfwBPwH8AT8D/wHDAfwBAwHAASMD/wHDAfwBAwHA
AQMD/wHDAf8DwwP/AcMB/wPDAf8B8AH/AfAB/wHwAf8B+QH/AfAB/wHwAf8B8AH/AfAB/wHwAf8B
8AH/AfAB/wHwAf8B8AH/AfAB/wHwAf8B+QHnAcMB/wHDAf8B5wL/AsMB/wHDAf8BwwL/AcABAwH+
AUMB/wHDAv8B5AEDAfwBAwH/AecC/wH8AT8B/AE/BP8B/AE/Af4BfwT/AfwBPwH+AX8E/wH8AT8B
/AE/BP8BwAEnAcABPwHnA/8BwAEDAcIBfwHDA/8DwwH/AcMD/wHDAecBwwH/AecD/wEPAf8BDwH/
AQ8B/wGfAf8BDwH/AQ8B/wEPAf8BDwH/AQ8B/wEPAf8BDwH/AQ8B/wEPAf8BDwH/AQ8B/wGfA/8B
wwH/AcMB/wLDAv8BwwH/AcMB/wLDAv8BwwH/AcABPwHAAQMC/wHDAf8BwAE/AcABAwT/AfwBPwH8
AT8E/wH8AT8B/AE/Cw=='))
#endregion
$imagelistButtonBusyAnimation.ImageStream = $Formatter_binaryFomatter.Deserialize($System_IO_MemoryStream)
$Formatter_binaryFomatter = $null
$System_IO_MemoryStream = $null
$imagelistButtonBusyAnimation.TransparentColor = 'Transparent'
#
# timerJobTracker
#
$timerJobTracker.add_Tick($timerJobTracker_Tick)
$form1.ResumeLayout()
#endregion Generated Form Code
#----------------------------------------------
#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($Form_StateCorrection_Load)
#Clean up the control events
$form1.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
return $form1.ShowDialog()
} #End Function
#Call the form
Call-powershell-job-tracker-PoshRSJob_psf | Out-Null
Notes:
You will get the below error if you use the latest version of PoshRSJob module , i had to replace the Start-RSJob.ps1 with the latest one of this location https://github.com/MVKozlov/PoshRSJob and the error is gone
ERROR: You cannot call a method on a null-valued expression. Start-RSJob.ps1 (462, 70): ERROR: At Line: 462 char: 70 ERROR: + ... Write-Verbose "Adding Argument: $($_) " ERROR: + ~~~~~~~~~~~~~~~~~~~~~ ERROR: + CategoryInfo : InvalidOperation: (:) [], RuntimeException ERROR: + FullyQualifiedErrorId : InvokeMethodOnNull ERROR:
Request-Help Needed.
I have read about synchronized hash use in order to exchange info between runspaces .(between forms controls in my case i think)
but it is very difficult to comprehend
at the moment !!
Ideally i would like to provide me a working example of my code doing exactly the same using the PoshRSJob instead of Powershell jobs.The reason for this is to make comparisons and try to dig further,it would be HUGE help
I am aware that this is advanced stuff and far beyond my level of expertise but it would be of great help to achieve my goal in an efficient manner.In the meantime i have learned a lot and getting excited about the capabilities and prospects of Powershell and runspaces.
Epilogue
I would like to thank everyone in advance and sorry for this large post