Powershell and Multithreading

I have a question for the community. I am sure you are starting to get a lot of questions like these. Before I ask, let me explain my situation.

I work in an environment where compiled code is restricted and it takes an act of god to get a new application introduced into the production environment. Scripts however, are all fair game. I have found the need to parse account requests from PDF files to better handle the account creation process. In a perfect world, we would be using a pure Microsoft environment where I can fully automate the account creation process. Unfortunately, the the several accounts that I create are from a Solaris platform. I have, however, used Powershell (on a different system that is also at my work space) to create a Windows Forms application in combination with iTextSharp to drag the account request PDFs from either Outlook or another location on the system’s drive to automatically populate a DataGridView with the important information that I use to create these accounts. At first glance, this may seem like overkill, however, 1 user’s account request form can indicate a new account on up to 15 different Solaris applications. Thus, I found a need to be able to sort data on a real-time basis when creating these accounts.

That is the background information. The problem I have is sometimes we get 30 user account creation PDF’s at a time. I handle these in my application with a ForEach Loop, however the actual parsing function takes about 5-10 seconds sometimes to populate the DataGridView per each user. Importing a large amount of requests like this can sometimes freeze the application. Here comes my question.

Is is possible to populate a DataGridView with multithreading all using the same memory space?

Here is a simple example of what I am trying to do:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$Main = New-Object Windows.Forms.Form
$Main.Width = 1024
$Main.Height = 768

$ScriptBlock = `
{
# This Start-Sleep command is a representation of the amount of time it takes to parse the PDF data.
Start-Sleep -Seconds 5
$DataGrid.Rows.Add(“This is my test Name.”,“This is my test value.”)
}

$Button = New-Object Windows.Forms.Button
$Button.Location = New-Object Drawing.Point(0,708)
$Button.Size = New-Object Drawing.Size(1010,24)
$Button.Anchor = “Left,Right,Bottom”
$Button.Text = ‘Add Row (This Button is a representation of dropping a PDF account request into this window.)’

Preferably, I would like the button click to execute my function in a different thread, but still populate the $DataGrid with the results.

$Button.Add_Click($ScriptBlock)
$Main.Controls.Add($Button)

$DataGrid = New-Object System.Windows.Forms.DataGridView
$DataGrid.Size = New-Object Drawing.Size(1010,708)
$DataGrid.Anchor = “Top,Left,Right,Bottom”
$DataGrid.ColumnCount = 2
$DataGrid.Columns[0].Name = “Name”
$DataGrid.Columns[1].Name = “Value”
$Main.Controls.Add($DataGrid)

Is multithreading with Powershell possible here?

You’ve exited the world of PowerShell, which is inherently a single-threaded application, and entered the world of .NET Framework programming. Sadly, that means you might not get a good answer here - we don’t have a lot of .NET devs hanging out :(. You might try asking at StackOverflow.com if you don’t get a good answer here. Very sorry.

This sounds like something that I have done before with one of my projects: PoshPAIG. I initially used PSJobs plus eventing to try to handle the multithreading and while it worked out decently, I eventually went to using background runspaces and runspace pools to handle the workload. This does amp up the complexity as you will be handling your own runspaces, synchronized hashtables, etc… but if you get it all working, then it does a nice job of achieving the goal that you are looking for. You can download PoshPAIG and tear it open to see what I’ve done and either copy parts for your own use or come up with your own approach. I also wrote an article that deals with the idea of using runspaces to handle data using a WPF form, but it should translate to WinForms as well pretty easy.

That article is here: PowerShell and WPF: Writing Data to a UI From a Different Runspace | Learn Powershell | Achieve More

If you have any other questions after these items, feel free to post them and either myself or someone else will hopefully be able to get you in the right direction. I am not a .Net dev by any means so my techniques may not be considered to be a ‘best practice’, but it does get the job done (at least for me). :slight_smile:

Gentlemen, Thank you for your prompt reply. Don, there is no need to be sorry. I suspected that I was reaching the threshold of what powershell can effectively compute. \

Boe, it is so funny that you reply, I believe I have glanced at that exact post in the past while I was searching for something else, and thus I knew it was possible. I just couldn’t find your post again to save my life. The information you wrote about seems to be exactly what I need. Tomorrow I will tinker with my test code to try to develop a simplistic example to post here. Maybe my problem can help many to follow in the future.

Success!

Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing

This is the key that I needed to unlocking effective multithreading. Thank you very much Boe!

All data that will be shared across threads will be placed into this hash table.

$SyncHash = [hashtable]::Synchronized(@{})

$ScriptBlock = `
{
# Created the Powershell instance that will run my script block.
$NewUser = [PowerShell]::Create().AddScript({
Start-Sleep -Seconds 5
$SyncHash.DataGrid.Rows.Add(“New UserName”,“This is yet another test field.”)
})
# Created a new runspace
$NewUserRunspace = [RunspaceFactory]::CreateRunspace()
$NewUserRunspace.ApartmentState = “STA”
# The command below only allowed 1 click to be executed at a time, even if you clicked the hell out of the button while it was in progress. I commented it out for the ability to have more than 1 thread created in this runspace.
# $NewUserRunspace.ThreadOptions = “ReuseThread”
$NewUserRunspace.Open()
$NewUserRunspace.SessionStateProxy.SetVariable(“SyncHash”,$SyncHash)
# Assigned my new Powershell Instance to the runspace I just created.
$NewUser.Runspace = $NewUserRunspace
# Invoked the Powershell Instance/Script.
$NewUser.BeginInvoke()
}

$Main = New-Object Windows.Forms.Form
$Main.Width = 1024
$Main.Height = 768

$Button = New-Object Windows.Forms.Button
$Button.Location = New-Object Drawing.Point(0,708)
$Button.Size = New-Object Drawing.Size(1010,24)
$Button.Anchor = “Left,Right,Bottom”
$Button.Text = ‘Add Row (This Button is a representation of dropping a PDF account request into this window.)’

Click this beautiful button!

$Button.Add_Click($ScriptBlock)
$Main.Controls.Add($Button)

Since the DataGrid needed to be accessed from across multiple threads, it was created within the Synchronized Hash Table.

$SyncHash.DataGrid = New-Object System.Windows.Forms.DataGridView
$SyncHash.DataGrid.Size = New-Object Drawing.Size(1010,708)
$SyncHash.DataGrid.Anchor = “Top,Left,Right,Bottom”
$SyncHash.DataGrid.ColumnCount = 2
$SyncHash.DataGrid.Columns[0].Name = “UserName”
$SyncHash.DataGrid.Columns[1].Name = “Note”
#The DataGrid is still added to the main form in the normal way, nothing has changed because of the fact that I put it in another object.
$Main.Controls.Add($SyncHash.DataGrid)

Multithreading at it’s finest.

All credit goes to Don Jones for teaching me Powershell and Boe Prox for being a Genius.