Animated GIFs during installation progress

Hi team,

Long time user of PSappdeploy, first time poster.

I’ve created an animated GIF to use to show some helpful tips to users while an installation takes place in the background, to replace the static banner. The GIF works perfectly during the welcome prompt and post-installation window, however it does not animate on the progress window (program is installing. Please wait…). Has anyone had a similar issue or have a workaround?

Thank you!
Brian

Installation Progress uses WPF instead of Windows forms. Thats why it is not working the same way.

Thanks - I ended up using MediaElement along with Storyboard and effectively replaced the full XAML progress string. I used an MP4 file (instead of a gif, for better quality). Some of the customization has been lost (such as the custom appdeploy ICO) but I’m working on that.

I’ll post the script once I have it working correctly.

If anyone is interested, replace the below from line 8129 (in AppDeployToolkitMain.ps1, v3.8.4) and update the MediaTimeline source with your GIF or video.

		## Check if the progress thread is running before invoking methods on it
		If ($script:ProgressSyncHash.Window.Dispatcher.Thread.ThreadState -ne 'Running') {
			#  Notify user that the software installation has started
			$balloonText = "$deploymentTypeName $configBalloonTextStart"
			Show-BalloonTip -BalloonTipIcon 'Info' -BalloonTipText $balloonText
			#  Create a synchronized hashtable to share objects between runspaces
			$script:ProgressSyncHash = [hashtable]::Synchronized(@{ })
			#  Create a new runspace for the progress bar
			$script:ProgressRunspace = [runspacefactory]::CreateRunspace()
			$script:ProgressRunspace.ApartmentState = 'STA'
			$script:ProgressRunspace.ThreadOptions = 'ReuseThread'
			$script:ProgressRunspace.Open()
			#  Add the sync hash to the runspace
			$script:ProgressRunspace.SessionStateProxy.SetVariable('progressSyncHash', $script:ProgressSyncHash)
			#  Add other variables from the parent thread required in the progress runspace
			$script:ProgressRunspace.SessionStateProxy.SetVariable('installTitle', $installTitle)
			$script:ProgressRunspace.SessionStateProxy.SetVariable('windowLocation', $windowLocation)
			$script:ProgressRunspace.SessionStateProxy.SetVariable('topMost', $topMost.ToString())
			$script:ProgressRunspace.SessionStateProxy.SetVariable('appDeployLogoBanner', $appDeployLogoBanner)
			$script:ProgressRunspace.SessionStateProxy.SetVariable('ProgressStatusMessage', $statusMessage)
			$script:ProgressRunspace.SessionStateProxy.SetVariable('AppDeployLogoIcon', $AppDeployLogoIcon)


			#  Add the script block to be executed in the progress runspace
			$progressCmd = [PowerShell]::Create().AddScript({
				[string]$xamlProgressString = @'
				<Window 
				xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
				xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        		x:Name="Window" Title="Microsoft Office 365" Height="720" Width="1280" MinHeight="750" MinWidth="1280" WindowStartupLocation = "Manual" Topmost="True" ResizeMode="NoResize" Icon="" ShowInTaskbar="True">
    			<Grid>
     			<MediaElement Name="picture">
      				<MediaElement.Triggers>
        				<EventTrigger RoutedEvent="MediaElement.Loaded">
          					<EventTrigger.Actions>
            					<BeginStoryboard>
              						<Storyboard>
                						<MediaTimeline Source="file://c:\users\public\test2.mp4" 
										Storyboard.TargetName="picture" 
										RepeatBehavior="Forever" />
              						</Storyboard>
            					</BeginStoryboard>
          					</EventTrigger.Actions>
        				</EventTrigger>
      				</MediaElement.Triggers>
     			</MediaElement>
    			</Grid>
				</Window>
'@
				[Xml.XmlDocument]$xamlProgress = New-Object 'System.Xml.XmlDocument'
				$xamlProgress.LoadXml($xamlProgressString)
				## Set the configurable values using variables added to the runspace from the parent thread
				$xamlProgress.Window.TopMost = $topMost
				$xamlProgress.Window.Icon = $AppDeployLogoIcon
				$xamlProgress.Window.Grid.Image.Source = $appDeployLogoBanner
				$xamlProgress.Window.Title = $installTitle
				#  Parse the XAML
				$progressReader = New-Object -TypeName 'System.Xml.XmlNodeReader' -ArgumentList $xamlProgress
				$script:ProgressSyncHash.Window = [Windows.Markup.XamlReader]::Load($progressReader)
				#  Grey out the X button
				$script:ProgressSyncHash.Window.add_Loaded({
					#  Calculate the position on the screen where the progress dialog should be placed
					[int32]$screenWidth = [System.Windows.SystemParameters]::WorkArea.Width
					[int32]$screenHeight = [System.Windows.SystemParameters]::WorkArea.Height
					[int32]$screenCenterWidth = $screenWidth - $script:ProgressSyncHash.Window.ActualWidth
					[int32]$screenCenterHeight = $screenHeight - $script:ProgressSyncHash.Window.ActualHeight
					#  Set the start position of the Window based on the screen size
					If ($windowLocation -eq 'BottomRight') {
						#  Put the window in the corner
						$script:ProgressSyncHash.Window.Left = [Double]($screenCenterWidth)
						$script:ProgressSyncHash.Window.Top = [Double]($screenCenterHeight)
					}
					ElseIf($windowLocation -eq 'TopCenter'){
						$script:ProgressSyncHash.Window.Left = [Double]($screenCenterWidth / 2)
						$script:ProgressSyncHash.Window.Top = [Double]($screenCenterHeight / 6)
					}
					Else {
						#  Center the progress window by calculating the center of the workable screen based on the width of the screen minus half the width of the progress bar
						$script:ProgressSyncHash.Window.Left = [Double]($screenCenterWidth / 2)
						$script:ProgressSyncHash.Window.Top = [Double]($screenCenterHeight / 2)
					}
					#  Grey out the X button
					try {
						$windowHandle = (New-Object -TypeName System.Windows.Interop.WindowInteropHelper -ArgumentList $this).Handle
						If ($windowHandle -and ($windowHandle -ne [IntPtr]::Zero)) {
							$menuHandle = [PSADT.UiAutomation]::GetSystemMenu($windowHandle, $false)
							If ($menuHandle -and ($menuHandle -ne [IntPtr]::Zero)) {
								[PSADT.UiAutomation]::EnableMenuItem($menuHandle, 0xF060, 0x00000001)
								[PSADT.UiAutomation]::DestroyMenu($menuHandle)
							}
						}
					}
					catch {
						# Not a terminating error if we can't grey out the button
						Write-Log "Failed to grey out the Close button." -Severity 2 -Source ${CmdletName}
					}
				})
				#  Prepare the ProgressText variable so we can use it to change the text in the text area
				#  Add an action to the Window.Closing event handler to disable the close button
				$script:ProgressSyncHash.Window.Add_Closing({ $_.Cancel = $true })
				#  Allow the window to be dragged by clicking on it anywhere
				$script:ProgressSyncHash.Window.Add_MouseLeftButtonDown({ $script:ProgressSyncHash.Window.DragMove() })
				#  Add a tooltip
				$script:ProgressSyncHash.Window.ToolTip = $installTitle
				$null = $script:ProgressSyncHash.Window.ShowDialog()
				$script:ProgressSyncHash.Error = $Error
			})

			$progressCmd.Runspace = $script:ProgressRunspace
			#  Invoke the progress runspace
			$null = $progressCmd.BeginInvoke()
			#  Allow the thread to be spun up safely before invoking actions against it.
			Start-Sleep -Seconds 1
			If ($script:ProgressSyncHash.Error) {
				Write-Log -Message "Failure while displaying progress dialog. `n$(Resolve-Error -ErrorRecord $script:ProgressSyncHash.Error)" -Severity 3 -Source ${CmdletName}
			}
		}
	}
	End {
		Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
	}
}
#endregion


#region Function Close-InstallationProgress
Function Close-InstallationProgress {
	<#
	.SYNOPSIS
		Closes the dialog created by Show-InstallationProgress.
	.DESCRIPTION
		Closes the dialog created by Show-InstallationProgress.
		This function is called by the Exit-Script function to close a running instance of the progress dialog if found.
	.EXAMPLE
		Close-InstallationProgress
	.NOTES
		This is an internal script function and should typically not be called directly.
	.LINK
		http://psappdeploytoolkit.com
	#>
		[CmdletBinding()]
		Param (
		)
	
		Begin {
			## Get the name of this function and write header
			[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
			Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
		}
		Process {
			If ($script:ProgressSyncHash.Window.Dispatcher.Thread.ThreadState -eq 'Running') {
				## Close the progress thread
				Write-Log -Message 'Close the installation progress dialog.' -Source ${CmdletName}
				$script:ProgressSyncHash.Window.Dispatcher.InvokeShutdown()
				$script:ProgressSyncHash.Clear()
				$script:ProgressRunspace.Close()
			}
		}
		End {
			Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
		}
	}
	#endregion

It could still use a bit of cleaning up but it works.

1 Like