Skip to content

Instantly share code, notes, and snippets.

@chadbaldwin
Last active May 11, 2025 07:05
Show Gist options
  • Save chadbaldwin/2ae7373ce6eb51c1b05b291c7dee40d8 to your computer and use it in GitHub Desktop.
Save chadbaldwin/2ae7373ce6eb51c1b05b291c7dee40d8 to your computer and use it in GitHub Desktop.
My personal PowerShell profile file
*
!Microsoft.*_profile.ps1
!.gitignore

PowerShell Profiles

[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 }
write "Profile: $($MyInvocation.MyCommand.Name)"
write "Loading: Microsoft.PowerShell_profile.ps1"
$userprofile = Join-Path ([Environment]::GetFolderPath("MyDocuments")) 'PowerShell\Microsoft.PowerShell_profile.ps1' | gi
. $userprofile
@chadbaldwin
Copy link
Author

[reserving first comment]

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