Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PowerShell Prompt
#Requires -Version 7
# Version 1.2.10
# check if newer version
$gistUrl = "https://api.github.com/gists/a208d2bd924691bae7ec7904cab0bd8e"
$latestVersionFile = [System.IO.Path]::Combine("$HOME",'.latest_profile_version')
$versionRegEx = "# Version (?<version>\d+\.\d+\.\d+)"
if ([System.IO.File]::Exists($latestVersionFile)) {
$latestVersion = [System.IO.File]::ReadAllText($latestVersionFile)
$currentProfile = [System.IO.File]::ReadAllText($profile)
[version]$currentVersion = "0.0.0"
if ($currentProfile -match $versionRegEx) {
$currentVersion = $matches.Version
}
if ([version]$latestVersion -gt $currentVersion) {
Write-Verbose "Your version: $currentVersion" -Verbose
Write-Verbose "New version: $latestVersion" -Verbose
$choice = Read-Host -Prompt "Found newer profile, install? (Y)"
if ($choice -eq "Y" -or $choice -eq "") {
try {
$gist = Invoke-RestMethod $gistUrl -ErrorAction Stop
$gistProfile = $gist.Files."profile.ps1".Content
Set-Content -Path $profile -Value $gistProfile
Write-Verbose "Installed newer version of profile" -Verbose
. $profile
return
}
catch {
# we can hit rate limit issue with GitHub since we're using anonymous
Write-Verbose -Verbose "Was not able to access gist, try again next time"
}
}
}
}
$global:profile_initialized = $false
function prompt {
function Initialize-Profile {
$null = Start-ThreadJob -Name "Get version of `$profile from gist" -ArgumentList $gistUrl, $latestVersionFile, $versionRegEx -ScriptBlock {
param ($gistUrl, $latestVersionFile, $versionRegEx)
try {
$gist = Invoke-RestMethod $gistUrl -ErrorAction Stop
$gistProfile = $gist.Files."profile.ps1".Content
[version]$gistVersion = "0.0.0"
if ($gistProfile -match $versionRegEx) {
$gistVersion = $matches.Version
Set-Content -Path $latestVersionFile -Value $gistVersion
}
}
catch {
# we can hit rate limit issue with GitHub since we're using anonymous
Write-Verbose -Verbose "Was not able to access gist to check for newer version"
}
}
if ((Get-Module PSReadLine).Version -lt 2.2) {
throw "Profile requires PSReadLine 2.2+"
}
# setup psdrives
if ([System.IO.File]::Exists([System.IO.Path]::Combine("$HOME",'test'))) {
New-PSDrive -Root ~/test -Name Test -PSProvider FileSystem -ErrorAction Ignore > $Null
}
if (!(Test-Path repos:)) {
if (Test-Path ([System.IO.Path]::Combine("$HOME",'git'))) {
New-PSDrive -Root ~/repos -Name git -PSProvider FileSystem > $Null
}
elseif (Test-Path "d:\PowerShell") {
New-PSDrive -Root D:\ -Name git -PSProvider FileSystem > $Null
}
}
Set-PSReadLineOption -Colors @{ Selection = "`e[92;7m"; InLinePrediction = "`e[36;7;238m" } -PredictionSource History
Set-PSReadLineKeyHandler -Chord Shift+Tab -Function MenuComplete
Set-PSReadLineKeyHandler -Chord Ctrl+b -Function BackwardWord
Set-PSReadLineKeyHandler -Chord Ctrl+f -Function ForwardWord
if ($IsWindows) {
Set-PSReadLineOption -EditMode Emacs -ShowToolTips
Set-PSReadLineKeyHandler -Chord Ctrl+Shift+c -Function Copy
Set-PSReadLineKeyHandler -Chord Ctrl+Shift+v -Function Paste
}
else {
try {
Import-UnixCompleters
}
catch [System.Management.Automation.CommandNotFoundException]
{
Install-Module Microsoft.PowerShell.UnixCompleters -Repository PSGallery -AcceptLicense -Force
Import-UnixCompleters
}
}
# add path to dotnet global tools
$env:PATH += [System.IO.Path]::PathSeparator + [System.IO.Path]::Combine("$HOME",'.dotnet','tools')
# ensure dotnet cli is in path
$dotnet = Get-Command dotnet -CommandType Application -ErrorAction Ignore
if ($null -eq $dotnet) {
if ([System.IO.File]::Exists("$HOME/.dotnet/dotnet")){
$env:PATH += [System.IO.Path]::PathSeparator+ [System.IO.Path]::Combine("$HOME",'.dotnet')
}
}
}
if ($global:profile_initialized -ne $true) {
$global:profile_initialized = $true
Initialize-Profile
}
$currentLastExitCode = $LASTEXITCODE
$lastSuccess = $?
$color = @{
Reset = "`e[0m"
Red = "`e[31;1m"
Green = "`e[32;1m"
Yellow = "`e[33;1m"
Grey = "`e[37;0m"
White = "`e[37;1m"
Invert = "`e[7m"
RedBackground = "`e[41m"
}
# set color of PS based on success of last execution
if ($lastSuccess -eq $false) {
$lastExit = $color.Red
} else {
$lastExit = $color.Green
}
# get the execution time of the last command
$lastCmdTime = ""
$lastCmd = Get-History -Count 1
if ($null -ne $lastCmd) {
$cmdTime = $lastCmd.Duration.TotalMilliseconds
$units = "ms"
$timeColor = $color.Green
if ($cmdTime -gt 250 -and $cmdTime -lt 1000) {
$timeColor = $color.Yellow
} elseif ($cmdTime -ge 1000) {
$timeColor = $color.Red
$units = "s"
$cmdTime = $lastCmd.Duration.TotalSeconds
if ($cmdTime -ge 60) {
$units = "m"
$cmdTIme = $lastCmd.Duration.TotalMinutes
}
}
$lastCmdTime = "$($color.Grey)[$timeColor$($cmdTime.ToString("#.##"))$units$($color.Grey)]$($color.Reset) "
}
# get git branch information if in a git folder or subfolder
$gitBranch = ""
$path = Get-Location
while ($path -ne "") {
if (Test-Path ([System.IO.Path]::Combine($path,'.git'))) {
# need to do this so the stderr doesn't show up in $error
$ErrorActionPreferenceOld = $ErrorActionPreference
$ErrorActionPreference = 'Ignore'
$branch = git rev-parse --abbrev-ref --symbolic-full-name '@{u}'
$ErrorActionPreference = $ErrorActionPreferenceOld
# handle case where branch is local
if ($lastexitcode -ne 0 -or $null -eq $branch) {
$branch = git rev-parse --abbrev-ref HEAD
}
$branchColor = $color.Green
if ($branch -match "/master") {
$branchColor = $color.Red
}
$gitBranch = " $($color.Grey)[$branchColor$branch$($color.Grey)]$($color.Reset)"
break
}
$path = Split-Path -Path $path -Parent
}
# truncate the current location if too long
$currentDirectory = $executionContext.SessionState.Path.CurrentLocation.Path
$consoleWidth = [Console]::WindowWidth
$maxPath = [int]($consoleWidth / 2)
if ($currentDirectory.Length -gt $maxPath) {
$currentDirectory = "`u{2026}" + $currentDirectory.SubString($currentDirectory.Length - $maxPath)
}
# check if running dev built pwsh
$devBuild = ''
if ($PSHOME.Contains("publish")) {
$devBuild = " $($color.White)$($color.RedBackground)DevPwsh$($color.Reset)"
}
"${lastCmdTime}${currentDirectory}${gitBranch}${devBuild}`n${lastExit}PS$($color.Reset)$('>' * ($nestedPromptLevel + 1)) "
# set window title
try {
$prefix = ''
if ($isWindows) {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$windowsPrincipal = [Security.Principal.WindowsPrincipal]::new($identity)
if ($windowsPrincipal.IsInRole("Administrators") -eq 1) {
$prefix = "Admin:"
}
}
$Host.ui.RawUI.WindowTitle = "$prefix$PWD"
} catch {
# do nothing if can't be set
}
$global:LASTEXITCODE = $currentLastExitCode
}
@FrancoisMongeau

