Updating a Progress Bar within a loop, within a click event?

I’m trying to get a Progress Bar to update within a loop but it’s not displaying it at all, even when it’s completed. I’m really stuck, can anyone help?

The code for the click event is:

$SyncHash.home_submit_Button.Add_Click({
        if ($SyncHash.msgPath) {
            $SyncHash.threatLevel = 0
            if ($SyncHash.msg) {
                [System.Runtime.Interopservices.Marshal]::ReleaseComObject($SyncHash.msg)
            }
            Get-Processing
            Process-Email
            Process-Results
            Start-Sleep -Milliseconds 50 #delay progress
            Get-Results
            $SyncHash.displayThreatLevel = 0
            Start-Sleep -Milliseconds 50
            $ScriptBlock = {
                Param($SyncHash)
                for($i = 0; $i -lt $SyncHash.threatLevel; $i++){Set-ProgressBar -Value $i}
            }
            try {
                Start-Job -ScriptBlock $ScriptBlock
            }
            catch [Exception] {
                Write-Debug $_
            }
        } else {
            [System.Windows.MessageBox]::Show("Please specify the location of the email (Step 2).")
        }
    })

The ‘Set-ProgressBar’ function used above is:

function Set-ProgressBar {
param( [string]$Value )

$SyncHash.Dispatcher.Invoke(
[action]{$SyncHash.res_threatlevel_ProgressBar.Value = ("$($Value)`n")},
"Render"
)

The full script (minus the XML):

#===========================================================================
# Main RunSpace
#===========================================================================

$global:SyncHash = [hashtable]::Synchronized(@{})
$mainRunspace=[RunspaceFactory]::CreateRunspace()
$mainRunspace.ApartmentState = "STA"
$mainRunspace.ThreadOptions = "ReuseThread"
$mainRunspace.Open()
$mainRunspace.SessionStateProxy.SetVariable("SyncHash",$global:SyncHash)
$psCmd = [PowerShell]::Create().AddScript({
    Add-Type -AssemblyName presentationframework, presentationcore, windowsbase

    #===========================================================================
    # Load XAML Objects In PowerShell
    #===========================================================================

    function Get-XamlObject{
	    [CmdletBinding()]
	    param (
		    [Parameter(Position = 0,
				        Mandatory = $true,
				        ValuefromPipelineByPropertyName = $true,
				        ValuefromPipeline = $true)]
		    [Alias("FullName")]
		    [System.String[]]$Path
	    )

	    BEGIN
	    {
		    Set-StrictMode -Version Latest

		    $wpfObjects = @{ }

	    } #BEGIN

	    PROCESS
	    {
		    try
		    {
			    foreach ($xamlFile in $Path)
			    {
				    #Change content of Xaml file to be a set of powershell GUI objects
				    $inputXML = Get-Content -Path $xamlFile -ErrorAction Stop
				    $inputXMLClean = $inputXML -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace 'x:Class=".*?"', '' -replace 'd:DesignHeight="\d*?"', '' -replace 'd:DesignWidth="\d*?"', ''
				    [xml]$xaml = $inputXMLClean
				    $reader = New-Object System.Xml.XmlNodeReader $xaml -ErrorAction Stop
				    $tempform = [Windows.Markup.XamlReader]::Load($reader)

				    #Grab named objects from tree and put in a flat structure
				    $namedNodes = $xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]")
				    $namedNodes | ForEach-Object {

					    $SyncHash.Add($_.Name, $tempform.FindName($_.Name))

				    } #foreach-object
			    } #foreach xamlpath
		    } #try
		    catch
		    {
			    throw $error[0]
		    } #catch
	    } #PROCESS

	    END
	    {
		    Write-Output $SyncHash
	    } #END
    }

    $path = '*removed*'

    Get-ChildItem -Path $path -Filter *.xaml -file | Where-Object { $_.Name -ne 'App.xaml' } | Get-XamlObject


    #================================================
    #--------------------Load-GUI Cleanup------------
    #================================================

    Register-ObjectEvent -InputObject $mainRunspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                }


    #================================================
    #--------------------Buttons---------------------
    #================================================

    #============================
    # 'Home' Buttons
    #============================

    $SyncHash.home_browse_Button.Add_Click({
        $SyncHash.msgPath = Get-FilePath "C:\"
        $SyncHash.home_path_TextBox.IsEnabled = $true
        $SyncHash.home_path_TextBox.Text = $SyncHash.msgPath
    })

    $SyncHash.home_clear_Button.Add_Click({
        $SyncHash.home_path_TextBox.Text = ""
    })

    $SyncHash.home_submit_Button.Add_Click({
        function Update-Window {
            Param (
                $Title,
                $Control,
                $Property,
                $Value,
                [switch]$AppendContent
            )
            $SyncHash.$Control.Dispatcher.invoke([action]{
                If ($PSBoundParameters['AppendContent']) {
                    $SyncHash.$Control.AppendText($Value)
                } Else {
                    $SyncHash.$Control.$Property = $Value
                }
            },
            "Send")
        }

        if ($SyncHash.msgPath) {
#           [System.Windows.MessageBox]::Show($SyncHash.msgPath)
            $SyncHash.threatLevel = 0
#           [System.Windows.MessageBox]::Show($SyncHash.threatLevel)
            if ($SyncHash.msg) {
                [System.Runtime.Interopservices.Marshal]::ReleaseComObject($SyncHash.msg)
            }
            Get-Processing
#           [System.Windows.MessageBox]::Show("processing?")
            Process-Email
            Process-Results
#           [System.Windows.MessageBox]::Show($SyncHash.threatLevel)
            Start-Sleep -Milliseconds 50 #delay progress
            Get-Results
#           [System.Windows.MessageBox]::Show("results?")
            $SyncHash.displayThreatLevel = 0
            Start-Sleep -Milliseconds 50
            #$SyncHash.res_threatlevel_ProgressBar.Dispatcher.InvokeAsync([action]{$SyncHash.res_threatlevel_ProgressBar.Value = $SyncHash.displayThreatLevel})
            $ScriptBlock = {
                Param($SyncHash)
                #[System.Threading.Monitor]::Enter($SyncHash.SyncRoot)
                    for($i = 0; $i -lt $SyncHash.threatLevel; $i++){Set-ProgressBar -Value $i}
                #[System.Threading.Monitor]::Exit($SyncHash.SyncRoot)
            }

            #Update-Window -Control res_threatLevel_ProgressBar -Property Value -Value $displayThreatLevel
            #$SyncHash.res_threatlevel_ProgressBar.Dispatcher.InvokeAsync([action]{$SyncHash.res_threatlevel_ProgressBar.Refresh()})
            try {
                Start-Job -ScriptBlock $ScriptBlock
            }
            catch [Exception] {
                Write-Debug $_
            }
        } else {
            [System.Windows.MessageBox]::Show("Please specify the location of the email (Step 2).")
        }
    #   [System.Windows.MessageBox]::Show("end of submit")
    })

    function Set-ProgressBar {
    param( [string]$Value )
    
    $SyncHash.Dispatcher.Invoke(
        [action]{$SyncHash.res_threatlevel_ProgressBar.Value = ("$($Value)`n")},
        "Render"
    )
}

    #===========================================================================
    # Functions (Display)
    #===========================================================================

    function Get-Home {
        $SyncHash.msgPath = ""
        $SyncHash.home_path_TextBox.Text = "e.g. C:\folder\example.msg"
        $SyncHash.home_path_TextBox.IsEnabled = $false
        $SyncHash.home_Grid.Visibility = 'Visible'
        $SyncHash.processing_Grid.Visibility = 'Collapsed'
        $SyncHash.results_Grid.Visibility = 'Collapsed'
    }

    #Show the processing window
    function Get-Processing {
        $SyncHash.home_Grid.Visibility = 'Collapsed'
        $SyncHash.processing_Grid.Visibility = 'Visible'
        $SyncHash.results_Grid.Visibility = 'Collapsed'
    }

    #Show the results window
    function Get-Results {
        $SyncHash.home_Grid.Visibility = 'Collapsed'
        $SyncHash.processing_Grid.Visibility = 'Collapsed'
        $SyncHash.results_Grid.Visibility = 'Visible'
    }

    function Update-ProgressBar {
    #   [System.Windows.MessageBox]::Show("update progress bar")
        $SyncHash.res_threatlevel_ProgressBar.Dispatcher.InvokeAsync([action]{$SyncHash.res_threatlevel_ProgressBar.Value = $SyncHash.displayThreatLevel})
        
    }


    #============================
    # Functions (Processing)
    #============================

    function Process-Email {
	    $SyncHash.outlook = New-Object -comobject outlook.application
	    $SyncHash.msg = $SyncHash.outlook.Session.OpenSharedItem($SyncHash.msgPath)
	    $SyncHash.msg | Select body | ft -AutoSize

	    $SyncHash.msgBody = $SyncHash.msg | Select-Object -ExpandProperty Body
	    $SyncHash.msgSubject = $SyncHash.msg | Select-Object -ExpandProperty Subject
	    $SyncHash.msgSenderName = $SyncHash.msg | Select-Object -ExpandProperty SenderName
	    $SyncHash.msgSenderEmailAddress = $SyncHash.msg | Select-Object -ExpandProperty SenderEmailAddress

	    $SyncHash.msgSubjectCheck = [regex]::Matches($SyncHash.msgSubject, "spam") | foreach {$_.Success}

	    $SyncHash.msgSenderDomain = [regex]::Matches($SyncHash.msgSenderEmailAddress, "@[a-z0-9]*\.") | foreach {$_.Value}
	    $SyncHash.msgSenderDomain = $SyncHash.msgSenderDomain -replace ".{1}$"
	    $SyncHash.msgSenderDomain = $SyncHash.msgSenderDomain.Substring(1)

	    $SyncHash.msgSenderCheck = [regex]::Matches($SyncHash.msgSenderName, "@[$_.msgSenderDomain]23") | foreach {$_.Success}

	    if ($SyncHash.msgSubjectCheck -eq "Success") {
		    $SyncHash.threatLevel += 20
	    }

	    if ($SyncHash.msgSenderCheck -ne "Success") {
		    $SyncHash.threatLevel += 30
	    }
    #   [System.Windows.MessageBox]::Show("process email done")
    }

    function Process-Results {
        if ($SyncHash.threatLevel -ge 50) {
            $SyncHash.res_result_TextBlock.Text = "Malicious Email"
        } else {
            $SyncHash.res_result_TextBlock.Text = "Not detected as malicious"
        }
        $SyncHash.res_threatlevel_ProgressBar.Value = "0"
    #   [System.Windows.MessageBox]::Show("process results done")
    }


    #================================================
    #--------------------Events----------------------
    #================================================

    function Get-FilePath($initialDirectory){
        $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
        $OpenFileDialog.initialDirectory = $initialDirectory
        $OpenFileDialog.filter = "Outlook Item (*.msg)| *.msg"
        $OpenFileDialog.ShowDialog() | Out-Null
        $OpenFileDialog.filename
    }

    function Display-ThreatLevel {
        $SyncHash.res_threatlevel_ProgressBar.Dispatcher.Invoke([action]{$SyncHash.res_threatlevel_ProgressBar.Value = $SyncHash.displayThreatLevel})
        $SyncHash.res_threatlevel_ProgressBar.Dispatcher.Invoke([action]{$SyncHash.res_threatlevel_ProgressBar.Refresh()})
        [System.Windows.MessageBox]::Show($SyncHash.displayThreatLevel)
    }


    #===========================================================================
    # Potentional functions for updating items
    #===========================================================================


    function Update-Window {
        Param (
            $Title,
            $Control,
            $Property,
            $Value,
            [switch]$AppendContent
        )
        $SyncHash.$Control.Dispatcher.invokeasync([action]{
            If ($PSBoundParameters['AppendContent']) {
                $SyncHash.$Control.AppendText($Value)
            } Else {
                $SyncHash.$Control.$Property = $Value
            }
        },
        "Normal")
    }

    Function Update-Window_Old {
            Param (
                $Control,
                $Property,
                $Value,
                [switch]$AppendContent
            )
 
            # This is kind of a hack, there may be a better way to do this
            If ($Property -eq "Close") {
                $SyncHash.Window.Dispatcher.invoke([action]{$SyncHash.Window.Close()},"Normal")
                Return
            }
 
            # This updates the control based on the parameters passed to the function
            $SyncHash.$Control.Dispatcher.Invoke([action]{
                # This bit is only really meaningful for the TextBox control, which might be useful for logging progress steps
                If ($PSBoundParameters['AppendContent']) {
                    $SyncHash.$Control.AppendText($Value)
                } Else {
                    $SyncHash.$Control.$Property = $Value
                }
            },
            "Send")
        }


    #================================================
    #---------------------Load-GUI-------------------
    #================================================

        $SyncHash.Email_Check.ShowDialog() | Out-Null
        $SyncHash.Error = $Error

})

$psCmd.Runspace = $mainRunspace
$data = $psCmd.BeginInvoke()

Wow. Lotta code. Honestly, a bit much for me to read. :slight_smile: Nicely formatted, though!

It appears as if you’re launching jobs. Any GUI elements within a job won’t display on the main screen - they can’t, as they’re essentially on a separate process with its own identity (that’s an oversimplification, but it’s the same basic effect). Jobs run in the background, meaning, ENTIRELY in the background. Ergo, not in the foreground, which is what you can see.

This is one of the difficulties in using WPF or, especially, WinForms, from within PowerShell. As a single-threaded environment, you’re always going to run up against “edges” that wouldn’t exist if you were coding in C#.