A Git-Aware Prompt for PowerShell
PowerShell Coreβs default prompt exposes the current path but no repository or
privilege metadata. The following prompt function adds the current logon
server, elevation state, Git branch, working-tree status, index status, and
stash count without requiring a separate prompt framework.
Prompt Layout
The prompt consists of three colour-coded segments.
Logon server β Reads $env:LOGONSERVER, normalises its casing, and renders
the result on a grey background followed by the πΈ glyph. This identifies the
authentication server associated with the current session.
$LogonServer = ConvertTo-TitleCase -InputString $env:LOGONSERVER
Write-Host -NoNewline "${LogonServer}[`u{1D6B8}] " -BackgroundColor Gray -ForegroundColor Black
Working directory and elevation state β Resolves the current Windows principal and checks membership in the built-in Administrators role. The working directory uses a blue background for a standard session and a dark-red background for an elevated session.
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
$location = (Get-Location).Path
if ($isAdmin) {
Write-Host -NoNewline "$($location) " -ForegroundColor Gray -BackgroundColor DarkRed
} else {
Write-Host -NoNewline "$($location) " -BackgroundColor Blue -ForegroundColor Black
}
Git status β When git is available and git branch --show-current
returns a branch name, the third segment contains:
- The current branch, prefixed with π©
- A dark-green background for a clean repository or dark yellow when changes are present
- Separate indicators for untracked (π°), unstaged (π³), and staged (π±) changes
- The stash count, prefixed with πΈ, when at least one stash exists
The segment is omitted outside a repository. It is also omitted in detached
HEAD state because git branch --show-current returns an empty string in that
case.
Status values are mapped to colours and indicators as follows:
$isDirty = $hasUnstagedChanges -or $hasStagedChanges -or $hasUntrackedFiles
$bgColor = if ($isDirty) { "DarkYellow" } else { "DarkGreen" }
$stashCount = (git stash list 2>$null).Count
$stashInfo = if ($stashCount -gt 0) { " `u{1D6B8}:$stashCount " } else { "" }
$unstagedIndicator = if ($hasUnstagedChanges) { "`u{1D6B3} " } else { "" }
$stagedIndicator = if ($hasStagedChanges) { "`u{1D6B1} " } else { "" }
$untrackedIndicator = if ($hasUntrackedFiles) { "`u{1D6B0}" } else { "" }
Write-Host -NoNewline "`u{1D6A9}:" -ForegroundColor Black -BackgroundColor $bgColor
Write-Host -NoNewline "$gitBranch" -ForegroundColor Black -BackgroundColor $bgColor
# ... stash count and status indicators follow
Write-Host -NoNewline "$untrackedIndicator$unstagedIndicator$stagedIndicator" `
-ForegroundColor Black -BackgroundColor $bgColor
Implementation Scope
Oh My Posh and Starship provide richer prompt engines, configuration formats,
and theme ecosystems. This implementation targets a narrower requirement: one
PowerShell function stored in $PROFILE, with no prompt binary, theme file, or
Nerd Font dependency.
The function invokes git branch --show-current, git status --porcelain, and
git stash list directly. Porcelain status output is intended for script
consumption: unlike the default git status presentation, its format does not
depend on localisation or user-facing layout changes.
Parsing Porcelain Status
Each git status --porcelain record starts with a two-character XY status
field:
Xdescribes the index stateYdescribes the working-tree state??identifies an untracked path
The parser performs one pass over the records and maintains three Boolean flags. It does not retain paths or count records because rendering depends only on whether each category is present. The parsing step therefore uses constant additional state; repository scanning and status generation remain the dominant costs.
$gitStatusOutput = git status --porcelain 2>$null
$hasUnstagedChanges = $false
$hasStagedChanges = $false
$hasUntrackedFiles = $false
if ($gitStatusOutput) {
foreach ($line in $gitStatusOutput) {
if ($line.StartsWith('??')) {
$hasUntrackedFiles = $true
continue
}
# Second character: work tree (unstaged) status
if ($line.Length -gt 1 -and $line[1] -ne ' ') {
$hasUnstagedChanges = $true
}
# First character: index (staged) status
if ($line.Length -gt 0 -and $line[0] -ne ' ' -and $line[0] -ne '?') {
$hasStagedChanges = $true
}
}
}
Normalising the Server Name
ConvertTo-TitleCase formats the value of LOGONSERVER, which commonly
contains leading backslashes and an uppercase host name. The function preserves
any leading non-letter characters, uppercases the first letter, and lowercases
the remaining substring. Empty input and strings without letters are returned
unchanged.
function ConvertTo-TitleCase {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
if ([string]::IsNullOrEmpty($InputString)) { return $InputString }
for ($i = 0; $i -lt $InputString.Length; $i++) {
if ([char]::IsLetter($InputString[$i])) {
return $InputString.Substring(0, $i) +
$InputString[$i].ToString().ToUpper() +
$InputString.Substring($i + 1).ToLower()
}
}
return $InputString
}
Runtime Characteristics
The visual encoding provides four independent state signals:
- Presence of the Git segment indicates a named branch in a repository
- Green or yellow identifies a clean or modified repository
- π°, π³, and π± identify untracked, unstaged, and staged content
- A dark-red path segment identifies an elevated PowerShell process
The function starts up to three Git processes whenever PowerShell renders the prompt inside a repository. That cost is usually small, but it can become noticeable in large repositories, on slow file systems, or when Git status requires expensive work-tree traversal.
To install the prompt, add both functions to $PROFILE (code $PROFILE) and
use a Unicode-capable terminal. Windows Terminal supports the Mathematical Bold
Capital glyphs used here without requiring a Nerd Font.
Complete Source
function ConvertTo-TitleCase {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
if ([string]::IsNullOrEmpty($InputString)) {
return $InputString
}
# Find the first letter and capitalize it
for ($i = 0; $i -lt $InputString.Length; $i++) {
if ([char]::IsLetter($InputString[$i])) {
return $InputString.Substring(0, $i) + $InputString[$i].ToString().ToUpper() + $InputString.Substring($i + 1).ToLower()
}
}
# If no letter found, return original string
return $InputString
}
# Enhanced git prompt for PowerShell Core
function prompt {
# Show logon server with a PowerShell icon and convert to title case
$LogonServer = ConvertTo-TitleCase -InputString $env:LOGONSERVER
Write-Host -NoNewline "${LogonServer}[`u{1D6B8}] " -BackgroundColor Gray -ForegroundColor Black
# Check if running as admin
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
# Start with the location part of the prompt
$location = (Get-Location).Path
if ($isAdmin) {
Write-Host -NoNewline "$($location) " -ForegroundColor Gray -BackgroundColor DarkRed
}
else {
Write-Host -NoNewline "$($location) " -BackgroundColor Blue -ForegroundColor Black
}
# Get Git branch and status if in a git repository
if (Get-Command git -ErrorAction SilentlyContinue) {
try {
$gitBranch = git branch --show-current 2>$null
if ($gitBranch) {
# Get all status output
$gitStatusOutput = git status --porcelain 2>$null
# Initialize status flags
$hasUnstagedChanges = $false
$hasStagedChanges = $false
$hasUntrackedFiles = $false
if ($gitStatusOutput) {
foreach ($line in $gitStatusOutput) {
# Check for untracked files (indicated by '??')
if ($line.StartsWith('??')) {
$hasUntrackedFiles = $true
continue
}
# Check if the second character is not a space (indicating unstaged change)
if ($line.Length -gt 1 -and $line[1] -ne ' ') {
$hasUnstagedChanges = $true
}
# Check if the first character is not a space (indicating staged change)
if ($line.Length -gt 0 -and $line[0] -ne ' ' -and $line[0] -ne '?') {
$hasStagedChanges = $true
}
}
}
# Set background color based on whether there are any changes
$isDirty = $hasUnstagedChanges -or $hasStagedChanges -or $hasUntrackedFiles
$bgColor = if ($isDirty) {
"DarkYellow"
}
else {
"DarkGreen"
}
# Check for stashes
$stashCount = (git stash list 2>$null).Count
$stashInfo = if ($stashCount -gt 0) {
" `u{1D6B8}:$stashCount "
}
else {
""
}
$unstagedIndicator = if ($hasUnstagedChanges) {
"`u{1D6B3} "
}
else {
""
}
$stagedIndicator = if ($hasStagedChanges) {
"`u{1D6B1} "
}
else {
""
}
$untrackedIndicator = if ($hasUntrackedFiles) {
"`u{1D6B0}"
}
else {
""
}
# Write opening angle bracket with background color
Write-Host -NoNewline "`u{1D6A9}:" -ForegroundColor Black -BackgroundColor $bgColor
# Write branch name
Write-Host -NoNewline "$gitBranch" -ForegroundColor Black -BackgroundColor $bgColor
# Add stash count if there are stashes
if ($stashCount -gt 0) {
Write-Host -NoNewline $stashInfo -ForegroundColor Black -BackgroundColor $bgColor
}
elseif ($isDirty) {
# Write a space if there are no stashes
Write-Host -NoNewline " " -ForegroundColor Black -BackgroundColor $bgColor
}
# Add status indicators
Write-Host -NoNewline "$untrackedIndicator$unstagedIndicator$stagedIndicator" -ForegroundColor Black -BackgroundColor $bgColor
# Determine if we need a separator
$hasAnyIndicators = $stashCount -gt 0 -or $hasUnstagedChanges -or $hasStagedChanges -or $hasUntrackedFiles
if ($hasAnyIndicators -eq $false) {
Write-Host -NoNewline " " -ForegroundColor Black -BackgroundColor $bgColor
}
}
}
catch {
# Not in a git repository or git command failed
}
}
# Close the bracket
if ($isAdmin) {
Write-Host -NoNewline ">"
}
else {
Write-Host -NoNewline ">"
}
# Return a space for the actual prompt
return " "
}