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.

Add PowerShell 7-preview to Microsoft Windows Terminal (0.6+)

Update: the Windows Terminal now automatically detects PowerShell Core and this blog post is defunct.

The new Windows Terminal continues to be developed and it has a dwindling list of deal-breakers (why won’t my mousewheel scroll??)

Along the way, the format of the settings file has changed, and my previous method for adding a profile for PowerShell 7 no longer works.

Below is a new block of code that’ll do the job. As before, just copy-n-paste into a PowerShell window and you’re good to go.

PowerShell 7-preview x64 needs to be installed and you need to have run Windows Terminal at least once.

I’m now doing horrible things with strings – because it’s easier than dealing with a comments and a JSON object – so this code is officially 3x more likely to blow up.

# Get Windows Terminal settings
$terminalFolderPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState"
$settingsFilePath = Join-Path $terminalFolderPath 'profiles.json'
[System.Collections.ArrayList]$settings = Get-Content $settingsFilePath
# Download icon
$pwsh7IconPath = Join-Path $terminalFolderPath 'pwsh7.ico'
Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/weebsnore/Add-PS7ToWindowsTerminal/master/pwsh7.ico' -OutFile $pwsh7IconPath
# Generate PS7 profile JSON
$ps7profile = @{
    'guid' = '{' + (New-Guid).ToString() + '}'
    'name' = 'PowerShell 7-preview (x64)'
    'commandline' = 'C:\Program Files\PowerShell\7-preview\pwsh.exe'
    'icon' = $pwsh7IconPath
} | ConvertTo-Json
# Append comma to profile JSON
$ps7profile = $ps7profile + ','
# Find "profiles" line number
$profilesLine = ($settings | Select-String '"profiles":').LineNumber
# Add new profile to JSON and write to disk
,$settings.Insert($profilesLine+1,$ps7profile)
$settings | Out-File $settingsFilePath

Code here on GitHub.

Tutorial: Use a CSV file to make a graphical menu of PowerShell scripts

I’ve recently been working on a PowerShell module that uses a CSV file to create a custom menu of scripts.

It looks like this:

It hopefully helps to bridge the gap between engineers and automators, who write scripts useful to others, and service desk people and technicians, who may not be confident with the command line. PSScriptMenuGui allows PowerShell coders to put their scripts in a simple menu, usable by anyone.

It’s loosely inspired by (criminally similar to) something I made for a previous employer.

(The main difference is that this version starts instantly. The old version was so slow that I made an entertaining loading screen to fill the void. A good subject for a future blog post…)

If you’d like to dive straight in, please:

The rest of this blog post acts as a tutorial.

If you’d like to be guided through making your own menu, keep reading…

Step 0: System requirements

The module works on Windows only – sorry, rest of world!

Apart from that, it should run pretty much anywhere. It works on:

  • PowerShell for Windows 5.1 which comes with Windows 10.
  • PowerShell 7, currently available as a preview and due to be finished at the start of 2020.

It does not work on PowerShell Core 6. If you have this version, the easiest solution is to use PowerShell 5.1 as it is already on your PC.

Step 1: Install the module and make an example menu

Open a PowerShell prompt and:

# Navigate to where you want to work on your menu - in my case OneDrive:
cd $env:OneDrive
# Install the module:
Install-Module PSScriptMenuGui -Scope CurrentUser
# You may need read and agree to messages about updates and trust
# Make an example menu to get you going:
New-ScriptMenuGuiExample

You should see this:

VERBOSE: Copying example files to PSScriptMenuGui_example...

Step 2: Explore the example

Navigate to your PSScriptMenuGui_example folder and open PSScriptMenuGui.ps1. You should see a bit of boilerplate to ensure that the module is loaded, followed by this line which displays the menu:

Show-ScriptMenuGui -csvPath '.\example_data.csv' -Verbose

Try running the line in your PowerShell window. You should see the example menu from the GIF at the top of this post.

Now open example_data.csv. A text editor is fine but Excel is easier. You can see that every row in the CSV represents an item in the menu.

Step 3: Make it your own

Experiment with editing the CSV and running the Show-ScriptMenuGui command again to see your changes.

A few ideas:

  • Put one of your scripts in the folder and add it to the menu using Method powershell_file and Command .\filename_of_script.ps1.
  • Try including PowerShell commands in the CSV file. Use Method powershell_inline and Command Get-ComputerInfo. Run Show-ScriptMenuGui with -NoExit to stop the PowerShell window from closing.
  • Add a link to an external application. Use Method cmd and enter the path of the program in Command.

Step 4: Next steps

Step 5: Make a shortcut

When you’re happy with your menu, why not make a shortcut to it in File Explorer?

  1. Right click a blank area in a folder window or on your desktop.
  2. Select New → Shortcut
  3. Enter something like this as the location:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\full\path\PSScriptMenuGui_example\PSScriptMenuGui.ps1"

Add PowerShell-7 preview to Windows Terminal in 30 seconds

Edit: The stuff below doesn’t work any more. But this new stuff works.

Fun times in Windows land over the last few days with the release of new previews of Windows Terminal (v0.4) and PowerShell 7 (Preview 3).

To celebrate, here’s a simple script that you can copy-and-paste into a PowerShell window to add PowerShell 7 to Windows Terminal. (You can even use the Windows Terminal PowerShell terminal. Wow!)

The whole thing might take 30 seconds, which is a significant improvement on my last effort of 2 minutes. Why not take 1 minutes 30 seconds to make yourself a drink?

Pre-requisites:

  • PowerShell 7-preview x64 is installed
  • Windows Terminal v0.4 is installed and has been run

Here’s what it looks like:

Here’s the code:

$terminalFolderPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState"
# Get Windows Terminal settings file
$settingsFilePath = Join-Path $terminalFolderPath 'profiles.json'
$json = Get-Content $settingsFilePath | ConvertFrom-Json
# Get profiles
$profiles = $json.profiles
# Make a copy of first profile and configure for PS7 x64
$ps7 = $profiles[0].psobject.Copy()
$ps7.name = 'PowerShell 7-preview (x64)'
$ps7.commandline = 'C:\Program Files\PowerShell\7-preview\pwsh.exe'
$ps7.guid = '{' + (New-Guid).ToString() + '}'
# Download and set icon
$pwsh7IconPath = Join-Path $terminalFolderPath 'pwsh7.ico'
Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/weebsnore/Add-PS7ToWindowsTerminal/master/pwsh7.ico' -OutFile $pwsh7IconPath
$ps7.icon = $pwsh7IconPath
# Write updated settings file to disk
$json.profiles = $profiles + $ps7
$json | ConvertTo-Json | Out-File $settingsFilePath

And here it is on GitHub.

Get Task Manager list of Apps with PowerShell

Over the past couple of years I’ve been impressed by a series of small improvements to the Task Manager which have made it pretty great to use.

I recently noticed that you if you right click the column titles in the Processes tab and tick all the boxes, you rarely have to venture to the Details tab. (The most valuable column to add, in my opinion, is Command line.)

The Processes tab also attempts to lump items into a few categories: Apps, Background processes, Windows processes.

How does it do this?

Luckily, Raymond Chen briefly explains what’s going on in a blog post from 2017.

To take Apps as an example: If the process has a visible window, then Task Manager calls it an “App”

Can we do something similar with PowerShell?

Probably. Kinda.

Here’s my attempt:

Get-Process | Where-Object {$_.MainWindowTitle} | Select-Object Description

And the result:

You can see that I get processes that have a MainWindowTitle and display the process Description.

The results are similar but not identical: PowerShell shows some bits of Windows internals that are displayed elsewhere in Task Manager.

Can you get any closer?