Skip to content

Instantly share code, notes, and snippets.

@jeanfrancoislarente
Last active February 11, 2022 00:38
Show Gist options
  • Save jeanfrancoislarente/d31af4c7f8095896eb9c39af3e7de7d4 to your computer and use it in GitHub Desktop.
Save jeanfrancoislarente/d31af4c7f8095896eb9c39af3e7de7d4 to your computer and use it in GitHub Desktop.
Custom powershell profiles with aliases directory navigation

Powerful PowerShell Profile

Bart Plasmeijer had a bit of a stint on my team a while back and brought with him a custom PS profile which brought much convenience and efficiency to the entire team.

Our team adopted this profile, commited the .ps1 to a git repo and then hooked up our machines to read from the same profile.

We've since added aliases and wrapper functions to a lot of complex onboarding tasks. With a simple command we can set environment variables, spin up our CLI, execute common git and docker commands... and so much more.

To leverage the profile, take a look at Microsoft.PowerShell_profile.ps1.sample which you can rename to .ps1 and put in the Documents\WindowsPowerShell folder. When you start PowerShell, the profile will look for the ps-profile.ps1 and invoke it to load the functions and aliases.

As a bonus, you can take a look at the psexit.ps1.example file to see how you can do cross-machine command history by syncing the file to a cloud folder. This is totally optional and to be frank, I've never really made much use of it.

A few examples (which seem benign but imagine multiple times daily):

  • p is an alias to Set-Location c:\projects
  • dcb is an alias to docker-compose build. So you can use dcb <servicename>
  • dcbp is an alias to docker-compose build -m 16G --parallel - now THAT is a finger full to type repeatedly

We also have functions that are extremely useful. What if you had to add secrets to a keyvault on a regular basis.

