Learning and Sharing
  • Home
  • Blog
  • Linux
  • macOS
  • Virtualization
    • VMware
    • VirtualBox
  • Windows
    • Windows 11
    • Windows 10
    • Windows Server
  • Series
    • Symantec
    • Intune
    • Microsoft Azure
    • Powershell
    • VirtualBox
    • VMware
    • PowerShell Learning
    • Microsoft Graph
  • More
    • Auto Installation
    • AEC Installation
  • Contact
No Result
View All Result
  • Home
  • Blog
  • Linux
  • macOS
  • Virtualization
    • VMware
    • VirtualBox
  • Windows
    • Windows 11
    • Windows 10
    • Windows Server
  • Series
    • Symantec
    • Intune
    • Microsoft Azure
    • Powershell
    • VirtualBox
    • VMware
    • PowerShell Learning
    • Microsoft Graph
  • More
    • Auto Installation
    • AEC Installation
  • Contact
No Result
View All Result
No Result
View All Result

[Winforms] Creating a Responsive Windows Form Using PowerShell With Runspaces

December 18, 2023
in Blog, Powershell
0
ADVERTISEMENT

Table of Contents

I recently wrote a PowerShell script that used the Windows Forms .NET assembly (Winforms) to create a graphical user interface. The form included a button that initiated a long-running task.

While the task was executing, I wanted to ensure the form remained responsive to user input, and I also wanted to update a status field on the form with the progress of the executing task.

Below is the code for the script that we’ve create.

Add-Type -AssemblyName System.Windows.Forms

# Long running task with runspace
function PoSHCounter {
    # Disable the button 
    $button.Enabled = $false

    for ($i = 10; $i -le 99; $i++) {
        $label.Text = $i
        Start-Sleep -Milliseconds 50
    }

    # Re-enable the button when the loop finishes.
    $button.Enabled = $true
}

# Create the form.
    $form                   = New-Object Windows.Forms.Form
    $form.ClientSize        = New-Object Drawing.Size(400, 200)
    $form.Text              = "Responsive Windows Form"
    $form.StartPosition     = "CenterScreen"
    $form.FormBorderStyle   = "FixedSingle"

# Create the button.
    $button             = New-Object Windows.Forms.Button
    $button.Location    = New-Object Drawing.Point(100, 20)
    $button.Width       = 180
    $button.Text        = "Start Counting"
    $button.Add_Click({PoSHCounter})

# Create the label.
    $label              = New-Object Windows.Forms.Label
    $label.Location     = New-Object Drawing.Point(125, 60)
    $label.Font         = New-Object System.Drawing.Font("Consolas",63,[System.Drawing.FontStyle]::Bold)
    $label.ForeColor    = [System.Drawing.Color]::DarkBlue
    $label.AutoSize     = $True
    $label.Text         = 10

# Add controls to the form.
    $form.Controls.AddRange(@($button, $label))

# Show the form.
    [Windows.Forms.Application]::Run($form)

Now, by default, any graphical interface created via PowerShell will run in a single-threaded apartment; which means that the application will be blocked, or become unresponsive, while it is waiting for a task to complete.

Gxasdasda5rwertwecsessdasvt9fdczczxcsfdsdfwfdsfU6JME8I3dssdfsdfsdfdsaofGF65756WSr5345365fsd

Responsive Windows Form with runspace

The best way of overcoming this restriction is to leverage .NET runspaces (each runspace provides a separate execution context for a PowerShell pipeline).

The following code provides a simplified example of how I used runspaces to manage the execution of a long running task.

  • The script creates a form with a button and a text label.
  • The button initiates a script block that counts to 99.
  • The button is intentionally disabled during this period.
  • The window remains responsive throughout, and the label is incrementally refreshed to show the value of the counter.
Add-Type -AssemblyName System.Windows.Forms
# Long running task with runspace
function PoSHCounter {
    # The main script block
    $scriptBlock = {
        # Disable the button 
        $sync.button.Enabled = $false
        
        for ($i = 10; $i -le 99; $i++) {
            $sync.label.Text = $i
            Start-Sleep -Milliseconds 50
        }
        # Re-enable the button when the loop finishes.
        $sync.button.Enabled = $true
    }

    # Create a PowerShell instance with the script block
    $psInstance = [PowerShell]::Create().AddScript($scriptBlock)
    
    # Create then open a new runspace
    $runspace = [RunspaceFactory]::CreateRunspace()
    $runspace.ApartmentState = "STA"
    $runspace.ThreadOptions = "ReuseThread"
    $runspace.Open()

    # Add shared data to the runspace
    $runspace.SessionStateProxy.SetVariable("sync", $sync)
    
    # Associate the PowerShell instance with the opened runspace
    $psInstance.Runspace = $runspace

    # Execute the script asynchronously using BeginInvoke() method
    $psInstance.BeginInvoke()
}

# Create the form.
    $form                   = New-Object Windows.Forms.Form
    $form.ClientSize        = New-Object Drawing.Size(400, 200)
    $form.Text              = "Responsive Windows Form"
    $form.StartPosition     = "CenterScreen"
    $form.FormBorderStyle   = "FixedSingle"

# Create the button.
    $button             = New-Object Windows.Forms.Button
    $button.Location    = New-Object Drawing.Point(100, 20)
    $button.Width       = 180
    $button.Text        = "Start Counting"
    $button.Add_Click({PoSHCounter})

# Create the label.
    $label              = New-Object Windows.Forms.Label
    $label.Location     = New-Object Drawing.Point(125, 60)
    $label.Font         = New-Object System.Drawing.Font("Consolas",63,[System.Drawing.FontStyle]::Bold)
    $label.ForeColor    = [System.Drawing.Color]::DarkBlue
    $label.AutoSize     = $True
    $label.Text         = 10

# For talking across runspaces (share info between runspaces)
    $sync           = [hashtable]::Synchronized(@{})
    $sync.runspace  = $runspace
    $sync.host      = $host
    $sync.form      = $form
    $sync.button    = $button
    $sync.label     = $label

# Add controls to the form.
    $form.Controls.AddRange(@($sync.button, $sync.label))

# Show the form.
    [Windows.Forms.Application]::Run($form)
Gxv9ftrfv42345rwertwecsessdasvt9fdczczxcsfdsdfwfdsfU6JME8I3dssdfsdfsdfdsaofGF65756WSr5345365fsd

As you can see in the above screenshot. This time, when the job is running ( in the background) the GUI can be moved normally.

In the next section, I’ve created some examples of using runspace to do some tasks in Windows.

PowerShell runspace examples

Example 1: Ping a host:

  • The script creates a form with a button and a textbox.
  • The button initiates a script block to ping a host
  • The button is intentionally disabled during this period.
  • The window remains responsive throughout, and the textbox shows the status.
Add-Type -AssemblyName System.Windows.Forms

# Long running task with runspace
function PoSHCounter {
    # The main script block
    $scriptBlock = {
        # Disable the button 
        $sync.button.Enabled = $false

        $sync.textbox.Text = "Pinging to the host..."
        $sync.textbox.AppendText([Environment]::NewLine)

        $sync.textbox.AppendText("Completed. Below are the results:")
        $sync.textbox.AppendText([Environment]::NewLine)

        $sync.textbox.AppendText((powershell -noprofile -Command "ping 127.0.0.1" | Out-String))

        # Re-enable the button when the loop finishes.
        $sync.button.Enabled = $true
    }

    # Create a PowerShell instance with the script block
    $psInstance = [PowerShell]::Create().AddScript($scriptBlock)
    
    # Create then open a new runspace
    $runspace = [RunspaceFactory]::CreateRunspace()
    $runspace.ApartmentState = "STA"
    $runspace.ThreadOptions = "ReuseThread"
    $runspace.Open()

    # Add shared data to the runspace
    $runspace.SessionStateProxy.SetVariable("sync", $sync)
    
    # Associate the PowerShell instance with the opened runspace
    $psInstance.Runspace = $runspace

    # Execute the script asynchronously using BeginInvoke() method
    $psInstance.BeginInvoke()
}

