Last active
March 21, 2024 07:24
-
-
Save bradwilson/d965fa17fd3b3eee168270122e0f7da4 to your computer and use it in GitHub Desktop.
Downloadable files for bradwilson.io/blog/prompt/powershell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######## POSH-GIT | |
# ... Import-Module for posh-git here ... | |
# Background colors | |
$GitPromptSettings.AfterStash.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.AfterStatus.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BeforeIndex.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BeforeStash.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BeforeStatus.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BranchAheadStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BranchBehindAndAheadStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BranchBehindStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BranchColor.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BranchGoneStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.BranchIdenticalStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.DefaultColor.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.DelimStatus.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.ErrorColor.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.IndexColor.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.LocalDefaultStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.LocalStagedStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.LocalWorkingStatusSymbol.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.StashColor.BackgroundColor = 0x5F5FAF | |
$GitPromptSettings.WorkingColor.BackgroundColor = 0x5F5FAF | |
# Foreground colors | |
$GitPromptSettings.AfterStash.ForegroundColor = 0xF49797 | |
$GitPromptSettings.AfterStatus.ForegroundColor = 0x729FCF | |
$GitPromptSettings.BeforeStash.ForegroundColor = 0xF49797 | |
$GitPromptSettings.BeforeStatus.ForegroundColor = 0x729FCF | |
$GitPromptSettings.BranchAheadStatusSymbol.ForegroundColor = 0x8AE234 | |
$GitPromptSettings.BranchBehindAndAheadStatusSymbol.ForegroundColor = 0xFCE94F | |
$GitPromptSettings.BranchBehindStatusSymbol.ForegroundColor = 0xF49797 | |
$GitPromptSettings.BranchColor.ForegroundColor = 0xFBFBFB | |
$GitPromptSettings.BranchGoneStatusSymbol.ForegroundColor = 0x729FCF | |
$GitPromptSettings.BranchIdenticalStatusSymbol.ForegroundColor = 0x729FCF | |
$GitPromptSettings.DefaultColor.ForegroundColor = 0xB5BBAE | |
$GitPromptSettings.DelimStatus.ForegroundColor = 0x729FCF | |
$GitPromptSettings.ErrorColor.ForegroundColor = 0xF49797 | |
$GitPromptSettings.IndexColor.ForegroundColor = 0x2EC3C3 | |
$GitPromptSettings.StashColor.ForegroundColor = 0xF49797 | |
$GitPromptSettings.WorkingColor.ForegroundColor = 0xFCE94F | |
# Prompt shape | |
$GitPromptSettings.AfterStatus.Text = " " | |
$GitPromptSettings.BeforeStatus.Text = " " | |
$GitPromptSettings.BranchAheadStatusSymbol.Text = "" | |
$GitPromptSettings.BranchBehindStatusSymbol.Text = "" | |
$GitPromptSettings.BranchGoneStatusSymbol.Text = "" | |
$GitPromptSettings.BranchBehindAndAheadStatusSymbol.Text = "" | |
$GitPromptSettings.BranchIdenticalStatusSymbol.Text = "" | |
$GitPromptSettings.BranchUntrackedText = "" | |
$GitPromptSettings.DelimStatus.Text = " ॥" | |
$GitPromptSettings.LocalStagedStatusSymbol.Text = "" | |
$GitPromptSettings.LocalWorkingStatusSymbol.Text = "" | |
$GitPromptSettings.PathStatusSeparator = "" | |
$GitPromptSettings.EnableStashStatus = $false | |
$GitPromptSettings.ShowStatusWhenZero = $false | |
######## INI FILE PARSER | |
function parseIniFile { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position = 0)] | |
[String] $Inputfile | |
) | |
if ($Inputfile -eq "") { | |
Write-Error "Ini File Parser: No file specified or selected to parse." | |
Break | |
} | |
else { | |
$ContentFile = Get-Content $Inputfile | |
# commented Section | |
$COMMENT_CHARACTERS = ";" | |
# match section header | |
$HEADER_REGEX = "\[+[A-Z0-9._ %<>/#+-]+\]" | |
$OccurenceOfComment = 0 | |
$ContentComment = $ContentFile | Where-Object { ($_ -match "^\s*$COMMENT_CHARACTERS") -or ($_ -match "^$COMMENT_CHARACTERS") } | ForEach-Object { | |
[PSCustomObject]@{ Comment = $_ ; | |
Index = [Array]::IndexOf($ContentFile, $_) | |
} | |
$OccurenceOfComment++ | |
} | |
$COMMENT_INI = @() | |
foreach ($COMMENT_ELEMENT in $ContentComment) { | |
$COMMENT_OBJ = New-Object PSObject | |
$COMMENT_OBJ | Add-Member -type NoteProperty -name Index -value $COMMENT_ELEMENT.Index | |
$COMMENT_OBJ | Add-Member -type NoteProperty -name Comment -value $COMMENT_ELEMENT.Comment | |
$COMMENT_INI += $COMMENT_OBJ | |
} | |
$CONTENT_USEFUL = $ContentFile | Where-Object { ($_ -notmatch "^\s*$COMMENT_CHARACTERS") -or ($_ -notmatch "^$COMMENT_CHARACTERS") } | |
$ALL_SECTION_HASHTABLE = $CONTENT_USEFUL | Where-Object { $_ -match $HEADER_REGEX } | ForEach-Object { [PSCustomObject]@{ Section = $_ ; Index = [Array]::IndexOf($CONTENT_USEFUL, $_) } } | |
#$ContentUncomment | Select-String -AllMatches $HEADER_REGEX | Select-Object -ExpandProperty Matches | |
$SECTION_INI = @() | |
foreach ($SECTION_ELEMENT in $ALL_SECTION_HASHTABLE) { | |
$SECTION_OBJ = New-Object PSObject | |
$SECTION_OBJ | Add-Member -type NoteProperty -name Index -value $SECTION_ELEMENT.Index | |
$SECTION_OBJ | Add-Member -type NoteProperty -name Section -value $SECTION_ELEMENT.Section | |
$SECTION_INI += $SECTION_OBJ | |
} | |
$INI_FILE_CONTENT = @() | |
$NBR_OF_SECTION = $SECTION_INI.count | |
$NBR_MAX_LINE = $CONTENT_USEFUL.count | |
#********************************************* | |
# select each lines and value of each section | |
#********************************************* | |
for ($i = 1; $i -le $NBR_OF_SECTION ; $i++) { | |
if ($i -ne $NBR_OF_SECTION) { | |
if (($SECTION_INI[$i - 1].Index + 1) -eq ($SECTION_INI[$i].Index )) { | |
$CONVERTED_OBJ = @() #There is nothing between the two section | |
} | |
else { | |
$SECTION_STRING = $CONTENT_USEFUL | Select-Object -Index (($SECTION_INI[$i - 1].Index + 1)..($SECTION_INI[$i].Index - 1)) | Out-String | |
$CONVERTED_OBJ = convertfrom-stringdata -stringdata $SECTION_STRING | |
} | |
} | |
else { | |
if (($SECTION_INI[$i - 1].Index + 1) -eq $NBR_MAX_LINE) { | |
$CONVERTED_OBJ = @() #There is nothing between the two section | |
} | |
else { | |
$SECTION_STRING = $CONTENT_USEFUL | Select-Object -Index (($SECTION_INI[$i - 1].Index + 1)..($NBR_MAX_LINE - 1)) | Out-String | |
$CONVERTED_OBJ = convertfrom-stringdata -stringdata $SECTION_STRING | |
} | |
} | |
$CURRENT_SECTION = New-Object PSObject | |
$CURRENT_SECTION | Add-Member -Type NoteProperty -Name Section -Value $SECTION_INI[$i - 1].Section | |
$CURRENT_SECTION | Add-Member -Type NoteProperty -Name Content -Value $CONVERTED_OBJ | |
$INI_FILE_CONTENT += $CURRENT_SECTION | |
} | |
return $INI_FILE_CONTENT | |
} | |
} | |
######## FIND A FILE/PATTERN HERE OR IN ANY PARENT FOLDER | |
function findHereOrParent { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true)] | |
[string[]] $filePaths | |
) | |
try { | |
$currentFolder = Get-Location | |
while ($currentFolder -ne "") { | |
foreach ($filePath in $filePaths) { | |
if ((Get-ChildItem -ErrorAction Ignore -LiteralPath $currentFolder -Filter $filePath).Count -gt 0) { | |
return $true | |
} | |
} | |
$currentFolder = Split-Path $currentFolder | |
} | |
} | |
catch { | |
$Error.Clear() | |
} | |
return $false | |
} | |
######## PROMPT | |
set-content Function:prompt { | |
# Start with a blank line, for breathing room :) | |
Write-Host "" | |
# Reset the foreground color to default | |
$Host.UI.RawUI.ForegroundColor = "Gray" | |
Write-Host " " -NoNewLine | |
# Write ERR for any PowerShell errors | |
if ($Error.Count -ne 0) { | |
Write-Host "$([char]27)[38;5;131;40m$([char]27)[38;5;227;48;5;131m ERR $([char]27)[38;5;131;40m" -NoNewLine | |
$Error.Clear() | |
} | |
# Write Ctrl+C separately | |
if ($LASTEXITCODE -eq -1073741510) { | |
Write-Host "$([char]27)[38;5;131;40m$([char]27)[38;5;227;48;5;131m Ctrl-C $([char]27)[38;5;131;40m" -NoNewLine | |
$LASTEXITCODE = "" | |
} | |
# Write non-zero exit code from last launched process | |
if ($LASTEXITCODE -ne "") { | |
Write-Host "$([char]27)[38;5;131;40m$([char]27)[38;5;227;48;5;131m $LASTEXITCODE $([char]27)[38;5;131;40m" -NoNewLine | |
$LASTEXITCODE = "" | |
} | |
# Write any custom prompt environment (f.e., from vs2017.ps1) | |
if (get-content variable:\PromptEnvironment -ErrorAction Ignore) { | |
Write-Host "$([char]27)[38;5;183;40m$([char]27)[38;5;54;48;5;183m$PromptEnvironment$([char]27)[38;5;183;40m" -NoNewLine | |
} | |
# Write Go version | |
if (($null -ne (Get-Command "go" -ErrorAction Ignore)) -and (findHereOrParent go.mod)) { | |
$goVersion = (& go version).split()[2].substring(2) | |
Write-Host "$([char]27)[38;5;80;48;5;0m$([char]27)[38;5;16;48;5;80m $goVersion $([char]27)[38;5;80;40m" -NoNewLine | |
} | |
# Write Rust version | |
if (($null -ne (Get-Command "rustc" -ErrorAction Ignore)) -and (findHereOrParent Cargo.toml)) { | |
$rustVersion = (& rustc --version).Split()[1] | |
Write-Host "$([char]27)[38;5;172;40m$([char]27)[38;5;231;48;5;172m $rustVersion $([char]27)[38;5;172;40m" -NoNewLine | |
} | |
# Write .NET SDK version | |
if (($null -ne (Get-Command "dotnet" -ErrorAction Ignore)) -and (findHereOrParent *.sln,*.csproj)) { | |
$dotNetVersion = (& dotnet --version) | |
Write-Host "$([char]27)[38;5;54;40m$([char]27)[38;5;254;48;5;54m $dotNetVersion $([char]27)[38;5;54;40m" -NoNewLine | |
} | |
# Write the current kubectl context | |
if ($null -ne (Get-Command "kubectl" -ErrorAction Ignore)) { | |
$currentContext = (& kubectl config current-context 2> $null) | |
if (($Error.Count -eq 0) -and ($currentContext -ne $null)) { | |
Write-Host "$([char]27)[38;5;242;40m$([char]27)[38;5;112;48;5;242m $([char]27)[38;5;254m$currentContext $([char]27)[38;5;242;40m" -NoNewLine | |
} | |
else { | |
$Error.Clear() | |
} | |
} | |
# Write the current public cloud Azure CLI subscription | |
# NOTE: You will need sed from somewhere (for example, from Git for Windows) | |
if (Test-Path ~/.azure/clouds.config) { | |
$cloudsConfig = parseIniFile ~/.azure/clouds.config | |
$azureCloud = $cloudsConfig | Where-Object { $_.Section -eq "[AzureCloud]" } | |
if ($null -ne $azureCloud) { | |
$currentSub = $azureCloud.Content.subscription | |
if ($null -ne $currentSub) { | |
$currentAccount = (Get-Content ~/.azure/azureProfile.json | ConvertFrom-Json).subscriptions | Where-Object { $_.id -eq $currentSub } | |
if ($null -ne $currentAccount) { | |
Write-Host "$([char]27)[38;5;30;40m$([char]27)[38;5;227;48;5;30m $([char]27)[38;5;254m$($currentAccount.name) $([char]27)[38;5;30;40m" -NoNewLine | |
} | |
} | |
} | |
} | |
# Write the current Git information | |
if ($null -ne (Get-Command "Get-GitDirectory" -ErrorAction Ignore)) { | |
if (Get-GitDirectory -ne $null) { | |
Write-Host "$([char]27)[38;5;61;40m$(Write-VcsStatus)$([char]27)[38;5;61;40m" -NoNewLine | |
} | |
} | |
# Write the current directory, with home folder normalized to ~ | |
$currentPath = (get-location).Path.replace($home, "~") | |
$idx = $currentPath.IndexOf("::") | |
if ($idx -gt -1) { $currentPath = $currentPath.Substring($idx + 2) } | |
$host.UI.RawUI.WindowTitle = $currentPath | |
Write-Host "$([char]27)[38;5;28;40m$([char]27)[38;5;227;48;5;28m $([char]27)[38;5;254m$currentPath " -NoNewline | |
# Reset LASTEXITCODE so we don't show it over and over again | |
$global:LASTEXITCODE = 0 | |
# Write one + for each level of the pushd stack | |
if ((get-location -stack).Count -gt 0) { | |
Write-Host ("$([char]27)[38;5;227;48;5;28m" + ("«" * ((get-location -stack).Count)) + " ") -NoNewLine | |
} | |
# Newline | |
Write-Host "$([char]27)[38;5;28;40m$([char]27)[30;40m $([char]27)[0m" | |
# Determine if the user is admin, so we color the prompt green or red | |
$isAdmin = $false | |
$isDesktop = ($PSVersionTable.PSEdition -eq "Desktop") | |
if ($isDesktop -or $IsWindows) { | |
$windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() | |
$windowsPrincipal = new-object 'System.Security.Principal.WindowsPrincipal' $windowsIdentity | |
$isAdmin = $windowsPrincipal.IsInRole("Administrators") -eq 1 | |
} | |
else { | |
$isAdmin = ((& id -u) -eq 0) | |
} | |
if ($isAdmin) { $color = "Red"; } | |
else { $color = "Green"; } | |
# Write PS> for desktop PowerShell, pwsh> for PowerShell Core | |
if ($isDesktop) { | |
Write-Host " PS>" -NoNewLine -ForegroundColor $color | |
} | |
else { | |
Write-Host " pwsh>" -NoNewLine -ForegroundColor $color | |
} | |
# Always have to return something or else we get the default prompt | |
return " " | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param( | |
[string]$edition, | |
[string]$version = "2022", | |
[string]$basePath = "", | |
[switch]$noWeb = $false | |
) | |
function append-path { | |
param( | |
[string[]][Parameter(Mandatory = $true)]$pathsToAdd | |
) | |
$local:separator = ";" | |
if (($PSVersionTable.PSVersion.Major -gt 5) -and ($PSVersionTable.Platform -eq "Unix")) { | |
$local:separator = ":" | |
} | |
$env:PATH = $env:PATH + $local:separator + ($pathsToAdd -join $local:separator) | |
} | |
# Only support one prompt environment at a time | |
if ($PromptEnvironment -ne $null) { | |
write-host "error: Prompt is already in a custom environment." -ForegroundColor Red | |
exit 1 | |
} | |
# Look in the standard installation locations unless they override | |
if ($basePath -eq "") { | |
$basePath = join-path (join-path ${env:ProgramFiles(x86)} "Microsoft Visual Studio") $version | |
if ((test-path $basePath) -eq $false) { | |
$basePath = join-path (join-path ${env:ProgramFiles} "Microsoft Visual Studio") $version | |
} | |
} | |
if ((test-path $basePath) -eq $false) { | |
write-warning "Visual Studio $version is not installed in '$basePath'." | |
exit 1 | |
} | |
# If edition wasn't specified, see what's there, and bail out if there is more than 1 | |
if ($edition -eq "") { | |
$editions = (get-childitem $basePath | where-object { $_.PSIsContainer }) | |
if ($editions.Count -eq 0) { | |
write-warning "Visual Studio $version is not installed." | |
exit 1 | |
} | |
if ($editions.Count -gt 1) { | |
write-warning "Multiple editions of Visual Studio $version are installed. Please specify one of the editions ($($($editions | Foreach-Object { "'" + $_.Name + "'" }) -join ', ')) with the -edition switch." | |
exit 1 | |
} | |
$edition = $editions[0].Name | |
} | |
# Find VsDevCmd.bat | |
$path = join-path (join-path (join-path $basePath $edition) "Common7") "Tools" | |
if ((test-path $path) -eq $false) { | |
write-warning "Visual Studio $version edition '$edition' could not be found." | |
exit 1 | |
} | |
$cmdPath = join-path $path "VsDevCmd.bat" | |
if ((test-path $cmdPath) -eq $false) { | |
write-warning "File not found: $cmdPath" | |
exit 1 | |
} | |
# Run VsDevCmd.bat and then dump all environment variables, so we can | |
# overwrite ours with theirs | |
$tempFile = [IO.Path]::GetTempFileName() | |
cmd /c " `"$cmdPath`" && set > `"$tempFile`" " | |
Get-Content $tempFile | %{ | |
if ($_ -match "^(.*?)=(.*)$") { | |
Set-Content "env:\$($matches[1])" $matches[2] | |
} | |
} | |
# Optionally add the external web tools unless skipped | |
if ($noWeb -eq $false) { | |
$path = join-path (join-path (join-path $basePath $edition) "Web") "External" | |
if (test-path $path) { | |
append-path $path | |
} else { | |
write-warning "Path $path not found; specify -noWeb to skip searching for web tools" | |
} | |
} | |
# Set the prompt environment variable (printed in our prompt function) | |
$global:PromptEnvironment = " $version " |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param( | |
[string]$edition, | |
[string]$basePath = "", | |
[switch]$noWeb = $false | |
) | |
$folder = Split-Path $PSCommandPath | |
$script = Join-Path $folder "vs.ps1" | |
& $script -edition:$edition -version:"2017" -basePath:$basePath -noWeb:$noWeb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param( | |
[string]$edition, | |
[string]$basePath = "", | |
[switch]$noWeb = $false | |
) | |
$folder = Split-Path $PSCommandPath | |
$script = Join-Path $folder "vs.ps1" | |
& $script -edition:$edition -version:"2019" -basePath:$basePath -noWeb:$noWeb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param( | |
[string]$edition, | |
[string]$basePath = "", | |
[switch]$noWeb = $false | |
) | |
$folder = Split-Path $PSCommandPath | |
$script = Join-Path $folder "vs.ps1" | |
& $script -edition:$edition -version:"2022" -basePath:$basePath -noWeb:$noWeb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param( | |
[string]$edition, | |
[string]$basePath = "", | |
[switch]$noWeb = $false | |
) | |
$folder = Split-Path $PSCommandPath | |
$script = Join-Path $folder "vs.ps1" | |
& $script -edition:$edition -version:"Preview" -basePath:$basePath -noWeb:$noWeb |
Thanks for all these suggestions!
Very nice! I had to add a small tweak to line 51 of vs.ps1
from $edition = $editions[0]
to $edition = $editions[0].Name
. From some contexts the value was the full path so it'd end up with C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\Tools\C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\Tools
and bail as the path would't exist.
# If edition wasn't specified, see what's there, and bail out if there is more than 1
if ($edition -eq "") {
$editions = (get-childitem $basePath | where-object { $_.PSIsContainer })
if ($editions.Count -eq 0) {
write-warning "Visual Studio $version is not installed."
exit 1
}
if ($editions.Count -gt 1) {
write-warning "Multiple editions of Visual Studio $version are installed. Please specify one of the editions ($($editions -join ', ')) with the -edition switch."
exit 1
}
$edition = $editions[0].Name
}
Thanks @thnk2wn, I also ran cross this recently, and I've updated the scripts!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Potential enhancement for showing the current dotnet SDK version:
Also, I found I could fairly consistently shave about 40 - 100ms off the rendering of the prompt by running the external commands (
kubectl
,sed
,dotnet
, etc.) in parallel using PowerShell runspaces. It makes the script more complicated since you have to separate the retrieval of the data from the rendering of the data, but the prompt feels a little faster. Like:I'm not using ConEmu so my glyphs aren't Nerd Font but you get the idea. I also can't guarantee I'm 100% handling all error conditions there so YMMV.