Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PowerShell Prompt
#Requires -Version 7
# Version 1.2.9
# 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"
}
}
}
}
$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')
}
}
$profile_initialized = $true
}
if (!$profile_initialized) {
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 🤷‍♂️

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