# Create the form.
    $form                   = New-Object Windows.Forms.Form
    $form.ClientSize        = New-Object Drawing.Size(772, 380)
    $form.Text              = "Responsive Windows Form"
    $form.StartPosition     = "CenterScreen"
    $form.FormBorderStyle   = "FixedSingle"

# Create the button.
    $button             = New-Object Windows.Forms.Button
    $button.Location    = New-Object Drawing.Point(20, 20)
    $button.BackColor   = [System.Drawing.Color]::Green
    $button.ForeColor   = [System.Drawing.Color]::White
    $button.Size        = New-Object System.Drawing.Size(150,40) 
    $button.Text        = "Start Pinging"
    $button.Add_Click({PoSHCounter})

# Create a textbox to display the output
    $textbox                      = New-Object system.Windows.Forms.TextBox
    $textbox.Multiline            = $true
    $textbox.Text                 = "Waiting for output..."
    $textbox.Font                 = New-Object System.Drawing.Font("Consolas",9,[System.Drawing.FontStyle]::Regular)
    $textbox.Size                 = New-Object System.Drawing.Size(728,270)
    $textbox.Location             = New-Object System.Drawing.Point(20,70)
    $textbox.BackColor            = "#1F1F1F"
    $textbox.ForeColor            = 'Cyan'

# For talking across runspaces (share info between runspaces)
    $sync           = [hashtable]::Synchronized(@{})
    $sync.runspace  = $runspace
    $sync.host      = $host
    $sync.form      = $form
    $sync.button    = $button
    $sync.label     = $label
    $sync.textbox   = $textbox

# Add controls to the form.
    $form.Controls.AddRange(@($sync.button, $sync.label, $sync.textbox))

# Show the form.
    [Windows.Forms.Application]::Run($form)
highmogrtgvghct2idhs8p0prsezehy15hhffttg3459887654ffd

Example 2: Download a file using PowerShell

  • The script creates a form with a button and a textbox.
  • The button initiates a script block to download a file from the internet.
  • The button is intentionally disabled during this period.
  • The window remains responsive throughout, and the textbox shows the download status.
Add-Type -AssemblyName System.Windows.Forms

# Long running task with runspace
function PoSHCounter {
    # The main script block
    $scriptBlock = {
        # Disable the button 
        $sync.button.Enabled = $false

        $sync.textbox.Text = "Downloading..."
        $sync.textbox.AppendText([Environment]::NewLine)
        
        $source = 'http://ipv4.download.thinkbroadband.com/1MB.zip'
        $destination = 'D:\temp\1MB.zip'
        Invoke-WebRequest -Uri $source -OutFile $destination

        $sync.textbox.AppendText("Download completed")
        $sync.textbox.AppendText([Environment]::NewLine)
        $sync.textbox.AppendText("The downloaded file is saved to:" + "$(Get-ChildItem -Path D:\temp | Out-String)")

        # Re-enable the button when the loop finishes.
        $sync.button.Enabled = $true
    }

    # Create a PowerShell instance with the script block
    $psInstance = [PowerShell]::Create().AddScript($scriptBlock)
    
    # Create then open a new runspace
    $runspace = [RunspaceFactory]::CreateRunspace()
    $runspace.ApartmentState = "STA"
    $runspace.ThreadOptions = "ReuseThread"
    $runspace.Open()

    # Add shared data to the runspace
    $runspace.SessionStateProxy.SetVariable("sync", $sync)
    
    # Associate the PowerShell instance with the opened runspace
    $psInstance.Runspace = $runspace

    # Execute the script asynchronously using BeginInvoke() method
    $psInstance.BeginInvoke()
}

