PoshProgressBar.psm1

#.ExternalHelp PoshProgressBar.psm1-help.xml
Function New-ProgressBar 
{
 
    param(
        
        [Parameter(ParameterSetName='Standard')]
        [Parameter(ParameterSetName='MaterialDesign')]
        [String]$IconPath,
        
        [Parameter(ParameterSetName='Standard')]
        [Parameter(ParameterSetName='MaterialDesign')]
        [Bool]$IsIndeterminate = $True,

        [Parameter(ParameterSetName='MaterialDesign', Mandatory=$true)]
        [switch]$MaterialDesign,
        
        [Parameter(ParameterSetName='MaterialDesign')]
        [ValidateSet("Circle","Horizontal","Vertical")]
        [String]$Type = "Horizontal",

        [Parameter(ParameterSetName='MaterialDesign')]
        [ValidateSet("Red","Pink","Purple","DeepPurple","Indigo",
                      "Blue","LightBlue","Cyan","Teal","Green","LightGreen",
                      "Lime","Yellow","Amber","Orange","DeepOrange","Brown",
                      "Grey","BlueGrey")]
        [String]$PrimaryColor = "Blue",

        [Parameter(ParameterSetName='MaterialDesign')]
        [ValidateSet("Red","Pink","Purple","DeepPurple","Indigo",
                      "Blue","LightBlue","Cyan","Teal","Green","LightGreen",
                      "Lime","Yellow","Amber","Orange","DeepOrange")]
        [String]$AccentColor = "LightBlue",

        [Parameter(ParameterSetName='Standard')]
        [Parameter(ParameterSetName='MaterialDesign')]
        [ValidateSet("Large","Medium","Small")]
        [String]$Size = "Medium",

        [Parameter(ParameterSetName='MaterialDesign')]
        [ValidateSet("Dark","Light")]
        [String]$Theme = "Light"
    )

    $ProgressSize = @{"Small"=140;"Medium"=280;"Large"=560}

    [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | Out-Null
    $syncHash = [hashtable]::Synchronized(@{})
    $newRunspace =[runspacefactory]::CreateRunspace()
    $syncHash.Runspace = $newRunspace
    $syncHash.Closing = $False
    $syncHash.SecondsRemainingInput = $Null
    $syncHash.StatusInput = ''
    $syncHash.CurrentOperationInput = ''
    $syncHash.IconPath = $IconPath
    $syncHash.XAML = @"
        <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Name="Window" Title="Progress..." WindowStartupLocation = "CenterScreen"
            Width = "$($ProgressSize[$Size]+75)" SizeToContent = "Height" ShowInTaskbar = "True"
             
            $(if($MaterialDesign){
            @'
            TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        Background="{DynamicResource MaterialDesignPaper}"
        TextElement.FontWeight="Medium"
        TextElement.FontSize="14"
        FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
'@
             
            })
 
            $(
            if($SyncHash.IconPath){
                 
                @"
                Icon="$($SyncHash.IconPath)"
"@
 
}
            )
             
            >
            $(
             
                if($MaterialDesign)
                {
 
                  @"
                    <Window.Resources>
                        <ResourceDictionary>
                            <ResourceDictionary.MergedDictionaries>
                                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.$Theme.xaml" />
                                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
                                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.$PrimaryColor.xaml" />
                                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.$AccentColor.xaml" />
                            </ResourceDictionary.MergedDictionaries>
                        </ResourceDictionary>
                    </Window.Resources>
"@
 
                }
             
            )
            <StackPanel Margin="20">
            $(
 
                if($MaterialDesign)
                {
 
                    @"
                    <ProgressBar $(
                                     
                                    switch($Type) {
                                         
                                        "Circle" {
                                         
                                        @"
                                        Style="{StaticResource MaterialDesignCircularProgressBar}" Height="$($ProgressSize[$Size]+10)" Width="$($ProgressSize[$Size])"
"@
                                        }
                                         
                                        "Horizontal" {
                                         
                                        @"
                                        Orientation="Horizontal" Width="$($ProgressSize[$Size])"
"@
 
                                        }
 
                                        "Vertical" {
                                         
                                        @"
                                        Orientation="Vertical" Height="$($ProgressSize[$Size])"
"@
 
                                        }
 
                                    }
                                     
                                    ) IsIndeterminate="$($IsIndeterminate)" Name="ProgressBar" />
"@
 
                }
                else
                {
 
                    @"
                    <ProgressBar IsIndeterminate="$($IsIndeterminate)" Width="$($ProgressSize[$Size])" Name="ProgressBar" />
"@
 
                }
 
            )
                
               <TextBlock Name="PercentCompleteTextBlock" StackPanel.ZIndex = "99" Text="{Binding ElementName=ProgressBar, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
               <TextBlock Name="Status" Text="" HorizontalAlignment="Left" />
               <TextBlock Name="TimeRemaining" Text="" HorizontalAlignment="Left" />
               <TextBlock Name="CurrentOperation" Text="" HorizontalAlignment="Left" />
            </StackPanel>
        </Window>
"@


    $newRunspace.ApartmentState = "STA" 
    $newRunspace.ThreadOptions = "ReuseThread"           
    $data = $newRunspace.Open() | Out-Null
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
    
    
              
    $PowerShellCommand = [PowerShell]::Create().AddScript({    
 
   
        $syncHash.Window=[Windows.Markup.XamlReader]::parse( $SyncHash.XAML ) 
        #===========================================================================
        # Store Form Objects In PowerShell
        #===========================================================================
        ([xml]$SyncHash.XAML).SelectNodes("//*[@Name]") | %{ $SyncHash."$($_.Name)" = $SyncHash.Window.FindName($_.Name)}
        $TimeRemaining = [System.TimeSpan]

        $updateBlock = {            
            
            
            if($SyncHash.Closing -eq $True)
            {

                $SyncHash.NotifyIcon.Visible = $false
                $syncHash.Window.Close()
                [System.Windows.Forms.Application]::Exit()
                Break
            }
            
            
            $SyncHash.Window.Title = $SyncHash.Activity
            $SyncHash.ProgressBar.Value = $SyncHash.PercentComplete
            if([string]::IsNullOrEmpty($SyncHash.PercentComplete) -ne $True -and $SyncHash.ProgressBar.IsIndeterminate -eq $True)
            {

                $SyncHash.ProgressBar.IsIndeterminate = $False

            }
            $SyncHash.Status.Text = $SyncHash.StatusInput
            if($SyncHash.SecondsRemainingInput)
            {
                $TimeRemaining = [System.TimeSpan]::FromSeconds($SyncHash.SecondsRemainingInput)
                $SyncHash.TimeRemaining.Text = '{0:00}:{1:00}:{2:00}' -f $TimeRemaining.Hours,$TimeRemaining.Minutes,$TimeRemaining.Seconds
            }
            $SyncHash.CurrentOperation.Text = $SyncHash.CurrentOperationInput
            
            $SyncHash.NotifyIcon.text = "Activity: $($SyncHash.Activity)`nPercent Complete: $($SyncHash.PercentComplete)"
                       
        }

        ############### New Blog ##############
        $syncHash.Window.Add_SourceInitialized( {            
            ## Before the window's even displayed ...
            ## We'll create a timer
            $timer = new-object System.Windows.Threading.DispatcherTimer            
            ## Which will fire 4 times every second
            $timer.Interval = [TimeSpan]"0:0:0.01"            
            ## And will invoke the $updateBlock
            $timer.Add_Tick( $updateBlock )            
            ## Now start the timer running
            $timer.Start()            
            if( $timer.IsEnabled ) {            
                           
            } else {            
               $clock.Close()            
               Write-Error "Timer didn't start"            
            }            
        } )


        # Extract icon from PowerShell to use as the NotifyIcon

        if($syncHash.IconPath)
        {

            $icon = [System.Drawing.Icon]::new($syncHash.IconPath)
            $syncHash.Window.Icon = $Icon

        }
        else
        {
        
            $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$pshome\powershell.exe")
        
        }

        # Create notifyicon, and right-click -> Exit menu
        $SyncHash.NotifyIcon = New-Object System.Windows.Forms.NotifyIcon
        $SyncHash.NotifyIcon.Text = "Activity: $($SyncHash.Activity)`nPercent Complete: $($SyncHash.PercentComplete)"
        $SyncHash.NotifyIcon.Icon = $icon
        $SyncHash.NotifyIcon.Visible = $true

        $menuitem = New-Object System.Windows.Forms.MenuItem
        $menuitem.Text = "Exit"

        $contextmenu = New-Object System.Windows.Forms.ContextMenu
        $SyncHash.NotifyIcon.ContextMenu = $contextmenu
        $SyncHash.NotifyIcon.contextMenu.MenuItems.AddRange($menuitem)

        $SyncHash.NotifyIcon.add_DoubleClick({$synchash.window.Show()})


        # When Exit is clicked, close everything and kill the PowerShell process
        $menuitem.add_Click({
            $SyncHash.NotifyIcon.Visible = $false
            $syncHash.Closing = $True
            $syncHash.Window.Close()
            [System.Windows.Forms.Application]::Exit()

         })

         $Synchash.window.Add_Closing({
         
            if($SyncHash.Closing -eq $True)
            {
                
            }
            else
            {
                
                $SyncHash.Window.Hide()
                $SyncHash.NotifyIcon.BalloonTipTitle = "Your script is still running..."
                $SyncHash.NotifyIcon.BalloonTipText = "Double click to open the progress bar again."
                $SyncHash.NotifyIcon.ShowBalloonTip(100)
                $_.Cancel = $true

            }
         
         })


        
        $syncHash.Window.Show() | Out-Null
        $appContext = [System.Windows.Forms.ApplicationContext]::new()
        [void][System.Windows.Forms.Application]::Run($appContext) 
        $syncHash.Error = $Error 

    }) 
    $PowerShellCommand.Runspace = $newRunspace 
    $data = $PowerShellCommand.BeginInvoke() 
   
    
    Register-ObjectEvent -InputObject $SyncHash.Runspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                } | Out-Null

    return $syncHash

}

#.ExternalHelp PoshProgressBar.psm1-help.xml
function Write-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        $ProgressBar,
        [Parameter(Mandatory=$true)]
        [String]$Activity,
        [int]$PercentComplete,
        [String]$Status = $Null,
        [int]$SecondsRemaining = $Null,
        [String]$CurrentOperation = $Null
    ) 
   
   if($ProgressBar.Closing -eq $true) {exit}

   Write-Verbose -Message "Setting activity to $Activity"
   $ProgressBar.Activity = $Activity

   if($PercentComplete)
   {
       
       $ProgressBar.PercentComplete = $PercentComplete

   }
   

    $ProgressBar.SecondsRemainingInput = $SecondsRemaining

    $ProgressBar.StatusInput = $Status

    $ProgressBar.CurrentOperationInput = $CurrentOperation

}

#.ExternalHelp PoshProgressBar.psm1-help.xml
function Close-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        $ProgressBar
    )

    $ProgressBar.Closing = $True

}