Last active
July 31, 2025 14:11
-
-
Save matfax/e61e84cf186681ad1f9ac3b25f517df4 to your computer and use it in GitHub Desktop.
PowerShell CommandNotFoundHandler with WSL and Git Alias Fallback
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
| # PowerShell Profile - Add this to your $PROFILE file | |
| # Enable experimental features if needed (PowerShell 7.0-7.4) | |
| if ($PSVersionTable.PSVersion -lt [Version]"7.5.0") { | |
| try { | |
| Enable-ExperimentalFeature PSFeedbackProvider -ErrorAction SilentlyContinue | |
| Enable-ExperimentalFeature PSCommandNotFoundSuggestion -ErrorAction SilentlyContinue | |
| } catch { | |
| # Features may already be enabled or not available | |
| } | |
| } | |
| # Initialize caches | |
| if ($null -eq $script:gitAliases) { | |
| $script:gitAliases = @() | |
| } | |
| if ($null -eq $script:wslCommandCache) { | |
| $script:wslCommandCache = @{} | |
| } | |
| # Hybrid CommandNotFoundAction Handler | |
| $ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction = { | |
| param($CommandName, $CommandLookupEventArgs) | |
| # Git Aliases | |
| if ($script:gitAliases.Count -eq 0) { | |
| try { | |
| $script:gitAliases = (git config --global -l) | | |
| Where-Object { $_ -match '^alias\.(.+?)=' } | | |
| ForEach-Object { $matches[1] } | |
| } catch { | |
| $script:gitAliases = @() | |
| } | |
| } | |
| if ($CommandName -in $script:gitAliases) { | |
| Write-Host "Git Alias Found: Running 'git $CommandName'" -ForegroundColor Cyan | |
| $CommandLookupEventArgs.CommandScriptBlock = [scriptblock]::Create("git $CommandName") | |
| $CommandLookupEventArgs.StopSearch = $true | |
| return | |
| } | |
| # WSL Fallback with Caching | |
| try { | |
| # Check cache first | |
| if (-not $script:wslCommandCache.ContainsKey($CommandName)) { | |
| # Get the full path to the command using bash with proper environment | |
| $whichOutput = wsl.exe bash -l -c "which $CommandName" 2>$null | |
| if ($LASTEXITCODE -eq 0 -and $whichOutput) { | |
| # Split by newlines and take the first path (the one that would execute) | |
| $firstPath = ($whichOutput -split "`n" | Where-Object { $_.Trim() } | Select-Object -First 1) | |
| if ($firstPath) { | |
| $script:wslCommandCache[$CommandName] = $firstPath.Trim() | |
| } else { | |
| $script:wslCommandCache[$CommandName] = $null | |
| } | |
| } else { | |
| $script:wslCommandCache[$CommandName] = $null | |
| } | |
| } | |
| $cachedPath = $script:wslCommandCache[$CommandName] | |
| if ($cachedPath) { | |
| # Define a list of potentially destructive commands to block | |
| $unsafeWslCommands = @("rm", "mkfs", "fdisk", "dd", "chmod", "chown") | |
| if ($CommandName -in $unsafeWslCommands) { | |
| Write-Warning "WSL command '$CommandName' is potentially unsafe and was blocked." | |
| $CommandLookupEventArgs.StopSearch = $true | |
| return | |
| } | |
| Write-Host "PowerShell command not found. Running from WSL..." -ForegroundColor Cyan | |
| # Define the script block to execute the command in WSL using cached path | |
| $CommandLookupEventArgs.CommandScriptBlock = { | |
| # Force the result into an array using @() to prevent strings from being split during splatting | |
| $wslArgs = @($args | ForEach-Object { | |
| # Check if an argument is a Windows path (e.g., C:\...) | |
| if ($_ -match '^[A-Za-z]:') { | |
| # Convert the Windows path to a WSL path (e.g., /mnt/c/...) | |
| $driveLetter = $_.Substring(0,1).ToLower() | |
| $path = $_.Substring(2) -replace '\\', '/' | |
| "/mnt/$driveLetter$path" | |
| } else { | |
| # Pass other arguments through unchanged | |
| $_ | |
| } | |
| }) | |
| # Execute using the cached full path | |
| wsl.exe -- $cachedPath @wslArgs | |
| }.GetNewClosure() | |
| $CommandLookupEventArgs.StopSearch = $true | |
| return | |
| } | |
| } catch { | |
| # WSL not available or an error occurred, continue to built-in suggestions | |
| } | |
| # Let PowerShell's built-in suggestion system work if no other handler has run | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment