Initial commit
This commit is contained in:
470
skills/powershell-shell-detection.md
Normal file
470
skills/powershell-shell-detection.md
Normal file
@@ -0,0 +1,470 @@
|
||||
---
|
||||
name: powershell-shell-detection
|
||||
description: Shell detection and cross-shell compatibility guidance for PowerShell vs Git Bash/MSYS2 on Windows
|
||||
---
|
||||
|
||||
# PowerShell Shell Detection & Cross-Shell Compatibility
|
||||
|
||||
Critical guidance for distinguishing between PowerShell and Git Bash/MSYS2 shells on Windows, with shell-specific path handling and compatibility notes.
|
||||
|
||||
## Shell Detection Priority (Windows)
|
||||
|
||||
When working on Windows, correctly identifying the shell environment is crucial for proper path handling and command execution.
|
||||
|
||||
### Detection Order (Most Reliable First)
|
||||
|
||||
1. **process.env.PSModulePath** (PowerShell specific)
|
||||
2. **process.env.MSYSTEM** (Git Bash/MinGW specific)
|
||||
3. **process.env.WSL_DISTRO_NAME** (WSL specific)
|
||||
4. **uname -s output** (Cross-shell, requires execution)
|
||||
|
||||
## PowerShell Detection
|
||||
|
||||
### Primary Indicators
|
||||
|
||||
**PSModulePath (Most Reliable):**
|
||||
```powershell
|
||||
# PowerShell detection
|
||||
if ($env:PSModulePath) {
|
||||
Write-Host "Running in PowerShell"
|
||||
# PSModulePath contains 3+ paths separated by semicolons
|
||||
$env:PSModulePath -split ';'
|
||||
}
|
||||
|
||||
# Check PowerShell version
|
||||
$PSVersionTable.PSVersion
|
||||
# Output: 7.5.4 (PowerShell 7) or 5.1.x (Windows PowerShell)
|
||||
```
|
||||
|
||||
**PowerShell-Specific Variables:**
|
||||
```powershell
|
||||
# These only exist in PowerShell
|
||||
$PSVersionTable # Version info
|
||||
$PSScriptRoot # Script directory
|
||||
$PSCommandPath # Script full path
|
||||
$IsWindows # Platform detection (PS 7+)
|
||||
$IsLinux # Platform detection (PS 7+)
|
||||
$IsMacOS # Platform detection (PS 7+)
|
||||
```
|
||||
|
||||
### Shell Type Detection in Scripts
|
||||
|
||||
```powershell
|
||||
function Get-ShellType {
|
||||
if ($PSVersionTable) {
|
||||
return "PowerShell $($PSVersionTable.PSVersion)"
|
||||
}
|
||||
elseif ($env:PSModulePath -and ($env:PSModulePath -split ';').Count -ge 3) {
|
||||
return "PowerShell (detected via PSModulePath)"
|
||||
}
|
||||
else {
|
||||
return "Not PowerShell"
|
||||
}
|
||||
}
|
||||
|
||||
Get-ShellType
|
||||
```
|
||||
|
||||
## Git Bash / MSYS2 Detection
|
||||
|
||||
### Primary Indicators
|
||||
|
||||
**MSYSTEM Environment Variable (Most Reliable):**
|
||||
```bash
|
||||
# Bash detection in Git Bash/MSYS2
|
||||
if [ -n "$MSYSTEM" ]; then
|
||||
echo "Running in Git Bash/MSYS2: $MSYSTEM"
|
||||
fi
|
||||
|
||||
# MSYSTEM values:
|
||||
# MINGW64 - Native Windows 64-bit environment
|
||||
# MINGW32 - Native Windows 32-bit environment
|
||||
# MSYS - POSIX-compliant build environment
|
||||
```
|
||||
|
||||
**Secondary Detection Methods:**
|
||||
```bash
|
||||
# Using OSTYPE (Bash-specific)
|
||||
case "$OSTYPE" in
|
||||
msys*) echo "MSYS/Git Bash" ;;
|
||||
cygwin*) echo "Cygwin" ;;
|
||||
linux*) echo "Linux" ;;
|
||||
darwin*) echo "macOS" ;;
|
||||
esac
|
||||
|
||||
# Using uname (Most portable)
|
||||
case "$(uname -s)" in
|
||||
MINGW64*) echo "Git Bash 64-bit" ;;
|
||||
MINGW32*) echo "Git Bash 32-bit" ;;
|
||||
MSYS*) echo "MSYS" ;;
|
||||
CYGWIN*) echo "Cygwin" ;;
|
||||
Linux*) echo "Linux" ;;
|
||||
Darwin*) echo "macOS" ;;
|
||||
esac
|
||||
```
|
||||
|
||||
## Cross-Shell Compatibility on Windows
|
||||
|
||||
### Critical Differences
|
||||
|
||||
| Aspect | PowerShell | Git Bash/MSYS2 |
|
||||
|--------|-----------|----------------|
|
||||
| **Environment Variable** | `$env:VARIABLE` | `$VARIABLE` |
|
||||
| **Path Separator** | `;` (semicolon) | `:` (colon) |
|
||||
| **Path Style** | `C:\Windows\System32` | `/c/Windows/System32` |
|
||||
| **Home Directory** | `$env:USERPROFILE` | `$HOME` |
|
||||
| **Temp Directory** | `$env:TEMP` | `/tmp` |
|
||||
| **Command Format** | `Get-ChildItem` | `ls` (native command) |
|
||||
| **Aliases** | PowerShell cmdlet aliases | Unix command aliases |
|
||||
|
||||
### Path Handling: PowerShell vs Git Bash
|
||||
|
||||
**PowerShell Path Handling:**
|
||||
```powershell
|
||||
# Native Windows paths work directly
|
||||
$path = "C:\Users\John\Documents"
|
||||
Test-Path $path # True
|
||||
|
||||
# Forward slashes also work in PowerShell 7+
|
||||
$path = "C:/Users/John/Documents"
|
||||
Test-Path $path # True
|
||||
|
||||
# Use Join-Path for cross-platform compatibility
|
||||
$configPath = Join-Path -Path $PSScriptRoot -ChildPath "config.json"
|
||||
|
||||
# Use [System.IO.Path] for advanced scenarios
|
||||
$fullPath = [System.IO.Path]::Combine($home, "documents", "file.txt")
|
||||
```
|
||||
|
||||
**Git Bash Path Handling:**
|
||||
```bash
|
||||
# Git Bash uses Unix-style paths
|
||||
path="/c/Users/John/Documents"
|
||||
test -d "$path" && echo "Directory exists"
|
||||
|
||||
# Automatic path conversion (CAUTION)
|
||||
# Git Bash converts Unix-style paths to Windows-style
|
||||
# /c/Users → C:\Users (automatic)
|
||||
# Arguments starting with / may be converted unexpectedly
|
||||
|
||||
# Use cygpath for manual conversion
|
||||
cygpath -u "C:\path" # → /c/path (Unix format)
|
||||
cygpath -w "/c/path" # → C:\path (Windows format)
|
||||
cygpath -m "/c/path" # → C:/path (Mixed format)
|
||||
```
|
||||
|
||||
## Automatic Path Conversion in Git Bash (CRITICAL)
|
||||
|
||||
Git Bash/MSYS2 automatically converts paths in certain scenarios, which can cause issues:
|
||||
|
||||
### What Triggers Conversion
|
||||
|
||||
```bash
|
||||
# Leading forward slash triggers conversion
|
||||
command /foo # Converts to C:\msys64\foo
|
||||
|
||||
# Path lists with colons
|
||||
export PATH=/foo:/bar # Converts to C:\msys64\foo;C:\msys64\bar
|
||||
|
||||
# Arguments after dashes
|
||||
command --path=/foo # Converts to --path=C:\msys64\foo
|
||||
```
|
||||
|
||||
### What's Exempt from Conversion
|
||||
|
||||
```bash
|
||||
# Arguments with equals sign (variable assignments)
|
||||
VAR=/foo command # NOT converted
|
||||
|
||||
# Drive specifiers
|
||||
command C:/path # NOT converted
|
||||
|
||||
# Arguments with semicolons (already Windows format)
|
||||
command "C:\foo;D:\bar" # NOT converted
|
||||
|
||||
# Double slashes (Windows switches)
|
||||
command //e //s # NOT converted
|
||||
```
|
||||
|
||||
### Disabling Path Conversion
|
||||
|
||||
```bash
|
||||
# Disable ALL conversion (Git Bash)
|
||||
export MSYS_NO_PATHCONV=1
|
||||
command /foo # Stays as /foo
|
||||
|
||||
# Exclude specific patterns (MSYS2)
|
||||
export MSYS2_ARG_CONV_EXCL="*" # Exclude everything
|
||||
export MSYS2_ARG_CONV_EXCL="--dir=;/test" # Specific prefixes
|
||||
```
|
||||
|
||||
## When to Use PowerShell vs Git Bash on Windows
|
||||
|
||||
### Use PowerShell When:
|
||||
|
||||
- ✅ **Windows-specific tasks** - Registry, WMI, Windows services
|
||||
- ✅ **Azure/Microsoft 365 automation** - Az, Microsoft.Graph modules
|
||||
- ✅ **Module ecosystem** - Leverage PSGallery modules
|
||||
- ✅ **Object-oriented pipelines** - Rich object manipulation
|
||||
- ✅ **Native Windows integration** - Built into Windows
|
||||
- ✅ **CI/CD with pwsh** - GitHub Actions, Azure DevOps
|
||||
- ✅ **Cross-platform scripting** - PowerShell 7 works on Linux/macOS
|
||||
|
||||
**Example PowerShell Scenario:**
|
||||
```powershell
|
||||
# Azure VM management with Az module
|
||||
Connect-AzAccount
|
||||
Get-AzVM -ResourceGroupName "Production" |
|
||||
Where-Object {$_.PowerState -eq "VM running"} |
|
||||
Stop-AzVM -Force
|
||||
```
|
||||
|
||||
### Use Git Bash When:
|
||||
|
||||
- ✅ **Unix tool compatibility** - sed, awk, grep, find
|
||||
- ✅ **Git operations** - Native Git command-line experience
|
||||
- ✅ **POSIX script execution** - Running Linux shell scripts
|
||||
- ✅ **Cross-platform shell scripts** - Bash scripts from Linux/macOS
|
||||
- ✅ **Text processing** - Unix text utilities (sed, awk, cut)
|
||||
- ✅ **Development workflows** - Node.js, Python, Ruby with Unix tools
|
||||
|
||||
**Example Git Bash Scenario:**
|
||||
```bash
|
||||
# Git workflow with Unix tools
|
||||
git log --oneline | grep -i "feature" | awk '{print $1}' |
|
||||
xargs git show --stat
|
||||
```
|
||||
|
||||
## Shell-Aware Script Design
|
||||
|
||||
### Detect and Adapt (PowerShell)
|
||||
|
||||
```powershell
|
||||
# Detect if running in PowerShell or Git Bash context
|
||||
function Test-PowerShellContext {
|
||||
return ($null -ne $PSVersionTable)
|
||||
}
|
||||
|
||||
# Adapt path handling based on context
|
||||
function Get-CrossPlatformPath {
|
||||
param([string]$Path)
|
||||
|
||||
if (Test-PowerShellContext) {
|
||||
# PowerShell: Use Join-Path
|
||||
return (Resolve-Path $Path -ErrorAction SilentlyContinue).Path
|
||||
}
|
||||
else {
|
||||
# Non-PowerShell context
|
||||
Write-Warning "Not running in PowerShell. Path operations may differ."
|
||||
return $Path
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Detect and Adapt (Bash)
|
||||
|
||||
```bash
|
||||
# Detect shell environment
|
||||
detect_shell() {
|
||||
if [ -n "$MSYSTEM" ]; then
|
||||
echo "git-bash"
|
||||
elif [ -n "$PSModulePath" ]; then
|
||||
echo "powershell"
|
||||
elif [ -n "$WSL_DISTRO_NAME" ]; then
|
||||
echo "wsl"
|
||||
else
|
||||
echo "unix"
|
||||
fi
|
||||
}
|
||||
|
||||
# Adapt path handling
|
||||
convert_path() {
|
||||
local path="$1"
|
||||
local shell_type=$(detect_shell)
|
||||
|
||||
case "$shell_type" in
|
||||
git-bash)
|
||||
# Convert Windows path to Unix style
|
||||
echo "$path" | sed 's|\\|/|g' | sed 's|^\([A-Z]\):|/\L\1|'
|
||||
;;
|
||||
*)
|
||||
echo "$path"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Usage
|
||||
shell_type=$(detect_shell)
|
||||
echo "Running in: $shell_type"
|
||||
```
|
||||
|
||||
## Environment Variable Comparison
|
||||
|
||||
### Common Environment Variables
|
||||
|
||||
| Variable | PowerShell | Git Bash | Purpose |
|
||||
|----------|-----------|----------|---------|
|
||||
| **Username** | `$env:USERNAME` | `$USER` | Current user |
|
||||
| **Home Directory** | `$env:USERPROFILE` | `$HOME` | User home |
|
||||
| **Temp Directory** | `$env:TEMP` | `/tmp` | Temporary files |
|
||||
| **Path List** | `$env:Path` (`;` sep) | `$PATH` (`:` sep) | Executable paths |
|
||||
| **Shell Detection** | `$env:PSModulePath` | `$MSYSTEM` | Shell identifier |
|
||||
|
||||
### Cross-Shell Variable Access
|
||||
|
||||
**PowerShell accessing environment variables:**
|
||||
```powershell
|
||||
$env:PATH # Current PATH
|
||||
$env:PSModulePath # PowerShell module paths
|
||||
$env:MSYSTEM # Would be empty in PowerShell
|
||||
[Environment]::GetEnvironmentVariable("PATH", "Machine") # System PATH
|
||||
```
|
||||
|
||||
**Git Bash accessing environment variables:**
|
||||
```bash
|
||||
echo $PATH # Current PATH
|
||||
echo $MSYSTEM # Git Bash: MINGW64, MINGW32, or MSYS
|
||||
echo $PSModulePath # Would be empty in pure Bash
|
||||
```
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Example 1: Cross-Shell File Finding
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
# Find files modified in last 7 days
|
||||
Get-ChildItem -Path "C:\Projects" -Recurse -File |
|
||||
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) } |
|
||||
Select-Object FullName, LastWriteTime
|
||||
```
|
||||
|
||||
**Git Bash:**
|
||||
```bash
|
||||
# Same operation in Git Bash
|
||||
find /c/Projects -type f -mtime -7 -exec ls -lh {} \;
|
||||
```
|
||||
|
||||
### Example 2: Process Management
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
# Stop all Chrome processes
|
||||
Get-Process chrome -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
```
|
||||
|
||||
**Git Bash:**
|
||||
```bash
|
||||
# Same operation in Git Bash
|
||||
ps aux | grep chrome | awk '{print $2}' | xargs kill -9 2>/dev/null
|
||||
```
|
||||
|
||||
### Example 3: Text File Processing
|
||||
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
# Extract unique email addresses from logs
|
||||
Get-Content "logs.txt" |
|
||||
Select-String -Pattern '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' |
|
||||
ForEach-Object { $_.Matches.Value } |
|
||||
Sort-Object -Unique
|
||||
```
|
||||
|
||||
**Git Bash:**
|
||||
```bash
|
||||
# Same operation in Git Bash
|
||||
grep -oE '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' logs.txt |
|
||||
sort -u
|
||||
```
|
||||
|
||||
## Troubleshooting Cross-Shell Issues
|
||||
|
||||
### Issue 1: Command Not Found
|
||||
|
||||
**Problem:** Command works in one shell but not another
|
||||
```powershell
|
||||
# PowerShell
|
||||
Get-Process # Works
|
||||
```
|
||||
```bash
|
||||
# Git Bash
|
||||
Get-Process # Command not found
|
||||
```
|
||||
|
||||
**Solution:** Understand that PowerShell cmdlets don't exist in Bash. Use native commands or install PowerShell Core (pwsh) in Git Bash:
|
||||
```bash
|
||||
# Run PowerShell from Git Bash
|
||||
pwsh -Command "Get-Process"
|
||||
```
|
||||
|
||||
### Issue 2: Path Format Mismatches
|
||||
|
||||
**Problem:** Paths don't work across shells
|
||||
```bash
|
||||
# Git Bash path
|
||||
/c/Users/John/file.txt # Works in Bash
|
||||
|
||||
# PowerShell
|
||||
Test-Path "/c/Users/John/file.txt" # May fail
|
||||
```
|
||||
|
||||
**Solution:** Use cygpath for conversion or normalize paths:
|
||||
```bash
|
||||
# Convert to Windows format for PowerShell
|
||||
win_path=$(cygpath -w "/c/Users/John/file.txt")
|
||||
pwsh -Command "Test-Path '$win_path'"
|
||||
```
|
||||
|
||||
### Issue 3: Alias Conflicts
|
||||
|
||||
**Problem:** `ls`, `cd`, `cat` behave differently
|
||||
```powershell
|
||||
# PowerShell
|
||||
ls # Actually runs Get-ChildItem
|
||||
```
|
||||
```bash
|
||||
# Git Bash
|
||||
ls # Runs native Unix ls command
|
||||
```
|
||||
|
||||
**Solution:** Use full cmdlet names in PowerShell scripts:
|
||||
```powershell
|
||||
# Instead of: ls
|
||||
Get-ChildItem # Explicit cmdlet name
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### PowerShell Scripts
|
||||
1. ✅ Use `$PSScriptRoot` for script-relative paths
|
||||
2. ✅ Use `Join-Path` or `[IO.Path]::Combine()` for paths
|
||||
3. ✅ Avoid hardcoded backslashes
|
||||
4. ✅ Use full cmdlet names (no aliases)
|
||||
5. ✅ Test on all target platforms
|
||||
6. ✅ Use `$IsWindows`, `$IsLinux`, `$IsMacOS` for platform detection
|
||||
|
||||
### Git Bash Scripts
|
||||
1. ✅ Check `$MSYSTEM` for Git Bash detection
|
||||
2. ✅ Use `cygpath` for path conversion when needed
|
||||
3. ✅ Set `MSYS_NO_PATHCONV=1` to disable auto-conversion if needed
|
||||
4. ✅ Quote paths with spaces
|
||||
5. ✅ Use Unix-style paths (`/c/...`) within Bash
|
||||
6. ✅ Convert to Windows paths when calling Windows tools
|
||||
|
||||
### Cross-Shell Development
|
||||
1. ✅ Document which shell your script requires
|
||||
2. ✅ Add shell detection at script start
|
||||
3. ✅ Provide clear error messages for wrong shell
|
||||
4. ✅ Consider creating wrapper scripts for cross-shell compatibility
|
||||
5. ✅ Test in both PowerShell and Git Bash if supporting both
|
||||
|
||||
## Resources
|
||||
|
||||
- [PowerShell Documentation](https://learn.microsoft.com/powershell)
|
||||
- [Git for Windows Documentation](https://git-scm.com/doc)
|
||||
- [MSYS2 Documentation](https://www.msys2.org/docs/what-is-msys2/)
|
||||
- [Cygpath Documentation](https://www.cygwin.com/cygwin-ug-net/cygpath.html)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** October 2025
|
||||
Reference in New Issue
Block a user