Pop up a SimCity-style PowerShell loading screen

In a previous life, I had one script that took a long time to load. Like several minutes. Like, if you’re English, long enough to make a cup of tea.

Rather than fix the underlying inefficiency, the obvious solution was to fill the void with an entertaining loading screen, inspired by a well-known city-building game, whose publisher’s handsome legal team possess a keen sense of proportionality, I’m sure.

A version of that loading screen is what I present today. The code is available on GitHub as a module if you’d like to try it out or follow along.

Here’s what it looks like:

And here’s the code from the example, for easier reading:

Import-Module .\SimCityLoadingScreen

# Create loading screen
$loadingScreen = Show-SimCityLoadingScreen

'Doing something...'
Start-Sleep -Seconds 10
'Done'

# Close loading screen
$loadingScreen.Kill()

As you can see, I import the SimCityLoadingScreen module, and then run Show-SimCityLoadingScreen and save its output in $loadingScreen.

I then do some stuff that takes a while – this could be anything, I’m using Start-Sleep as an example – and, when it’s finished, I close the loading screen with $loadingScreen.Kill()

So, how does it work?

That’s a wrap

If you look at the module code, you can see that that Show-SimCityLoadingScreen is a wrapper for Start-Process.

The function accepts a few parameters for customising the loading screen:

#param (
    [string]$WindowTitle,
    [int]$WindowHeight,
    [int]$WindowWidth,
    [string]$WelcomeMessage
)

Its first line uses the $PSEdition automatic variable to make sure that the new PowerShell process will match the current one:

# Decide whether to spawn pwsh or powershell
$powerShellPath = if ($PSEdition -eq 'Core') {'pwsh'} else {'powershell'}

Then it builds an array of strings that will be given to Start-Process as -ArgumentList. (I use the same technique in PSScriptMenuGui.) You can see that the new process will be told to run a script called loading_screen_script.ps1.

# Construct PowerShell arguments
$loadingScreenPath = Join-Path $PSScriptRoot 'loading_screen_script.ps1'
$psArguments = @()
$psArguments += '-ExecutionPolicy Bypass'
$psArguments += "-File `"$loadingScreenPath`""
# etc.

Finally, it starts the new PowerShell process:

# Launch loading screen script and return process object
# The process object can later be killed to close the window
return Start-Process -FilePath $powerShellPath -PassThru -ArgumentList $psArguments

Because Start-Process is run with -PassThru, it returns an object that represents the new process. In turn, Show-SimCityLoadingScreen returns this object (with the return keyword) to the original script, which is why the loading screen can be closed with $loadingScreen.Kill()

Now that we’ve looked at the wrapper, let’s explore the loading screen script itself. Spoiler: it makes heavy use of Get-Random.

OMG that’s so random

Here’s the meaty part of loading_screen_script.ps1:

$colours = [System.Enum]::GetValues('ConsoleColor')

$messages = Get-Content (Join-Path $PSScriptRoot 'loading_messages.txt') | Sort-Object {Get-Random}

# Loop through messages
foreach ($message in $messages) {
    # Pad message to width of window
    $windowWidth = $host.UI.RawUI.WindowSize.Width
    $message = $message.PadRight($windowWidth)
    # Display message with random foreground and background
    Write-Host $message -ForegroundColor ($colours | Get-Random) -BackgroundColor ($colours | Get-Random)
    # Wait a random amount of time
    Start-Sleep -Milliseconds (Get-Random -Minimum 0 -Maximum 1500)
}

You can see the script stores the 16 built-in console colours as $colours.

Then it grabs 108 wry, copyrighted messages that I found on GitHub somewhere, and:

  • The messages are put in random order with a very PowerShell-y bit of magic: Sort-Object {Get-Random}
  • Each message is padded to the current width of the console, so that the background colour extends across the window, and you get the nice stripey effect.
  • The message is displayed with Write-Host and a random -ForeGroundColor and -BackgroundColor ($colours | Get-Random)

By my calculation, that means there are 27,648‬ possible combinations of message and foreground and background colour, many of which are legible.

Between each message, the script pauses for a random amount of time between zero and 1.5 seconds, which I scientifically determined gives the best illusion that something is happening when in fact it is not.

I demand additional features

There you go: anatomy of a loading screen. Perhaps you learned something along the way about Start-Process and Get-Random?

What can you expect in the next version?

Er, well, probably nothing, because the whole thing is a bit of a toy and not that useful in the real world.

But I have daydreamed that these things would be neat to add:

  • An option to display loading messages in the current console à la Write-Progress.
  • Some logic that will only set a ForeGroundColor and BackgroundColor that display well together.
  • More control over the display of the loading window: perhaps it can be always-on-top, or drawn using WPF.