Animated GIFs during installation progress

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.

2 Likes