Start-SplashScreen.ps1

<#PSScriptInfo
 
.VERSION 1.7
.GUID b00d1997-e5da-4af1-86f0-92120cfab2f3
.AUTHOR Florian Salzmann
.COMPANYNAME scloud.work
.COPYRIGHT 2024 Florian Salzmann. GPL-3.0 license.
.TAGS Windows SplashScreen PowerShell
.LICENSEURI https://github.com/FlorianSLZ/scloud/blob/main/LICENSE
.PROJECTURI https://github.com/FlorianSLZ/scloud/tree/main/scripts/Start-SplashScreen
.ICONURI https://scloud.work/wp-content/uploads/Start-SplashScreen.webp
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
    2024-08-09, 1.0: Original published version.
    2024-08-13, 1.1: New icon, always in front.
    2024-08-14, 1.2: Removed OSDCloud dependency.
    2024-08-14, 1.3: -UseBasicParsing added to Invoke-WebRequest.
    2024-08-14, 1.4: Removed "Topmost" to allow better troubleshooting.
    2024-08-15, 1.5: Optimized online script handling.
    2024-08-15, 1.6: Added support for http and ftp.
    2025-03-14, 1.7: Added optional LogoURL parameter.
 
#>
 

<#
 
.DESCRIPTION
Start-SplashScreen is a PowerShell script designed to execute a series of scripts with a user-friendly graphical interface. It provides a visual representation of the script execution process, including progress updates and a status indicator. The script also offers the ability to open the running PowerShell terminal window for troubleshooting or additional actions.
 
.EXAMPLE
$Scripts2run = @(
  @{
    Name = "Dummy Wait for 15s"
    Script = "Start-Sleep -s 15"
  },
  @{
    Name = "Windows Quality Updates"
    Script = "https://raw.githubusercontent.com/FlorianSLZ/OSDCloud-Stuff/main/OOBE/Windows-Updates_Quality.ps1"
  }
)
 
Start-SplashScreen.ps1 -Processes $Scripts2run -LogoURL "https://scloud.work/wp-content/uploads/2023/08/terminal-logo-scloud.png"
 
#>
 

param (
    [parameter(Mandatory = $true, HelpMessage = "Specify the processes by name and PowerShell-command or https-link.")]
    [array]$Processes,
    
    [parameter(Mandatory = $false, HelpMessage = "Main message on the Splash Screen.")]
    [string]$MessageHeader = "Windows Preparation",
    
    [parameter(Mandatory = $false, HelpMessage = "Initial message where the script names will show on the Splash Screen.")]
    [string]$MessageText = "Initiate Installation",
    
    [parameter(Mandatory = $false, HelpMessage = "Initial status indicator on the Splash Screen.")]
    [string]$MessageStatus = "...",

    [parameter(Mandatory = $false, HelpMessage = "Finishing message before Splash Screen closes.")]
    [string]$MessageFinished = "All processes finished. This window closes automatically.", 

    [parameter(Mandatory = $false, HelpMessage = "Time until Splash Screen closes after finishing.")]
    [int]$ClosingTimer = 5,

    [parameter(Mandatory = $false, HelpMessage = "Background color of the Splash Screen. Eg. #CCf4f4f4 (CC = 80% transparent) or #f4f4f4")]
    [string]$ColorBackground = "#f4f4f4", 

    [parameter(Mandatory = $false, HelpMessage = "Text color of the Splash Screen. Eg. #161616")]
    [string]$ColorText = "#161616",

    [parameter(Mandatory = $false, HelpMessage = "Optional Logo URL.")]
    [string]$LogoURL = ""
)

Add-Type -AssemblyName PresentationFramework
[XML]$xaml = @"
<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="$MessageHeader"
  WindowStartupLocation="CenterScreen"
  WindowStyle="None"
  AllowsTransparency="True"
  WindowState="Maximized"
  ShowInTaskbar="False"
  Background="$ColorBackground"
  Foreground="$ColorText"
  >
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*"/>
      <RowDefinition Height="75"/>
    </Grid.RowDefinitions>
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
      <Image Name="LogoImage" Width="200" Height="200" Margin="0,0,0,20" Visibility="Hidden"/>
      <TextBlock Name="TextMessageHeader" Text="$MessageHeader" FontSize="32" FontWeight="Bold" TextAlignment="Center" />
      <TextBlock Name="TextMessageBody" Text="$MessageText" FontSize="16" TextWrapping="Wrap" TextAlignment="Center" FontStyle="Italic" Margin="0,20,0,20" />
      <TextBlock Name="TextMessageStatus" Text="$MessageStatus" FontSize="18" FontWeight="Bold" TextAlignment="Center"/>
    </StackPanel>
    <Button Grid.Row="1" Name="ShowTerminal" Content="" HorizontalAlignment="Stretch" Background="Transparent" BorderThickness="0" />
  </Grid>
</Window>
"@


# Load XAML
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$Window = [Windows.Markup.XamlReader]::Load($reader)

# Load the logo if provided
$LogoImage = $Window.FindName("LogoImage")
if (-not [string]::IsNullOrWhiteSpace($LogoURL)) {
    try {
        $bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
        $bitmap.BeginInit()
        $bitmap.UriSource = New-Object System.Uri($LogoURL)
        $bitmap.EndInit()
        $LogoImage.Source = $bitmap
        $LogoImage.Visibility = "Visible"
    }
    catch {
        Write-Warning "Failed to load logo from $LogoURL"
    }
}

# UI Elements
$messageScreenText = $Window.FindName("TextMessageBody")
$messageScreenStatus = $Window.FindName("TextMessageStatus")

$ShowTerminalButton = $Window.FindName("ShowTerminal")
$ShowTerminalButton.Add_Click({ Show-Console })

# Show/Hide Console Functions
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
 
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
 
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
'


function Show-Console {
    $consolePtr = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($consolePtr, 5)
    [Console.Window]::SetForegroundWindow($consolePtr)
}

function Hide-Console {
    $consolePtr = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($consolePtr, 0)
}

# Show the window
# Hide-Console
$Window.Show() | Out-Null

# Process scripts
$counter = 0 
$total = $Processes.Count
foreach ($script in $Processes) {
    $counter++
    $messageScreenText.Text = "$($script.Name)"
    $messageScreenStatus.Text = "($counter/$total)"

    $Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [System.Action]{})

    if ($script.Script -match "^https?://" -or $script.Script -match "^http://" -or $script.Script -match "^ftp://") {
        Write-Output "($counter/$total) - Running online script: $($script.Script)"
        $WebClient = New-Object System.Net.WebClient
        $WebPSCommand = $WebClient.DownloadString("$($script.Script)")
        Invoke-Expression -Command $WebPSCommand
        $WebClient.Dispose()
    } else {
        Write-Output "($counter/$total) - Running PowerShell command: $($script.Script)"
        Invoke-Expression $($script.Script)
    }
}

$messageScreenText.Text = $MessageFinished
for ($i = $ClosingTimer; $i -gt 0; $i--) {
    $messageScreenStatus.Text = "$i Seconds"
    $Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [System.Action]{})
    Start-Sleep -Seconds 1
}

$Window.Close()
Show-Console