Since I switched to Windows last year I'm using Microsoft's Powershell as my main shell environment (on my personal laptop). At first I didn't really look into Powershell's feature until around half a year ago when I found the motivation to work on a nicer prompt and spent some time migrating my ZSH profile to Powershell. I ended up creating a different profile that suits my needs, and that I had lot of fun building.

That's how my prompt looks:

My custom Powershell prompt

It has the following features:

  • Used for both Windows and Linux subsystem
  • Current directory
  • A dynamic "main directory" used to specify the context
  • Git branch
  • Current time
  • Last command success/failure
  • Admin user label

That was a cool and fun way to learn more about Powershell features, after spending all my life with sh, bash, and zsh it was refreshing to try out a different way to write shell scripts.

Some implementation details

Left and right prompt

That's a very common feature in other shells, you often have environment variables you can set to display some information on the left and/or right sides of the prompt. In the case of Powershell I had to implement my own solution.

# Renders a 2 columns layout, with $left argument written to the extreme left of the console, and $right to the extreme
# right. The cursor is restored back to its position thus should not be impacted.
function Write-Layout ($left, $right) {
    Write-Host $left -NoNewline

    $origX = $Host.UI.RawUI.CursorPosition.X
    $origY = $Host.UI.RawUI.CursorPosition.Y

    $rightX = $Host.UI.RawUI.WindowSize.Width - (Length-Without-ANSI $right)
    $rightY = $Host.UI.RawUI.CursorPosition.Y
    $position = New-Object System.Management.Automation.Host.Coordinates $rightX, $rightY

    $Host.UI.RawUI.CursorPosition = $position
    Write-Host $right -NoNewLine

    $position = New-Object System.Management.Automation.Host.Coordinates $origX, $origY
    $Host.UI.RawUI.CursorPosition = $position
}

It took me quite some time to find out what were the correct objects to use, the documentation was a bit sparse on this. So far I haven't found any problem when using it but I may have overlooked some issues.

ANSI escape code formatting

I didn't want to install external dependencies for something as simple and stable as escape code formatting, I took the full list of colors and styles and created enums for them.

The results in a simple file that can be copy pasted around if in need for other projects: formatting.ps1. It should cover all the escape codes. I also added functions to make it nicer to format text and test styles and colors directly from a shell.

Test-Styles and Test-Colors from formatting.ps1 are functions that can be used to see how ANSI escape codes look in your shell

Display the "main" parent of the current directory

This feature may only make sense in the context of my personal shell as I know exactly what to expect. I documented the function that implements the selection of the "main" directory fairly well, but I can paraphrase it here. I call "current main directory" a contextual parent directory that helps me understand what the current working directory is about, while avoiding displaying the complete path. The idea is to have something as terse as possible that still gives enough context to understand where I am.

As an example, if I am in C:\Users\Sam\Development\neocolor, everything until Development\neocolor is redundant. I spend almost all my time in the Development directory so I know exactly where to find it. In this case, the "main directory" is Development while the current directory is neocolor.

The possible values are:

  • OneDrive\{firstSubdir} if I am deep in a OneDrive subdirectory. e.g: OneDrive\Workspace when the full path is C:\Users\Sam\OneDrive\Workspace\Games\2D Tutorial
  • OneDrive if I am in a subdirectory of the OneDrive location
  • {firstSubdir} if I am deep in a subdirectory branch of the $HOME directory. e.g: Anayconda3 when the full path is C:\Users\Sam\Anaconda3\include\internal
  • Users\Sam if I am in a subdirectory of the $HOME directory
  • what I call the top directories, for example C:\Users when I am in C:\Users\Sam
  • special case for the root level, I only want to display C: in that case
  • and otherwise, just display the full path

That seems to be a lot of rules when I describe them, but in practice I don't memorize them, the prompt displays just enough of the current context to let me know exactly where I am. And if I don't then pwd is never far away.

Current git branch

The simplest way I found is this one:

# Get the current git branch name
function Git-Branch {
    $raw = git status --porcelain --branch
    $branch = $raw.Split(" ").Split("...")[1]
    return $branch
}

No issue found so far.

Check administrator role

I want admin shell to be marked as such in a visual way. If I detect an administrator role (Windows case) or a root user (Linux and macOS cases), I add an Admin :: marker on the left.

# Check if current user is an administrator
function Test-Administrator {
    if ($IsLinux -or $IsMacOS) {
        return ((id -u) -eq 0)
    }
    if ($IsWindows) {
        $user = [Security.Principal.WindowsIdentity]::GetCurrent()
        return ([Security.Principal.WindowsPrincipal] $user).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    return ""
}

Support for both Windows and WSL (Windows Subsystem Linux)

For some reasons it is mostly unknown by Linux users that Powershell is multiplatform and can be used as the main shell (or just a runtime for shell scripts) on Windows, Linux, and macOS. It's a bit of shame given that Powershell is quite capable and has way less ugly edge cases that make shells from the sh family really difficult to learn and use correctly. When checking for platform specific details, such as some specific paths, it's trivial to assert what platform we are on using $IsLinux, $IsWindows, and $IsMacOS.

A bonus feature: a ZenMode!

In addition to the general prompt, I implemented a more experimental way to interact with the shell that breaks some expected user experience and expectations. I call it the zen mode, and it looks like this.

It's a bit of an oddity, I don't think I've ever seen a shell behaving like this. The picture doesn't really communicate the experience of using the zen mode so here is a short video and explanation:

The idea is to have a shell that doesn't display a prompt at all and is as minimalistic as possible. It only shows you the current command and its output, has no other information. Why would you use such a mode? To be honest, I don't know, I was working on my prompt, then realized that I often don't want to have more details than just what I'm doing right now. In practice I haven't found myself using it that much 🤷‍♀️ but I see it as an interesting experiment.