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

This topic contains 1 reply, has 2 voices, and was last updated by  Don Jones 5 months, 2 weeks ago.

  • Author
    Posts
  • #72115

    Peter
    Participant

    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()
    
  • #72125

    Don Jones
    Keymaster

    Wow. Lotta code. Honestly, a bit much for me to read. 🙂 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#.

You must be logged in to reply to this topic.