This comment has been minimized.

Copy link

@FrancoisMongeau FrancoisMongeau commented Sep 30, 2019

Awsome! Why the new ThreadJob ?

@SteveL-MSFT

This comment has been minimized.

Copy link
Owner Author

@SteveL-MSFT SteveL-MSFT commented Sep 30, 2019

So one of the aspects of my profile is to auto-check if there's a newer version on GitHub. The reason I have that is because I test on macOS, Ubuntu, and Windows regularly and would like the same profile on each. Without this, my profile start time was over 1 sec due to the time it takes to make the rest call to GitHub to see if I'm running the latest version locally. Doing that specific part in a ThreadJob, my profile start up time is now ~500ms. It also means I defer knowing if there is an update to the next time my profile runs, but that's ok.

@Windos

This comment has been minimized.

Copy link

@Windos Windos commented Apr 6, 2021

I think this needs a tweak from:

if ($latestVersion -gt $currentVersion) {

to:

if ([Version] $latestVersion -gt $currentVersion) {

Or cast the $lastestVersion to the Version class when it's declared. Currently it's a string and in my testing is coming with a new line, so it's messing with the comparison.

At present I end up in a loop of "hey there's a newer version, want to update" despite already having it.

Looping update prompts

This continues until I Ctrl + C to get a usable prompt. At which point I can manually check things out a little:

Diging into the variables

@zhengbli

This comment has been minimized.

Copy link

@zhengbli zhengbli commented Apr 8, 2021

For some reason when I tried to use the prompt function, the Initialize-profile function gets called everytime. The $profile_initialized variable wasn't set to True properly after it ran. I added the $global prefix in the if check, which seems to have fixed the issue.

@danstur

This comment has been minimized.

Copy link

@danstur danstur commented Apr 8, 2021

@SteveL-MSFT I'm curious why you decided to use gists and the manual handling for this instead of using using git. That's how I sync my profile (although admittedly I update manually to avoid the performance hit of explicitly checking at startup)

I "borrowed" the idea of setting up custom PSDrives for common folders though. I feel stupid for never making that connection - that sounds like it should be very useful.

@corbob

This comment has been minimized.

Copy link

@corbob corbob commented Apr 8, 2021

why you decided to use gists and the manual handling for this instead of using using git.

@danstur, not trying to speak for Steve here, but I had the same thought. I'm thinking the reasoning is you don't need to install git to do it this way. That being said, being on a public GitHub project would allow the same thing... Would also allow extra niceties like a license file, and perhaps the version check is pulling down a small file with the version instead of the whole script 🤷‍♂️

@BigHoss

This comment has been minimized.

Copy link

@BigHoss BigHoss commented Sep 9, 2021

@SteveL-MSFT i try to implement powerline with the following line

Import-Module posh-git
Import-Module oh-my-posh

Set-PoshPrompt -Theme paradox

but i cant seem to find out how or where to correctly implement it with this script

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment