PowerShell Profiles
Last active
May 11, 2025 07:05
-
-
Save chadbaldwin/2ae7373ce6eb51c1b05b291c7dee40d8 to your computer and use it in GitHub Desktop.
My personal PowerShell profile file
This file contains hidden or 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
* | |
!Microsoft.*_profile.ps1 | |
!.gitignore |
This file contains hidden or 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
[CmdletBinding()] | |
param() | |
# gist - https://gist.github.com/chadbaldwin/2ae7373ce6eb51c1b05b291c7dee40d8 | |
Write-Verbose 'Profile Start' | |
$sw = [System.Diagnostics.Stopwatch]::new() | |
$sw.Start() | |
if ($PSBoundParameters['Debug']) { Set-PSDebug -Trace 1 } | |
###################################################### | |
# Prompt customization | |
###################################################### | |
#oh-my-posh init pwsh | iex | |
Set-PSReadLineOption -PredictionViewStyle InlineView -PredictionSource HistoryAndPlugin | |
Set-PSReadLineKeyHandler -Chord Ctrl+h -Function SwitchPredictionView | |
###################################################### | |
###################################################### | |
# Add custom paths to PATH | |
###################################################### | |
Write-Verbose 'Add custom paths to PATH' | |
$env:Path = ( | |
( | |
@( | |
, 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\Extensions\Microsoft\SQLDB\DAC' # SqlPackage.exe | |
, 'C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\' # SQLCMD | |
, 'C:\Program Files\Rancher Desktop\resources\resources\win32\bin\' # Rancher - Docker CLI / nerdctl | |
, 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\' # MSBuild + SSDT | |
, 'C:\Program Files (x86)\Red Gate\SQL Compare 15\' # sqlcompare | |
) + | |
@(gci -Directory -Path 'C:\tools\' | % FullName) + | |
# Existing path directories - split, test and clean | |
@($env:Path -split ';') | ? { Test-Path $_ } | select @{N='Path'; E={[System.IO.Path]::TrimEndingDirectorySeparator($_)}} | % Path | |
) | select -Unique | |
) -join ';' | |
###################################################### | |
###################################################### | |
# Custom Git Functions | |
###################################################### | |
# Run "git pull" on all Directories in the current directory | |
Write-Verbose 'Creating function: Git-PullAll' | |
function Git-PullAll { | |
Get-ChildItem -Directory | | |
ForEach-Object { | |
$_.FullName | |
Start-Job -WorkingDirectory $_ -ScriptBlock { git pull } | Receive-Job -AutoRemoveJob -Wait | |
} | |
} | |
Write-Verbose 'Creating function: Test-IsInsideGitRepo' | |
function Test-IsInsideGitRepo { | |
param ( | |
[Parameter(Position=0)][string]$Path = '.\' | |
) | |
(git -C $Path rev-parse --is-inside-work-tree 2> $null) -EQ 'true' ? $true : $false | |
} | |
Write-Verbose 'Creating function: Initialize-GitRepository' | |
function Initialize-GitRepository { | |
git init -b 'main' | |
git add . | |
git commit -m 'init' | |
} | |
###################################################### | |
###################################################### | |
# Custom Docker Functions | |
###################################################### | |
# gist - https://gist.github.com/chadbaldwin/1a486b4fbf48e9b0cd9551cb7e16382a | |
<# Docs: | |
https://hub.docker.com/_/microsoft-mssql-server | |
https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-configure-environment-variables | |
#> | |
# Start SQL Server docker instance | |
Write-Verbose 'Creating function: Start-SQLServer' | |
function Start-SQLServer { | |
[CmdletBinding()] | |
param ( | |
[Parameter()][string]$Tag = 'latest', | |
[Parameter()][string]$ContainerName = 'sqlserver', | |
[Parameter()][string]$Hostname, | |
[Parameter()][int]$Port = 1433, | |
[Parameter()][ValidateSet('Evaluation','Developer','Express','Web','Standard','EnterpriseCore')] | |
[string]$Edition, | |
[Parameter()][switch]$EnableSQLServerAgent, | |
[Parameter()][string]$ContainerTimeZone, | |
[Parameter()][switch]$Force | |
) | |
$sa_password = 'yourStrong(!)Password' | |
# If a container with that name already exists return an error unless -Force is specified, then we remove | |
if ($container_samename = docker container ls --filter "name=${ContainerName}" -q) { | |
$container_def = docker inspect $container_samename | ConvertFrom-Json | |
if ($Force) { | |
"`e[35mRemoving existing container '${ContainerName}'`e[0m`n" | |
docker rm -f $ContainerName | Out-Null | |
} else { | |
Write-Warning "Container '$($container_def.Name.TrimStart('/'))' already exists'. To replace, use -Force" | |
return | |
} | |
} | |
if ($container_sameport = docker container ls --filter "publish=${Port}/tcp" -q) { | |
$container_def = docker inspect $container_sameport | ConvertFrom-Json | |
Write-Warning "Container '$($container_def.Name.TrimStart('/'))' is already using port ${Port}. Specify a different port with -Port, or stop/remove the existing container." | |
return | |
} | |
# create & start | |
# Build params to use for docker run call | |
$params = ( | |
'--name', $ContainerName, | |
'-p', "${Port}:1433" | |
) | |
# Optional params | |
if ($Hostname) { $params += '-h', $Hostname } | |
# Environment variables # https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-configure-environment-variables | |
$params += '-e', 'ACCEPT_EULA=Y' | |
$params += '-e', "MSSQL_SA_PASSWORD=${sa_password}" | |
# Set the SQL Server edition - e.g. Developer, Standard, Enterprise, etc | |
if ($Edition) { $params += '-e', "MSSQL_PID=$Edition" } | |
# Enable SQL Server Agent | |
if ($EnableSQLServerAgent) { $params += '-e', "MSSQL_AGENT_ENABLED=${EnableSQLServerAgent}" } | |
# Set the time zone for the container # https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-configure-time-zone | |
if ($ContainerTimeZone) { $params += '-e', "TZ=${ContainerTimeZone}" } | |
# Image version | |
$params += "mcr.microsoft.com/mssql/server:${Tag}" | |
docker run -d @params | Out-Null | |
"Container '${ContainerName}' has been created and started`n`nWaiting until database is available..." | |
while ($true) { | |
sleep 1 | |
# Depending on the version of SQL Server, the path to sqlcmd is different. Testing both because I'm lazy | |
docker exec $ContainerName /opt/mssql-tools/bin/sqlcmd -U sa -P $sa_password -C -l 2 -t 2 -Q 'SELECT 1' *>&1 | Out-Null | |
if ($LASTEXITCODE -eq 0) { break } | |
docker exec $ContainerName /opt/mssql-tools18/bin/sqlcmd -U sa -P $sa_password -No -l 2 -t 2 -Q 'SELECT 1' *>&1 | Out-Null | |
if ($LASTEXITCODE -eq 0) { break } | |
# Commenting out - using docker exec instead to remove dependency on sqlcmd being installed locally | |
#sqlcmd -U sa -P $sa_password -S localhost -l 1 -t 1 -Q 'SELECT 1' *>&1 | Out-Null | |
#if ($LASTEXITCODE -eq 0) { break } | |
} | |
$container = docker inspect $ContainerName | ConvertFrom-Json | |
"`nImage: ${Tag}`n" | |
"SQL Server Version: $($container.Config.Labels.'com.microsoft.version')`n" | |
"Login credentials:`e[32m" | |
" Server: localhost$($Port -ne '1433' ? ",${Port}" : '')" | |
" Username: sa" | |
" Password: ${sa_password} `e[31m<-- DO NOT USE IN PRODUCTION`e[0m" | |
} | |
Write-Verbose 'Register ArgumentCompleter: Start-SQLServer:Tag' | |
Register-ArgumentCompleter -CommandName Start-SQLServer -ParameterName Tag -ScriptBlock { | |
(irm 'https://mcr.microsoft.com/v2/mssql/server/tags/list').tags | ? { $_ -match 'latest' } | |
} | |
###################################################### | |
###################################################### | |
# Other | |
###################################################### | |
# pseudo sudo - Opens a new instance of the current shell under an admin account | |
Write-Verbose 'Creating function: Open-PowerShellAsAdmin' | |
function Open-PowerShellAsAdmin { | |
$process = Get-Process -Id $PID | |
if ($process.Parent.Name -eq 'WindowsTerminal') { | |
<# Hard coded to use the PowerShell Core profile. | |
For some reason, Windows PowerShell doesn't seem to be aware | |
that it is running within Windows Terminal when pulling the | |
proess via Get-Process. #> | |
Start-Process 'wt' -ArgumentList "-p 'PowerShell' -d `"${PWD}`"" -Verb RunAs | |
} elseif ($process.Name -eq 'pwsh') { | |
Start-Process 'pwsh' -WorkingDirectory $PWD -Verb RunAs | |
} elseif ($process.Name -eq 'powershell') { | |
Start-Process 'powershell' -WorkingDirectory $PWD -Verb RunAs | |
} | |
} | |
# Sets the title of the current powershell window | |
Write-Verbose 'Creating function: Set-Title' | |
function Set-Title($Title) { | |
$host.ui.RawUI.WindowTitle = $Title | |
} | |
# Removes all empty directories recursively in the current directory | |
Write-Verbose 'Creating function: Remove-EmptyDirectories' | |
function Remove-EmptyDirectories { | |
Get-ChildItem -Path $folder -Directory -Recurse | Where-Object { -Not $_.GetFiles('*',1) } | Remove-Item -Recurse | |
} | |
# Cleans build data and user settings from a visual studio solution folder | |
Write-Verbose 'Creating function: Clean-SolutionFolder' | |
function Clean-SolutionFolder { | |
'bin','obj','.vs' | Get-ChildItem -Directory -Recurse -Force | Remove-Item -Force -Recurse | |
'*.user' | Get-ChildItem -File -Recurse | Remove-Item -Force -Recurse | |
} | |
# Shortcut for bypassing explorer...instead using Powershell and ZLocation. | |
Write-Verbose 'Creating function: Invoke-QuickOpen' | |
function Invoke-QuickOpen { | |
param ([Parameter()][string]$Search) | |
z $Search | |
Invoke-Item . | |
exit | |
} | |
# Open the local .sln file. If there's multiple, bring them up in fzf | |
Write-Verbose 'Creating function: Open-Solution' | |
function Open-Solution { | |
$files = Get-ChildItem -Filter *.sln | |
if ($files.Count -eq 0) { | |
Write-Output 'No *.sln files found' | |
} elseif ($files.Count -eq 1) { | |
Invoke-Item "${files}" | |
} else { | |
$file = $files | Where-Object ToString | fzf | |
Invoke-Item "${file}" | |
} | |
} | |
# Bring window specified by process name into focus | |
Write-Verbose 'Creating function: Set-ForegroundWindow' | |
function Set-ForegroundWindow { | |
[CmdletBinding()] | |
param ( | |
[Parameter(ValueFromPipeline)][string]$ProcessName, | |
[Parameter()][int]$ShowType | |
) | |
$type = ' | |
using System; | |
using System.Runtime.InteropServices; | |
public class WinAp { | |
[DllImport("user32.dll")] | |
public static extern bool SetForegroundWindow(IntPtr hWnd); | |
[DllImport("user32.dll")] | |
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); | |
}' | |
Add-Type $type | |
# Pick the first window that matches this process | |
$h = (Get-Process -Name $ProcessName | Where-Object MainWindowTitle | Select-Object -First 1).MainWindowHandle | |
# If you can't find the window, exit | |
if ($null -eq $h) { return } | |
# 2 = Minimize window | |
[WinAp]::ShowWindow($h, 2) # minimize it first...this is a hack so that we maintain snapped window positions | |
# 4 or 9 = Restore window, these are the only ones that seem to work predictably. | |
# They will restore a window to its original position if it is minimized, even if the window was snapped | |
# However, if the window is not minimized but is only out of focus, then it will unsnap the window. | |
# This is why we first minimize it, then restore it. It's hacky, but it works. It just looks a little funny. | |
[WinAp]::ShowWindow($h, 4) | |
# Bring window into focus | |
[WinAp]::SetForegroundWindow($h) | |
} | |
# Sort, dedup and overwrite clipboard contents | |
Write-Verbose 'Creating function: Sort-Clipboard' | |
function Sort-Clipboard { | |
(gcb -Raw) -split '[\r\n]' | % Trim | ? { $_ } | sort -Unique | scb | |
} | |
# Takes a pipeline input and groups it into batches of N size then returns the new arrays | |
Write-Verbose 'Creating function: Get-Batches' | |
function Get-Batches { | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)][int]$BatchSize, | |
[Parameter(ValueFromPipeline = $true)][psobject]$InputObject | |
) | |
begin { | |
$currentBatch = @() | |
} | |
process { | |
$currentBatch += $_ | |
if ($currentBatch.Count -eq $BatchSize) { | |
,$currentBatch | |
$currentBatch = @() | |
} | |
} | |
end { | |
if ($currentBatch.Count -gt 0) { | |
,$currentBatch | |
} | |
} | |
} | |
# Usage Examples: | |
# Write-Output (1..101) -NoEnumerate | Get-Batches -BatchSize 6 | % { $_ -join ',' } | |
# Get-Batches -BatchSize 6 -Array (1..101) | % { $_ -join ',' } | |
# Write-Output (1..101) -NoEnumerate | Get-Batches 6 | % { $_ -join ',' } | |
# Get-Batches 6 (1..101) | % { $_ -join ',' } | |
###################################################### | |
###################################################### | |
# Find system tools | |
###################################################### | |
# Useful for finding things like msbuild.exe, vswhere.exe, sqlpackage.exe, etc | |
Write-Verbose 'Creating function: Find-SystemTool' | |
function Find-SystemTool { | |
param ( | |
[Parameter(Mandatory,Position=0)][string]$Search | |
) | |
# Because we're searching a lot of system directories, we run into a lot of access denied errors. So for now | |
# it's easier to just ignore all and continue on. Maybe in the future I'll add better error handling. | |
$ErrorActionPreference = 'SilentlyContinue' | |
$list = @() | |
# Search PowerShell aware commands - this needs to be prioritized to indicate it can be called without including the full path | |
$list += Get-Command $Search -All | | |
Select-Object @{N='Path';E={$_.Source}}, | |
Version, | |
@{N='Source';E={'Get-Command'}}, @{N='Rank';E={1}} | |
if (Get-Command es) { | |
# If system has `Everything` cli installed, use that instead since it is the fastest | |
$list += es !"${env:SystemRoot}" !"${env:USERPROFILE}\Appdata" "*${Search}" | | |
Get-Item | | |
Select-Object @{N='Path';E={$_.FullName}}, | |
@{N='Version';E={$_.VersionInfo.FileVersion}}, | |
@{N='Source';E={'Everything'}}, @{N='Rank';E={2}} | |
} else { | |
# Search all path directories - redundant with Get-Command, except for non-applications (modules, scripts, etc) | |
$list += $env:Path -split ';' | | |
Get-Item | | |
Get-ChildItem -File -Filter $Search | | |
Select-Object @{N='Path';E={$_.FullName}}, | |
@{N='Version';E={$_.VersionInfo.FileVersion}}, | |
@{N='Source';E={'Path'}}, @{N='Rank';E={2}} | |
# Search all common directories - this is by far the slowest because we are recursively searching many system folders | |
$list += $env:ProgramFiles, ${env:ProgramFiles(x86)}, $env:ProgramData, | |
$env:ChocolateyToolsLocation, $env:ChocolateyInstall | | |
Get-Item | | |
Get-ChildItem -File -Recurse -Filter $Search | | |
Select-Object @{N='Path';E={$_.FullName}}, | |
@{N='Version';E={$_.VersionInfo.FileVersion}}, | |
@{N='Source';E={'Manual'}}, @{N='Rank';E={3}} | |
} | |
# Dedup by path, select first by rank | |
$list | | |
Group-Object Path | | |
ForEach-Object { $_.Group | Sort-Object Rank | Select-Object -First 1 } | | |
Sort-Object Version -Descending | | |
Select-Object Path, Version, Source | |
} | |
###################################################### | |
###################################################### | |
# Aliases | |
###################################################### | |
Write-Verbose 'Add custom aliases' | |
if (Test-Path "${env:ProgramFiles(x86)}\Notepad++\notepad++.exe") { | |
Set-Alias -Name npp -Value "${env:ProgramFiles(x86)}\Notepad++\notepad++.exe" -Verbose:($VerbosePreference -eq "Continue") | |
} elseif (Test-Path "${env:ProgramFiles}\Notepad++\notepad++.exe") { | |
Set-Alias -Name npp -Value "${env:ProgramFiles}\Notepad++\notepad++.exe" -Verbose:($VerbosePreference -eq "Continue") | |
} | |
Set-Alias -Name zz -Value 'Invoke-QuickOpen' -Verbose:($VerbosePreference -eq "Continue") | |
Set-Alias -Name w -Value 'Set-ForegroundWindow' -Verbose:($VerbosePreference -eq "Continue") | |
Set-Alias -Name query -Value 'Invoke-DbaQuery' -Verbose:($VerbosePreference -eq "Continue") | |
Set-Alias -Name sln -Value 'Open-Solution' -Verbose:($VerbosePreference -eq "Continue") | |
Set-Alias -Name ginit -Value 'Initialize-GitRepository' -Verbose:($VerbosePreference -eq "Continue") | |
Set-Alias -Name sudo -Value 'Open-PowerShellAsAdmin' -Verbose:($VerbosePreference -eq "Continue") | |
###################################################### | |
###################################################### | |
###################################################### | |
$sw.Stop() | |
Write-Host "Profile Loaded: $($sw.Elapsed) ($($sw.Elapsed.TotalMilliseconds) ms)" | |
Remove-Variable sw | |
if ($PSBoundParameters['Debug']) { Set-PSDebug -Trace 0 } |
This file contains hidden or 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
write "Profile: $($MyInvocation.MyCommand.Name)" | |
write "Loading: Microsoft.PowerShell_profile.ps1" | |
$userprofile = Join-Path ([Environment]::GetFolderPath("MyDocuments")) 'PowerShell\Microsoft.PowerShell_profile.ps1' | gi | |
. $userprofile |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
[reserving first comment]