# Create the form.
    $form                   = New-Object Windows.Forms.Form
    $form.ClientSize        = New-Object Drawing.Size(772, 380)
    $form.Text              = "Responsive Windows Form"
    $form.StartPosition     = "CenterScreen"
    $form.FormBorderStyle   = "FixedSingle"

# Create the button.
    $button             = New-Object Windows.Forms.Button
    $button.Location    = New-Object Drawing.Point(20, 20)
    $button.BackColor   = [System.Drawing.Color]::Green
    $button.ForeColor   = [System.Drawing.Color]::White
    $button.Size        = New-Object System.Drawing.Size(150,40) 
    $button.Text        = "Start Pinging"
    $button.Add_Click({PoSHCounter})

# Create a textbox to display the output
    $textbox                      = New-Object system.Windows.Forms.TextBox
    $textbox.Multiline            = $true
    $textbox.Text                 = "Waiting for output..."
    $textbox.Font                 = New-Object System.Drawing.Font("Consolas",9,[System.Drawing.FontStyle]::Regular)
    $textbox.Size                 = New-Object System.Drawing.Size(728,270)
    $textbox.Location             = New-Object System.Drawing.Point(20,70)
    $textbox.BackColor            = "#1F1F1F"
    $textbox.ForeColor            = 'Cyan'
    
# For talking across runspaces (share info between runspaces)
    $sync           = [hashtable]::Synchronized(@{})
    $sync.runspace  = $runspace
    $sync.host      = $host
    $sync.form      = $form
    $sync.button    = $button
    $sync.label     = $label
    $sync.textbox   = $textbox

# Add controls to the form.
    $form.Controls.AddRange(@($sync.button, $sync.label, $sync.textbox))

# Show the form.
    [Windows.Forms.Application]::Run($form)
highmdpjvu6ogrtgvghct2idhs8p0prsezehy15hhffttg3459887654ffd

Example 3: Get the list of processes

  • The script creates a form with a button and a textbox.
  • The button initiates a script block to get the first five processes on a computer.
  • The button is intentionally disabled during this period.
  • The window remains responsive throughout, and the textbox shows the results.
Add-Type -AssemblyName System.Windows.Forms

# Long running task with runspace
function PoSHCounter {
    # The main script block
    $scriptBlock = {
        # Disable the button 
        $sync.button.Enabled = $false
        
        $sync.textbox.AppendText([Environment]::NewLine)
        $sync.textbox.AppendText((Get-Process | Select-Object -First 5 | Out-String))

        # Re-enable the button when the loop finishes.
        $sync.button.Enabled = $true
    }

    # Create a PowerShell instance with the script block
    $psInstance = [PowerShell]::Create().AddScript($scriptBlock)
    
    # Create then open a new runspace
    $runspace = [RunspaceFactory]::CreateRunspace()
    $runspace.ApartmentState = "STA"
    $runspace.ThreadOptions = "ReuseThread"
    $runspace.Open()

    # Add shared data to the runspace
    $runspace.SessionStateProxy.SetVariable("sync", $sync)
    
    # Associate the PowerShell instance with the opened runspace
    $psInstance.Runspace = $runspace

    # Execute the script asynchronously using BeginInvoke() method
    $psInstance.BeginInvoke()
}

# Create the form.
    $form                   = New-Object Windows.Forms.Form
    $form.ClientSize        = New-Object Drawing.Size(772, 380)
    $form.Text              = "Responsive Windows Form"
    $form.StartPosition     = "CenterScreen"
    $form.FormBorderStyle   = "FixedSingle"

# Create the button.
    $button             = New-Object Windows.Forms.Button
    $button.Location    = New-Object Drawing.Point(20, 20)
    $button.BackColor   = [System.Drawing.Color]::Green
    $button.ForeColor   = [System.Drawing.Color]::White
    $button.Size        = New-Object System.Drawing.Size(150,40) 
    $button.Text        = "Submit"
    $button.Add_Click({PoSHCounter})