The following function saves a lot of typing (or searching the web for the syntax, if you're not doing it every day)

function Add-SecretToKeyVault {
  [CmdletBinding()]
  param(
    [string]$SecretName,
    [string]$Value,
    [string]$KeyVault = "default-your-kv-name",
    [string]$Subscription = "default-your-subscription-name"
  )
  az keyvault secret set --name $SecretName --value $Value --vault-name $KeyVault --subscription $Subscription
}

So, in this gist you'll find a slimmed down and sanitized version of the ps-profile.ps1 we use internally. Get inspired and make it your own, enjoy!

$sharedProfile = [string]::join([environment]::newline, (get-content -path "<path-to>\profile.ps1"))
Invoke-Expression $sharedProfile
$saveHistoryScript = $sharedProfile = [string]::join([environment]::newline, (get-content -path "C:\<path-to>\ps-profile\psexit.ps1"))
Invoke-Expression $saveHistoryScript
function Get-BranchName {
# Used to display the current branch name (if applicable) on the prompt.
$currentPath = Get-Location
while ($true) {
try {
if ([string]::IsNullOrEmpty($currentPath)) {
return
}
$git = (Join-Path $currentPath "\.git")
if (Test-Path $git) {
$head = (Get-Content (Join-Path $git "\HEAD") | Out-String).Trim().Replace("ref: refs/heads/", "")
if ([string]::IsNullOrEmpty($head)) {
return
}
return $head
}
$currentPath = Split-Path $currentPath
}
catch {
throw "GetBranchName failed..."
return
}
}
}
function Remove-AllDockerContainers {
docker rm $(docker ps -aq) -f
}
function Set-DemoTeamAzureSubscription {
$account = $(az account show) | ConvertFrom-Json
if (!$account) {
Write-Host "Please login using 'az login' first"
Exit 1
}
$subscription = "SET-PRIMARY-AZURE-SUBSCRIPTION-NAME"
if ($account.name -ne $subscription) {
az account set --subscription $subscription
}
}
function Invoke-GitClean {
git clean -Xdf
}
function Set-LocationProjects { Set-Location -Path C:\Projects }
function Set-LocationSpecificProject { Set-Location -Path C:\Projects\SpecificProjectPath }
function Get-DemoAliases { get-alias | Where-Object { $_.Description -eq "Demo" } }
function Start-DockerComposeParallelBuild { docker-compose build -m 16G --parallel --pull }
function Start-DockerComposeBuild { docker-compose build -m 16G --pull }
function Add-SecretToKeyVault {
[CmdletBinding()]
param(
[string]$SecretName,
[string]$Value,
[string]$KeyVault = "kv-name",
[string]$Subscription = "Azure subscription"
)
az keyvault secret set --name $SecretName --value $Value --vault-name $KeyVault --subscription $Subscription
}
function Set-DemoSPNEnvironmentVariables {
# useful on a shared team, to set local environment variables for each developer when onboarding
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ClientId,
[Parameter(Mandatory = $true)]
[string]$ClientSecret,
[parameter(Mandatory = $false)]
[string]$DefaultSubscription = "your-subscription",
[parameter(Mandatory = $false)]
[string]$DEMO_TEAM_TENANT = "AAD tenant"
)
[System.Environment]::SetEnvironmentVariable('DEMO_TEAM_CLIENTID', $ClientId, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('DEMO_TEAM_CLIENTSECRET', $ClientSecret, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('DEMO_TEAM_TENANT', $env:DEMO_TEAM_TENANT, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('DEMO_TEAM_SUBSCRIPTION', $DefaultSubscription, [System.EnvironmentVariableTarget]::User)
}
function Set-DemoDNSEnvironmentVariables {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ClientId,
[Parameter(Mandatory = $true)]
[string]$ClientSecret,
[Parameter(Mandatory = $true)]
[string]$Subscription,
[parameter(Mandatory = $false)]
[string]$DEMO_TEAM_TENANT = "AAD tenant"
)
[System.Environment]::SetEnvironmentVariable('DNS_CLIENTID', $ClientId, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('DNS_CLIENTSECRET', $ClientSecret, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('DNS_SUBSCRIPTION', $Subscription, [System.EnvironmentVariableTarget]::User)
[System.Environment]::SetEnvironmentVariable('DNS_TENANT', $DEMO_TEAM_TENANT, [System.EnvironmentVariableTarget]::User)
}
function Set-DemoEnvironmentVariables {
# You can default any sort of team environment variables here... Our are removed, for obvious reasons.
}
# Aliases
Set-Alias -Name p -Value Set-LocationProjects -Description "Demo"
Set-Alias -Name dc -Value docker-compose -Scope Global -Description "Demo"
Set-Alias -Name d -Value docker -Scope Global -Description "Demo"
Set-Alias -Name da -Value Get-DemoTeamAliases -Description "Demo"
Set-Alias -Name drm -Value Remove-AllDockerContainers -Description "Demo"
Set-Alias -Name dcbp -Value Start-DockerComposeParallelBuild -Description "Demo"
Set-Alias -Name dcb -Value Start-DockerComposeBuild -Description "Demo"
Set-Alias -Name sub -Value Set-DemoTeamAzureSubscription -Description "Demo"
# Optional - reload history from file (if exists)
$username = $env:USERNAME
$historyPath = "c:\users\$username\Box\personal-sync\command-history-$username.csv"
# only write if box is installed
if ((Test-Path -Path $historyPath -PathType Leaf )) {
Import-csv "$historyPath" | Add-History
}
else {
Write-Host "Box sync history skipped, directory not exist."
}
# This is where the prompt stuff happens
function Write-AnsiVT100 {
param(
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[string]$Message
,
[Parameter(Mandatory = $false)]
[ValidateNotNull()]
[int]$ForegroundColor = 255
,
[Parameter(Mandatory = $false)]
[ValidateNotNull()]
[int]$BackgroundColor = 0
)
$esc = "$([char]27)"
return "$esc[38;5;$ForegroundColor`m$esc[48;5;$BackgroundColor`m$Message$esc[0m$esc[0m"
}
# setup prompt
function Prompt {
# was the last command executed successful ?
[bool]$script:success = $?
# shorten and decorate current path
$pathArray = ($ExecutionContext.SessionState.Path.CurrentLocation) -split "\\"
if ($pathArray.Count -gt 3) {
$driveSegement = $pathArray | Select-Object -First 1
$pathSegment = ($pathArray | Select-Object -Last 3) -join " \ "
$currentLocation = "$driveSegement \ $([char]0x2026) \ $pathSegment"
}
else {
$currentLocation = $pathArray -join " \ "
}
# measure how long the last command took to execute
$lastCommand = Get-History -Count 1
if ($lastCommand) {
$duration = $lastCommand.EndExecutionTime - $lastCommand.StartExecutionTime
$elapsed = "{0:h\:mm\:ss\.ffff}" -f $duration
}
else {
$elapsed = "0:00:00.0000"
}
# get branch name
$branch = Get-BranchName
if ([string]::IsNullOrEmpty($branch)) {
$branch = "n/a"
}
# write prompt
$separator = ">"
$prompt = ""
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 0 -BackgroundColor 23
$prompt += Write-AnsiVT100 " | " -ForegroundColor 226 -BackgroundColor 23
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 23 -BackgroundColor 31
$prompt += Write-AnsiVT100 " $elapsed " -ForegroundColor 255 -BackgroundColor 31
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 31 -BackgroundColor 38
$prompt += Write-AnsiVT100 (" {0:HH\:mm\:ss\.ffff} " -f (Get-Date)) -ForegroundColor 0 -BackgroundColor 38
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 38 -BackgroundColor 236
$prompt += Write-AnsiVT100 " $($MyInvocation.HistoryId - 1) " -ForegroundColor 250 -BackgroundColor 236
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 236 -BackgroundColor 36
$prompt += Write-AnsiVT100 " $branch " -ForegroundColor 254 -BackgroundColor 36
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 36 -BackgroundColor 237
$prompt += Write-AnsiVT100 " $currentLocation " -ForegroundColor 253 -BackgroundColor 237
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 237 -BackgroundColor 0
$prompt += "`n"
$commandStateColor = @{ $true = 254; $false = 9 }[$script:success]
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 0 -BackgroundColor $commandStateColor
#$prompt += Write-AnsiVT100 " $ " -ForegroundColor 160 -BackgroundColor $commandStateColor
#$prompt += Write-AnsiVT100 "$separator" -ForegroundColor $commandStateColor -BackgroundColor 251
$prompt += Write-AnsiVT100 " PS " -ForegroundColor 234 -BackgroundColor 251
$prompt += Write-AnsiVT100 "$separator" -ForegroundColor 251 -BackgroundColor 0
return $prompt
}
# persistent history on exit
Register-EngineEvent -SupportEvent PowerShell.Exiting -Action {
# setup
$username = "<your-username>"
$historyRootPath = "C:\Users\$username\Box\personal-sync"
$commandHistoryFilePath = Join-Path $historyRootPath ("\command-history-{0}.csv" -f $username)
# save history
Get-History | Export-Csv -Path (Join-Path $historyRootPath ("\history-{0}-{1}.csv" -f $username, $PID)) -Append
# merge session history
Get-ChildItem -Path $historyRootPath -Filter ("history-{0}-*.csv" -f $username) | ForEach-Object {
Import-Csv -Path $_.FullName
} | Sort-Object -Property CommandLine -Unique | Export-Csv -Path $commandHistoryFilePath -Append
# delete old session history
Get-ChildItem -Path $historyRootPath -Filter ("history-{0}-*.csv" -f $username) | Remove-Item -Force;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment