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.