# Create a textbox to display the output
    $textbox                      = New-Object system.Windows.Forms.TextBox
    $textbox.Multiline            = $true
    $textbox.Text                 = "Waiting for output..."
    $textbox.Font                 = New-Object System.Drawing.Font("Consolas",9,[System.Drawing.FontStyle]::Regular)
    $textbox.Size                 = New-Object System.Drawing.Size(728,270)
    $textbox.Location             = New-Object System.Drawing.Point(20,70)
    $textbox.BackColor            = "#1F1F1F"
    $textbox.ForeColor            = 'Cyan'

# For talking across runspaces (share info between runspaces)
    $sync           = [hashtable]::Synchronized(@{})
    $sync.runspace  = $runspace
    $sync.host      = $host
    $sync.form      = $form
    $sync.button    = $button
    $sync.label     = $label
    $sync.textbox   = $textbox

# Add controls to the form.
    $form.Controls.AddRange(@($sync.button, $sync.label, $sync.textbox))

# Show the form.
    [Windows.Forms.Application]::Run($form)
highmogrtgvghct2idhs8p0prsezehy15hhffttg34598gfdeui87654ffd

Adding controls to the form

There are a lot of controls that we can use to create a GUI for a PowerShell script. You can follow the below link to get more details.

  • TextBox
  • Label
  • Button
  • PictureBox
  • CheckBox
  • ComboBox (Dropdown list)
  • ListBox
  • RadioButton
  • Groupbox
  • ProgressBar
  • DataGridView
  • ColorDialog
ADVERTISEMENT

Not a reader? Watch this related video tutorial:

5/5 - (2 votes)
Previous Post

Function Cannot be Created Because Function Capacity 4096 Has Been Exceeded for This Scope

Next Post

What is the WinForms in PowerShell?

Related Posts

Running Hyper-V and VMware Workstation on The Same Machine

August 15, 2024

How to Uninstall All Autodesk Products At Once Silently

July 29, 2024
Ftr5

How to Uninstall the Autodesk Genuine Service on Windows

July 29, 2024
Ftr19

How to Fix Windows Cannot Read the ProductKey From the Unattend Answer File in VirtualBox

July 26, 2024
Ftr25

How to Update Windows Terminal in Windows 10/11

July 26, 2024

How to Disable The Beep Sound in WSL Terminal on Windows

July 26, 2024

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • How To Turn On uBlock Origin Extension in Chrome (2025)
  • Images Hidden Due To Mature Content Settings In CivitAI
  • Azure OpenAI vs Azure AI Hub, How to Choose the Right One for Your Needs

Categories

Stay in Touch

Discord Server

Join the Discord server with the site members for all questions and discussions.

Telegram Community

Jump in Telegram server. Ask questions and discuss everything with the site members.

Youtube Channel

Watch more videos, learning and sharing with Leo ❤❤❤. Sharing to be better.

Newsletter

Join the movement and receive our weekly Tech related newsletter. It’s Free.

General

Microsoft Windows

Microsoft Office

VMware

VirtualBox

Technology

PowerShell

Microsoft 365

Microsoft Teams

Email Servers

Copyright 2025 © All rights Reserved. Design by Leo with ❤

No Result
View All Result
  • Home
  • Linux
  • Intune
  • macOS
  • VMware
  • VirtualBox
  • Powershell
  • Windows 10
  • Windows 11
  • Microsoft 365
  • Microsoft Azure
  • Microsoft Office
  • Active Directory

No Result
View All Result
  • Home
  • Linux
  • Intune
  • macOS
  • VMware
  • VirtualBox
  • Powershell
  • Windows 10
  • Windows 11
  • Microsoft 365
  • Microsoft Azure
  • Microsoft Office
  • Active Directory