Initial commit
This commit is contained in:
337
skills/powershell-2025-changes.md
Normal file
337
skills/powershell-2025-changes.md
Normal file
@@ -0,0 +1,337 @@
|
||||
---
|
||||
name: powershell-2025-changes
|
||||
description: Critical PowerShell changes, deprecations, and migrations for 2025
|
||||
---
|
||||
|
||||
# PowerShell 2025 Breaking Changes & Migrations
|
||||
|
||||
Critical changes, deprecations, and migration paths for PowerShell in 2025.
|
||||
|
||||
## PowerShell 2.0 Removal (August-September 2025)
|
||||
|
||||
### What's Removed
|
||||
|
||||
PowerShell 2.0 has been **completely removed** from:
|
||||
- **Windows 11 version 24H2** (August 2025)
|
||||
- **Windows Server 2025** (September 2025)
|
||||
|
||||
**Why:** Security improvements, reduced attack surface, legacy code cleanup
|
||||
|
||||
### Migration Path
|
||||
|
||||
```powershell
|
||||
# Check if PowerShell 2.0 is installed
|
||||
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
|
||||
|
||||
# If you still need PowerShell 2.0 (NOT RECOMMENDED)
|
||||
# - Use older Windows versions
|
||||
# - Use Windows containers with older base images
|
||||
# - Upgrade scripts to PowerShell 5.1 or 7+
|
||||
|
||||
# Recommended: Migrate to PowerShell 7.5+
|
||||
winget install Microsoft.PowerShell
|
||||
```
|
||||
|
||||
**Action Required:** Audit all scripts and remove `-Version 2.0` parameters from any PowerShell invocations.
|
||||
|
||||
---
|
||||
|
||||
## MSOnline & AzureAD Module Retirement
|
||||
|
||||
### Retirement Timeline
|
||||
|
||||
| Module | Stop Working | Retirement Complete |
|
||||
|--------|--------------|---------------------|
|
||||
| **MSOnline** | Late May 2025 | May 31, 2025 |
|
||||
| **AzureAD** | March 30, 2025 | After July 1, 2025 |
|
||||
|
||||
**Critical:** These modules will stop functioning - not just deprecated, but **completely non-functional**.
|
||||
|
||||
### Migration Path
|
||||
|
||||
**From MSOnline/AzureAD to Microsoft.Graph:**
|
||||
|
||||
```powershell
|
||||
# OLD (MSOnline) - STOPS WORKING MAY 2025
|
||||
Connect-MsolService
|
||||
Get-MsolUser
|
||||
Set-MsolUser -UserPrincipalName "user@domain.com" -UsageLocation "US"
|
||||
|
||||
# NEW (Microsoft.Graph 2.32.0)
|
||||
Connect-MgGraph -Scopes "User.ReadWrite.All"
|
||||
Get-MgUser
|
||||
Update-MgUser -UserId "user@domain.com" -UsageLocation "US"
|
||||
|
||||
# OLD (AzureAD) - STOPS WORKING MARCH 2025
|
||||
Connect-AzureAD
|
||||
Get-AzureADUser
|
||||
New-AzureADUser -DisplayName "John Doe" -UserPrincipalName "john@domain.com"
|
||||
|
||||
# NEW (Microsoft.Graph 2.32.0)
|
||||
Connect-MgGraph -Scopes "User.ReadWrite.All"
|
||||
Get-MgUser
|
||||
New-MgUser -DisplayName "John Doe" -UserPrincipalName "john@domain.com"
|
||||
```
|
||||
|
||||
**Alternative:** Use Microsoft Entra PowerShell module (successor to AzureAD)
|
||||
|
||||
```powershell
|
||||
Install-Module -Name Microsoft.Graph.Entra -Scope CurrentUser
|
||||
Connect-Entra
|
||||
Get-EntraUser
|
||||
```
|
||||
|
||||
### Common Command Mappings
|
||||
|
||||
| MSOnline/AzureAD | Microsoft.Graph | Notes |
|
||||
|------------------|----------------|-------|
|
||||
| `Get-MsolUser` / `Get-AzureADUser` | `Get-MgUser` | Requires User.Read.All scope |
|
||||
| `Get-MsolGroup` / `Get-AzureADGroup` | `Get-MgGroup` | Requires Group.Read.All scope |
|
||||
| `Get-MsolDevice` / `Get-AzureADDevice` | `Get-MgDevice` | Requires Device.Read.All scope |
|
||||
| `Connect-MsolService` / `Connect-AzureAD` | `Connect-MgGraph` | Scope-based permissions |
|
||||
|
||||
---
|
||||
|
||||
## WMIC Removal (Windows 11 25H2)
|
||||
|
||||
### What's Removed
|
||||
|
||||
**Windows Management Instrumentation Command-line (WMIC)** tool removed after upgrading to Windows 11 25H2+.
|
||||
|
||||
### Migration Path
|
||||
|
||||
**From WMIC to PowerShell WMI/CIM:**
|
||||
|
||||
```powershell
|
||||
# OLD (WMIC) - REMOVED
|
||||
wmic process list brief
|
||||
wmic os get caption
|
||||
|
||||
# NEW (PowerShell CIM)
|
||||
Get-CimInstance -ClassName Win32_Process | Select-Object Name, ProcessId, CommandLine
|
||||
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object Caption, Version
|
||||
|
||||
# For detailed process info
|
||||
Get-Process | Format-Table Name, Id, CPU, WorkingSet -AutoSize
|
||||
|
||||
# For system info
|
||||
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PowerShellGet → PSResourceGet Migration
|
||||
|
||||
### Modern Package Management (2025)
|
||||
|
||||
**PSResourceGet** is the official successor to PowerShellGet (2x faster, actively developed).
|
||||
|
||||
```powershell
|
||||
# Install PSResourceGet (ships with PowerShell 7.4+)
|
||||
Install-Module -Name Microsoft.PowerShell.PSResourceGet -Force
|
||||
|
||||
# New commands (PSResourceGet)
|
||||
Install-PSResource -Name Az -Scope CurrentUser # Replaces Install-Module
|
||||
Find-PSResource -Name "*Azure*" # Replaces Find-Module
|
||||
Update-PSResource -Name Az # Replaces Update-Module
|
||||
Get-InstalledPSResource # Replaces Get-InstalledModule
|
||||
|
||||
# Compatibility layer available for legacy scripts
|
||||
# Your old Install-Module commands still work but call PSResourceGet internally
|
||||
```
|
||||
|
||||
**Performance Comparison:**
|
||||
- **PowerShellGet**: 10-15 seconds to install module
|
||||
- **PSResourceGet**: 5-7 seconds to install module (2x faster)
|
||||
|
||||
---
|
||||
|
||||
## Test-Json Schema Changes
|
||||
|
||||
### Breaking Change (PowerShell 7.4+)
|
||||
|
||||
**Test-Json** now uses **JsonSchema.NET** instead of **Newtonsoft.Json.Schema**.
|
||||
|
||||
**Impact:** No longer supports Draft 4 JSON schemas.
|
||||
|
||||
```powershell
|
||||
# OLD (Draft 4 schema) - NO LONGER SUPPORTED
|
||||
$schema = @"
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object"
|
||||
}
|
||||
"@
|
||||
|
||||
Test-Json -Json $json -Schema $schema # FAILS in PowerShell 7.4+
|
||||
|
||||
# NEW (Draft 6+ schema) - SUPPORTED
|
||||
$schema = @"
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-06/schema#",
|
||||
"type": "object"
|
||||
}
|
||||
"@
|
||||
|
||||
Test-Json -Json $json -Schema $schema # WORKS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## #Requires -PSSnapin Removed
|
||||
|
||||
### Breaking Change (PowerShell 7.4+)
|
||||
|
||||
All code related to `#Requires -PSSnapin` has been removed.
|
||||
|
||||
```powershell
|
||||
# OLD (PowerShell 5.1 and earlier)
|
||||
#Requires -PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
|
||||
|
||||
# NEW (Use modules instead)
|
||||
#Requires -Modules ExchangeOnlineManagement
|
||||
|
||||
Import-Module ExchangeOnlineManagement
|
||||
Connect-ExchangeOnline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Hardening (2025 Standards)
|
||||
|
||||
### Just Enough Administration (JEA)
|
||||
|
||||
**JEA** is now a security requirement for production environments:
|
||||
|
||||
```powershell
|
||||
# Create JEA session configuration
|
||||
New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer `
|
||||
-Path "C:\JEA\RestrictedAdmin.pssc" `
|
||||
-VisibleCmdlets @{
|
||||
Name = 'Restart-Service'
|
||||
Parameters = @{ Name = 'Name'; ValidateSet = 'Spooler' }
|
||||
} `
|
||||
-LanguageMode NoLanguage
|
||||
|
||||
# Register JEA endpoint
|
||||
Register-PSSessionConfiguration -Name RestrictedAdmin `
|
||||
-Path "C:\JEA\RestrictedAdmin.pssc" `
|
||||
-Force
|
||||
|
||||
# Connect with limited privileges
|
||||
Enter-PSSession -ComputerName Server01 -ConfigurationName RestrictedAdmin
|
||||
```
|
||||
|
||||
### Windows Defender Application Control (WDAC)
|
||||
|
||||
**WDAC** replaces AppLocker for PowerShell script control:
|
||||
|
||||
```powershell
|
||||
# Create WDAC policy for PowerShell scripts
|
||||
New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-ScanPath "C:\Scripts" `
|
||||
-Level FilePublisher `
|
||||
-Fallback Hash
|
||||
|
||||
# Convert to binary and deploy
|
||||
ConvertFrom-CIPolicy -XmlFilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b"
|
||||
```
|
||||
|
||||
### Constrained Language Mode
|
||||
|
||||
**Constrained Language Mode** is now recommended for all users without admin privileges:
|
||||
|
||||
```powershell
|
||||
# Check current language mode
|
||||
$ExecutionContext.SessionState.LanguageMode
|
||||
# Output: FullLanguage (admin) or ConstrainedLanguage (standard user)
|
||||
|
||||
# Set system-wide constrained language mode via Group Policy or Environment Variable
|
||||
# Set HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\__PSLockdownPolicy = 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PowerShell 7.6 Preview Features
|
||||
|
||||
### Current Status (October 2025)
|
||||
|
||||
PowerShell 7.6.0 Preview 5 available (built on .NET 9.0.101)
|
||||
|
||||
**New Features:**
|
||||
- **PSRedirectToVariable**: Allow redirecting to a variable
|
||||
- **Module Rename**: ThreadJob → Microsoft.PowerShell.ThreadJob
|
||||
- **PSResourceGet 1.1.0**: Improved performance and Azure Artifacts support
|
||||
|
||||
```powershell
|
||||
# Check PowerShell version
|
||||
$PSVersionTable.PSVersion
|
||||
# 7.5.4 (stable) or 7.6.0-preview.5
|
||||
|
||||
# .NET version
|
||||
[System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription
|
||||
# .NET 9.0.101
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### Immediate Actions Required (2025)
|
||||
|
||||
- [ ] **Audit MSOnline/AzureAD usage** - Migrate to Microsoft.Graph 2.32.0 before May 2025
|
||||
- [ ] **Remove PowerShell 2.0 references** - Upgrade to PowerShell 7.5+
|
||||
- [ ] **Replace WMIC commands** - Use Get-CimInstance/Get-Process
|
||||
- [ ] **Update JSON schemas** - Migrate Draft 4 to Draft 6+
|
||||
- [ ] **Remove PSSnapin requirements** - Convert to modules
|
||||
- [ ] **Adopt PSResourceGet** - Faster, modern package management
|
||||
- [ ] **Implement JEA** - Role-based access control for production
|
||||
- [ ] **Enable WDAC** - Application control for PowerShell scripts
|
||||
- [ ] **Test Constrained Language Mode** - For non-admin users
|
||||
|
||||
### Recommended Actions
|
||||
|
||||
- [ ] **Upgrade to PowerShell 7.5.4** - Latest stable with .NET 9
|
||||
- [ ] **Adopt Az 14.5.0** - Latest Azure module with zone redundancy
|
||||
- [ ] **Use Microsoft.Graph 2.32.0** - Actively maintained Graph SDK
|
||||
- [ ] **Enable Script Block Logging** - Security auditing
|
||||
- [ ] **Implement Code Signing** - For production scripts
|
||||
- [ ] **Use Azure Key Vault** - For credential management
|
||||
|
||||
---
|
||||
|
||||
## Testing Migration
|
||||
|
||||
```powershell
|
||||
# Test for deprecated module usage
|
||||
Get-Module MSOnline, AzureAD -ListAvailable
|
||||
# If found, plan migration immediately
|
||||
|
||||
# Test for PowerShell 2.0 dependencies
|
||||
Get-Content "script.ps1" | Select-String -Pattern "powershell.exe -Version 2"
|
||||
# If found, remove version parameter
|
||||
|
||||
# Test for WMIC usage
|
||||
Get-ChildItem -Path "C:\Scripts" -Recurse -Filter "*.ps1" |
|
||||
Select-String -Pattern "wmic" |
|
||||
Select-Object Path, Line
|
||||
|
||||
# Verify PowerShell version compatibility
|
||||
#Requires -Version 7.0
|
||||
Test-Path $PSCommandPath # Ensures script is PowerShell 7+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [PowerShell 7.5 Release Notes](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-75)
|
||||
- [MSOnline/AzureAD Retirement Info](https://techcommunity.microsoft.com/blog/microsoft-entra-blog/action-required-msonline-and-azuread-powershell-retirement---2025-info-and-resou/4364991)
|
||||
- [PSResourceGet Documentation](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.psresourceget)
|
||||
- [JEA Documentation](https://learn.microsoft.com/en-us/powershell/scripting/security/remoting/jea/overview)
|
||||
- [WDAC Documentation](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** October 2025
|
||||
420
skills/powershell-7.5-features.md
Normal file
420
skills/powershell-7.5-features.md
Normal file
@@ -0,0 +1,420 @@
|
||||
---
|
||||
name: powershell-7.5-features
|
||||
description: PowerShell 7.5 new features and cmdlets built on .NET 9
|
||||
---
|
||||
|
||||
# PowerShell 7.5 New Features
|
||||
|
||||
PowerShell 7.5 GA (General Availability: January 2025) - Latest stable version 7.5.4 (October 2025) built on .NET 9.0.306 with significant performance and memory enhancements.
|
||||
|
||||
## New Cmdlets
|
||||
|
||||
### ConvertTo-CliXml and ConvertFrom-CliXml
|
||||
|
||||
Convert objects to/from CLI XML format without file I/O:
|
||||
|
||||
```powershell
|
||||
# ConvertTo-CliXml - Convert object to XML string
|
||||
$process = Get-Process -Name pwsh
|
||||
$xmlString = $process | ConvertTo-CliXml
|
||||
|
||||
# ConvertFrom-CliXml - Convert XML string back to object
|
||||
$restored = $xmlString | ConvertFrom-CliXml
|
||||
$restored.ProcessName # Outputs: pwsh
|
||||
|
||||
# Use cases:
|
||||
# - Serialize objects for API transmission
|
||||
# - Store object state in databases/caches
|
||||
# - Share objects across PowerShell sessions
|
||||
# - Clipboard operations with rich objects
|
||||
```
|
||||
|
||||
**Difference from Export/Import-Clixml:**
|
||||
- `Export-Clixml`: Writes to file
|
||||
- `ConvertTo-CliXml`: Returns string (no file I/O)
|
||||
|
||||
## Enhanced Test-Path Cmdlet
|
||||
|
||||
### -OlderThan and -NewerThan Parameters
|
||||
|
||||
Filter paths by modification time:
|
||||
|
||||
```powershell
|
||||
# Find files older than 30 days
|
||||
Test-Path "C:\Logs\*.log" -OlderThan (Get-Date).AddDays(-30)
|
||||
|
||||
# Find files newer than 1 hour
|
||||
Test-Path "C:\Temp\*" -NewerThan (Get-Date).AddHours(-1)
|
||||
|
||||
# Cleanup old log files
|
||||
Get-ChildItem "C:\Logs" -Filter "*.log" |
|
||||
Where-Object { Test-Path $_.FullName -OlderThan (Get-Date).AddDays(-90) } |
|
||||
Remove-Item -WhatIf
|
||||
|
||||
# Find recent downloads
|
||||
Get-ChildItem "C:\Users\*\Downloads" -Recurse |
|
||||
Where-Object { Test-Path $_.FullName -NewerThan (Get-Date).AddDays(-7) }
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Log rotation automation
|
||||
- Backup file cleanup
|
||||
- Recent file monitoring
|
||||
- Cache invalidation
|
||||
|
||||
## Enhanced Web Cmdlets
|
||||
|
||||
### -PassThru with -OutFile
|
||||
|
||||
Save response to file AND return content:
|
||||
|
||||
```powershell
|
||||
# Before PowerShell 7.5 (choose one):
|
||||
Invoke-WebRequest -Uri $url -OutFile "download.zip" # Save only
|
||||
$response = Invoke-WebRequest -Uri $url # Return only
|
||||
|
||||
# PowerShell 7.5 (both):
|
||||
$response = Invoke-WebRequest -Uri $url -OutFile "download.zip" -PassThru
|
||||
$response.StatusCode # 200
|
||||
# File also saved to download.zip
|
||||
|
||||
# Download and verify
|
||||
$result = Invoke-RestMethod -Uri "https://api.example.com/data.json" `
|
||||
-OutFile "data.json" `
|
||||
-PassThru
|
||||
|
||||
Write-Host "Downloaded $($result.Length) bytes"
|
||||
# File saved to data.json
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Download progress tracking
|
||||
- HTTP header inspection
|
||||
- Status code verification
|
||||
- Combined file save + content processing
|
||||
|
||||
## Enhanced Test-Json Cmdlet
|
||||
|
||||
### IgnoreComments and AllowTrailingCommas
|
||||
|
||||
Parse relaxed JSON formats:
|
||||
|
||||
```powershell
|
||||
# JSON with comments (previously invalid)
|
||||
$jsonWithComments = @"
|
||||
{
|
||||
// This is a comment
|
||||
"name": "example", // inline comment
|
||||
/* Multi-line
|
||||
comment */
|
||||
"version": "1.0"
|
||||
}
|
||||
"@
|
||||
|
||||
# PowerShell 7.5 - Parse with comments
|
||||
$obj = $jsonWithComments | ConvertFrom-Json -IgnoreComments
|
||||
$obj.name # Outputs: example
|
||||
|
||||
# JSON with trailing commas (previously invalid)
|
||||
$jsonTrailing = @"
|
||||
{
|
||||
"items": [
|
||||
"first",
|
||||
"second", // trailing comma
|
||||
],
|
||||
}
|
||||
"@
|
||||
|
||||
# PowerShell 7.5 - Parse with trailing commas
|
||||
$obj = $jsonTrailing | ConvertFrom-Json -AllowTrailingCommas
|
||||
|
||||
# Validate JSON with relaxed syntax
|
||||
Test-Json -Json $jsonWithComments -IgnoreComments
|
||||
Test-Json -Json $jsonTrailing -AllowTrailingCommas
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Parse configuration files with comments
|
||||
- Handle JSON from JavaScript tools
|
||||
- Accept relaxed JSON from APIs
|
||||
- Config file validation
|
||||
|
||||
## Enhanced Resolve-Path and Convert-Path
|
||||
|
||||
### -Force Parameter for Wildcard Hidden Files
|
||||
|
||||
Access hidden/system files with wildcards:
|
||||
|
||||
```powershell
|
||||
# PowerShell 7.4 and earlier - Hidden files not matched
|
||||
Resolve-Path "C:\Users\*\.*" | Select-Object -First 5
|
||||
# Skips .vscode, .gitignore, etc.
|
||||
|
||||
# PowerShell 7.5 - Include hidden files
|
||||
Resolve-Path "C:\Users\*\.*" -Force | Select-Object -First 5
|
||||
# Includes .vscode, .gitignore, .bashrc, etc.
|
||||
|
||||
# Find all hidden config files
|
||||
Resolve-Path "C:\Projects\*\.*" -Force |
|
||||
Where-Object { (Get-Item $_).Attributes -match "Hidden" }
|
||||
|
||||
# Convert-Path also supports -Force
|
||||
Convert-Path "~/.config/*" -Force
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Backup scripts including hidden files
|
||||
- Configuration discovery
|
||||
- Security audits
|
||||
- Development environment setup
|
||||
|
||||
## New-FileCatalog Version 2 Default
|
||||
|
||||
FileCatalog version 2 is now default:
|
||||
|
||||
```powershell
|
||||
# PowerShell 7.5 - Version 2 by default
|
||||
New-FileCatalog -Path "C:\Project" -CatalogFilePath "catalog.cat"
|
||||
# Creates version 2 catalog (SHA256)
|
||||
|
||||
# Explicitly specify version
|
||||
New-FileCatalog -Path "C:\Project" `
|
||||
-CatalogFilePath "catalog.cat" `
|
||||
-CatalogVersion 2
|
||||
|
||||
# Test file integrity
|
||||
Test-FileCatalog -Path "C:\Project" -CatalogFilePath "catalog.cat"
|
||||
```
|
||||
|
||||
**Version Differences:**
|
||||
- Version 1: SHA1 hashing (legacy)
|
||||
- Version 2: SHA256 hashing (default, more secure)
|
||||
|
||||
## .NET 9 Performance Enhancements
|
||||
|
||||
### Significant Performance Improvements
|
||||
|
||||
```powershell
|
||||
# PowerShell 7.5 benefits from .NET 9.0.306:
|
||||
# - Faster startup time
|
||||
# - Reduced memory consumption
|
||||
# - Improved JIT compilation
|
||||
# - Better garbage collection
|
||||
|
||||
# Example: Large dataset processing
|
||||
Measure-Command {
|
||||
1..1000000 | ForEach-Object { $_ * 2 }
|
||||
}
|
||||
# PowerShell 7.4: ~2.5 seconds
|
||||
# PowerShell 7.5: ~1.8 seconds (28% faster)
|
||||
```
|
||||
|
||||
### Memory Efficiency
|
||||
|
||||
```powershell
|
||||
# Lower memory footprint for:
|
||||
# - Large collections
|
||||
# - Long-running scripts
|
||||
# - Concurrent operations
|
||||
# - Pipeline processing
|
||||
|
||||
# Monitor memory usage
|
||||
[System.GC]::GetTotalMemory($false) / 1MB
|
||||
# PowerShell 7.5 uses 15-20% less memory on average
|
||||
```
|
||||
|
||||
## PSResourceGet 1.1.1 (March 2025)
|
||||
|
||||
### Modern Package Management
|
||||
|
||||
PSResourceGet is the official successor to PowerShellGet, offering significant performance improvements and enhanced security.
|
||||
|
||||
**Key Features:**
|
||||
- **2x faster** module installation
|
||||
- **Improved security** - SecretManagement integration for secure credential storage
|
||||
- **Azure Artifacts support** - Enterprise private feed integration
|
||||
- **Better error handling** - Clearer error messages and retry logic
|
||||
|
||||
```powershell
|
||||
# Install PSResourceGet (included in PowerShell 7.4+)
|
||||
Install-Module -Name Microsoft.PowerShell.PSResourceGet -Force
|
||||
|
||||
# New commands
|
||||
Install-PSResource -Name Az -Scope CurrentUser # 2x faster than Install-Module
|
||||
Find-PSResource -Name "*Azure*" # Replaces Find-Module
|
||||
Update-PSResource -Name Az # Replaces Update-Module
|
||||
Get-InstalledPSResource # Replaces Get-InstalledModule
|
||||
|
||||
# Security best practice - use SecretManagement for credentials
|
||||
Register-PSResourceRepository -Name "PrivateFeed" `
|
||||
-Uri "https://pkgs.dev.azure.com/org/project/_packaging/feed/nuget/v3/index.json" `
|
||||
-Trusted
|
||||
|
||||
# Retrieve credential from SecretManagement vault
|
||||
$credential = Get-Secret -Name "AzureArtifactsToken" -AsPlainText
|
||||
Install-PSResource -Name "MyPrivateModule" -Repository "PrivateFeed" -Credential $credential
|
||||
```
|
||||
|
||||
**Performance Comparison:**
|
||||
| Operation | PowerShellGet | PSResourceGet 1.1.1 | Improvement |
|
||||
|-----------|--------------|---------------------|-------------|
|
||||
| Install module | 10-15s | 5-7s | 2x faster |
|
||||
| Search modules | 3-5s | 1-2s | 2-3x faster |
|
||||
| Update module | 12-18s | 6-9s | 2x faster |
|
||||
|
||||
**Security Enhancements:**
|
||||
- Never use plaintext credentials in scripts
|
||||
- Use SecretManagement module for storing repository credentials
|
||||
- Support for Azure DevOps Personal Access Tokens (PAT)
|
||||
- Integrated authentication with Azure Artifacts
|
||||
|
||||
```powershell
|
||||
# WRONG - plaintext credential
|
||||
$cred = New-Object PSCredential("user", (ConvertTo-SecureString "password" -AsPlainText -Force))
|
||||
|
||||
# CORRECT - SecretManagement
|
||||
Install-Module Microsoft.PowerShell.SecretManagement
|
||||
Register-SecretVault -Name LocalVault -ModuleName Microsoft.PowerShell.SecretStore
|
||||
Set-Secret -Name "RepoToken" -Secret "your-token"
|
||||
|
||||
$token = Get-Secret -Name "RepoToken" -AsPlainText
|
||||
Install-PSResource -Name "Module" -Repository "Feed" -Credential $token
|
||||
```
|
||||
|
||||
## Migration from PowerShell 7.4
|
||||
|
||||
### Check Version
|
||||
|
||||
```powershell
|
||||
# Current version
|
||||
$PSVersionTable.PSVersion
|
||||
# 7.5.4 (latest stable as of October 2025)
|
||||
|
||||
# .NET version
|
||||
[System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription
|
||||
# .NET 9.0.306
|
||||
|
||||
# PSResourceGet version
|
||||
Get-Module Microsoft.PowerShell.PSResourceGet -ListAvailable
|
||||
# Version 1.1.1 (latest as of March 2025)
|
||||
```
|
||||
|
||||
### Update Scripts for 7.5
|
||||
|
||||
```powershell
|
||||
# Replace file-based XML serialization
|
||||
# Before:
|
||||
$data | Export-Clixml -Path "temp.xml"
|
||||
$xml = Get-Content "temp.xml" -Raw
|
||||
Remove-Item "temp.xml"
|
||||
|
||||
# After:
|
||||
$xml = $data | ConvertTo-CliXml
|
||||
|
||||
# Use new Test-Path filtering
|
||||
# Before:
|
||||
Get-ChildItem | Where-Object {
|
||||
$_.LastWriteTime -lt (Get-Date).AddDays(-30)
|
||||
}
|
||||
|
||||
# After:
|
||||
Get-ChildItem | Where-Object {
|
||||
Test-Path $_.FullName -OlderThan (Get-Date).AddDays(-30)
|
||||
}
|
||||
|
||||
# Leverage -PassThru for downloads
|
||||
# Before:
|
||||
Invoke-WebRequest -Uri $url -OutFile "file.zip"
|
||||
$size = (Get-Item "file.zip").Length
|
||||
|
||||
# After:
|
||||
$response = Invoke-WebRequest -Uri $url -OutFile "file.zip" -PassThru
|
||||
$size = $response.RawContentLength
|
||||
```
|
||||
|
||||
## Best Practices for PowerShell 7.5
|
||||
|
||||
1. **Use ConvertTo/From-CliXml for in-memory serialization:**
|
||||
```powershell
|
||||
# Serialize to clipboard
|
||||
$data | ConvertTo-CliXml | Set-Clipboard
|
||||
|
||||
# Deserialize from clipboard
|
||||
$restored = Get-Clipboard | ConvertFrom-CliXml
|
||||
```
|
||||
|
||||
2. **Leverage Test-Path time filtering:**
|
||||
```powershell
|
||||
# Clean old logs
|
||||
Get-ChildItem "C:\Logs" | Where-Object {
|
||||
Test-Path $_.FullName -OlderThan (Get-Date).AddDays(-90)
|
||||
} | Remove-Item
|
||||
```
|
||||
|
||||
3. **Use -Force for hidden file operations:**
|
||||
```powershell
|
||||
# Backup including hidden config files
|
||||
Resolve-Path "~/*" -Force |
|
||||
Where-Object { Test-Path $_ -OlderThan (Get-Date).AddDays(-1) } |
|
||||
Copy-Item -Destination "C:\Backup\"
|
||||
```
|
||||
|
||||
4. **Simplify download workflows:**
|
||||
```powershell
|
||||
# Download and verify in one step
|
||||
$response = Invoke-WebRequest $url -OutFile "data.zip" -PassThru
|
||||
if ($response.StatusCode -eq 200) {
|
||||
Expand-Archive "data.zip" -Destination "data/"
|
||||
}
|
||||
```
|
||||
|
||||
5. **Parse relaxed JSON:**
|
||||
```powershell
|
||||
# Configuration files with comments
|
||||
$config = Get-Content "config.jsonc" -Raw |
|
||||
ConvertFrom-Json -IgnoreComments
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# GitHub Actions with PowerShell 7.5
|
||||
- name: Setup PowerShell 7.5
|
||||
uses: actions/setup-powershell@v1
|
||||
with:
|
||||
pwsh-version: '7.5.x'
|
||||
|
||||
- name: Run Script with 7.5 Features
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Use ConvertTo-CliXml for artifact storage
|
||||
$results = ./Invoke-Tests.ps1
|
||||
$results | ConvertTo-CliXml | Out-File "results.xml"
|
||||
|
||||
# Download dependencies with -PassThru
|
||||
$response = Invoke-WebRequest $depUrl -OutFile "deps.zip" -PassThru
|
||||
Write-Host "Downloaded $($response.RawContentLength) bytes"
|
||||
```
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
PowerShell 7.5 maintains compatibility with 7.x scripts:
|
||||
- All 7.0-7.4 scripts work unchanged
|
||||
- New parameters are opt-in
|
||||
- No breaking changes to existing cmdlets
|
||||
- Module compatibility preserved
|
||||
|
||||
## Performance Benchmarks
|
||||
|
||||
| Operation | PowerShell 7.4 | PowerShell 7.5 | Improvement |
|
||||
|-----------|---------------|---------------|-------------|
|
||||
| Startup time | 1.2s | 0.9s | 25% faster |
|
||||
| Large pipeline | 2.5s | 1.8s | 28% faster |
|
||||
| Memory usage | 120MB | 95MB | 21% lower |
|
||||
| Web requests | 450ms | 380ms | 16% faster |
|
||||
|
||||
## Resources
|
||||
|
||||
- [PowerShell 7.5 Release Notes](https://github.com/PowerShell/PowerShell/releases)
|
||||
- [.NET 9 Performance](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9)
|
||||
- [PowerShell Team Blog](https://devblogs.microsoft.com/powershell)
|
||||
1248
skills/powershell-master/SKILL.md
Normal file
1248
skills/powershell-master/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
408
skills/powershell-security.md
Normal file
408
skills/powershell-security.md
Normal file
@@ -0,0 +1,408 @@
|
||||
---
|
||||
name: powershell-security
|
||||
description: Modern PowerShell security practices including SecretManagement, JEA, WDAC, and credential protection
|
||||
---
|
||||
|
||||
# PowerShell Security Best Practices (2025)
|
||||
|
||||
Modern security practices for PowerShell scripts and automation, including credential management, SecretManagement module, and hardening techniques.
|
||||
|
||||
## SecretManagement Module (Recommended 2025 Standard)
|
||||
|
||||
### Overview
|
||||
|
||||
**Microsoft.PowerShell.SecretManagement** is the official solution for secure credential storage in PowerShell.
|
||||
|
||||
**Why use SecretManagement:**
|
||||
- Never store plaintext credentials in scripts
|
||||
- Cross-platform secret storage
|
||||
- Multiple vault provider support
|
||||
- Integration with Azure Key Vault, 1Password, KeePass, etc.
|
||||
|
||||
### Installation
|
||||
|
||||
```powershell
|
||||
# Install SecretManagement module
|
||||
Install-Module -Name Microsoft.PowerShell.SecretManagement -Scope CurrentUser
|
||||
|
||||
# Install vault provider (choose one or more)
|
||||
Install-Module -Name Microsoft.PowerShell.SecretStore # Local encrypted vault
|
||||
Install-Module -Name Az.KeyVault # Azure Key Vault
|
||||
Install-Module -Name SecretManagement.KeePass # KeePass integration
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```powershell
|
||||
# Register a vault
|
||||
Register-SecretVault -Name LocalVault -ModuleName Microsoft.PowerShell.SecretStore
|
||||
|
||||
# Store a secret
|
||||
$password = Read-Host -AsSecureString -Prompt "Enter password"
|
||||
Set-Secret -Name "DatabasePassword" -Secret $password -Vault LocalVault
|
||||
|
||||
# Retrieve a secret
|
||||
$dbPassword = Get-Secret -Name "DatabasePassword" -Vault LocalVault -AsPlainText
|
||||
# Or as SecureString
|
||||
$dbPasswordSecure = Get-Secret -Name "DatabasePassword" -Vault LocalVault
|
||||
|
||||
# List secrets
|
||||
Get-SecretInfo
|
||||
|
||||
# Remove a secret
|
||||
Remove-Secret -Name "DatabasePassword" -Vault LocalVault
|
||||
```
|
||||
|
||||
### Azure Key Vault Integration
|
||||
|
||||
```powershell
|
||||
# Install and import Az.KeyVault
|
||||
Install-Module -Name Az.KeyVault -Scope CurrentUser
|
||||
Import-Module Az.KeyVault
|
||||
|
||||
# Authenticate to Azure
|
||||
Connect-AzAccount
|
||||
|
||||
# Register Azure Key Vault as secret vault
|
||||
Register-SecretVault -Name AzureKV `
|
||||
-ModuleName Az.KeyVault `
|
||||
-VaultParameters @{
|
||||
AZKVaultName = 'MyKeyVault'
|
||||
SubscriptionId = 'your-subscription-id'
|
||||
}
|
||||
|
||||
# Store secret in Azure Key Vault
|
||||
Set-Secret -Name "ApiKey" -Secret "your-api-key" -Vault AzureKV
|
||||
|
||||
# Retrieve from Azure Key Vault
|
||||
$apiKey = Get-Secret -Name "ApiKey" -Vault AzureKV -AsPlainText
|
||||
```
|
||||
|
||||
### Automation Scripts with SecretManagement
|
||||
|
||||
```powershell
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Secure automation script using SecretManagement
|
||||
|
||||
.DESCRIPTION
|
||||
Demonstrates secure credential handling without hardcoded secrets
|
||||
#>
|
||||
|
||||
#Requires -Modules Microsoft.PowerShell.SecretManagement
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
# Retrieve credentials from vault
|
||||
$dbConnectionString = Get-Secret -Name "SQLConnectionString" -AsPlainText
|
||||
$apiToken = Get-Secret -Name "APIToken" -AsPlainText
|
||||
|
||||
# Use credentials securely
|
||||
try {
|
||||
# Database operation
|
||||
$connection = New-Object System.Data.SqlClient.SqlConnection($dbConnectionString)
|
||||
$connection.Open()
|
||||
|
||||
# API call with token
|
||||
$headers = @{ Authorization = "Bearer $apiToken" }
|
||||
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
|
||||
|
||||
# Process results
|
||||
Write-Host "Operation completed successfully"
|
||||
}
|
||||
catch {
|
||||
Write-Error "Operation failed: $_"
|
||||
}
|
||||
finally {
|
||||
if ($connection) { $connection.Close() }
|
||||
}
|
||||
```
|
||||
|
||||
## Credential Management Best Practices
|
||||
|
||||
### Never Hardcode Credentials
|
||||
|
||||
```powershell
|
||||
# ❌ WRONG - Hardcoded credentials
|
||||
$password = "MyPassword123"
|
||||
$username = "admin"
|
||||
|
||||
# ❌ WRONG - Plaintext in script
|
||||
$cred = New-Object System.Management.Automation.PSCredential("admin", "password")
|
||||
|
||||
# ✅ CORRECT - SecretManagement
|
||||
$password = Get-Secret -Name "AdminPassword" -AsPlainText
|
||||
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
|
||||
$cred = New-Object System.Management.Automation.PSCredential("admin", $securePassword)
|
||||
|
||||
# ✅ CORRECT - Interactive prompt (for manual runs)
|
||||
$cred = Get-Credential -Message "Enter admin credentials"
|
||||
|
||||
# ✅ CORRECT - Managed Identity (Azure automation)
|
||||
Connect-AzAccount -Identity
|
||||
```
|
||||
|
||||
### Service Principal Authentication (Azure)
|
||||
|
||||
```powershell
|
||||
# Store service principal credentials in vault
|
||||
Set-Secret -Name "AzureAppId" -Secret "app-id-guid"
|
||||
Set-Secret -Name "AzureAppSecret" -Secret "app-secret-value"
|
||||
Set-Secret -Name "AzureTenantId" -Secret "tenant-id-guid"
|
||||
|
||||
# Retrieve and authenticate
|
||||
$appId = Get-Secret -Name "AzureAppId" -AsPlainText
|
||||
$appSecret = Get-Secret -Name "AzureAppSecret" -AsPlainText
|
||||
$tenantId = Get-Secret -Name "AzureTenantId" -AsPlainText
|
||||
|
||||
$secureSecret = ConvertTo-SecureString $appSecret -AsPlainText -Force
|
||||
$credential = New-Object System.Management.Automation.PSCredential($appId, $secureSecret)
|
||||
|
||||
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId
|
||||
```
|
||||
|
||||
## Just Enough Administration (JEA)
|
||||
|
||||
### What is JEA?
|
||||
|
||||
**Just Enough Administration** restricts PowerShell remoting sessions to specific cmdlets and parameters.
|
||||
|
||||
### Use Cases
|
||||
|
||||
- Delegate admin tasks without full admin rights
|
||||
- Compliance requirements (SOC 2, HIPAA, PCI-DSS)
|
||||
- Production environment hardening
|
||||
- Audit trail for privileged operations
|
||||
|
||||
### Creating a JEA Endpoint
|
||||
|
||||
```powershell
|
||||
# 1. Create role capability file
|
||||
New-PSRoleCapabilityFile -Path "C:\JEA\RestartServices.psrc" `
|
||||
-VisibleCmdlets @{
|
||||
Name = 'Restart-Service'
|
||||
Parameters = @{
|
||||
Name = 'Name'
|
||||
ValidateSet = 'Spooler', 'W32Time', 'WinRM'
|
||||
}
|
||||
}, 'Get-Service'
|
||||
|
||||
# 2. Create session configuration file
|
||||
New-PSSessionConfigurationFile -Path "C:\JEA\RestartServices.pssc" `
|
||||
-SessionType RestrictedRemoteServer `
|
||||
-RoleDefinitions @{
|
||||
'DOMAIN\ServiceAdmins' = @{ RoleCapabilities = 'RestartServices' }
|
||||
} `
|
||||
-LanguageMode NoLanguage
|
||||
|
||||
# 3. Register JEA endpoint
|
||||
Register-PSSessionConfiguration -Name RestartServices `
|
||||
-Path "C:\JEA\RestartServices.pssc" `
|
||||
-Force
|
||||
|
||||
# 4. Connect to JEA endpoint (as delegated user)
|
||||
Enter-PSSession -ComputerName Server01 -ConfigurationName RestartServices
|
||||
|
||||
# User can ONLY run allowed commands
|
||||
Restart-Service -Name Spooler # ✅ Allowed
|
||||
Restart-Service -Name DNS # ❌ Denied (not in ValidateSet)
|
||||
Get-Process # ❌ Denied (not visible)
|
||||
```
|
||||
|
||||
### JEA Audit Logging
|
||||
|
||||
```powershell
|
||||
# Enable transcription and logging
|
||||
New-PSSessionConfigurationFile -Path "C:\JEA\AuditedSession.pssc" `
|
||||
-SessionType RestrictedRemoteServer `
|
||||
-TranscriptDirectory "C:\JEA\Transcripts" `
|
||||
-RunAsVirtualAccount
|
||||
|
||||
# All JEA sessions are transcribed to C:\JEA\Transcripts
|
||||
# Review audit logs
|
||||
Get-ChildItem "C:\JEA\Transcripts" | Get-Content
|
||||
```
|
||||
|
||||
## Windows Defender Application Control (WDAC)
|
||||
|
||||
### PowerShell Script Control
|
||||
|
||||
**WDAC** replaces AppLocker for controlling which PowerShell scripts can execute.
|
||||
|
||||
```powershell
|
||||
# Create WDAC policy for signed scripts only
|
||||
New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-ScanPath "C:\Scripts" `
|
||||
-Level FilePublisher `
|
||||
-Fallback Hash `
|
||||
-UserPEs
|
||||
|
||||
# Allow only signed scripts
|
||||
Set-RuleOption -FilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-Option 3 # Required WHQL
|
||||
|
||||
# Convert to binary policy
|
||||
ConvertFrom-CIPolicy -XmlFilePath "C:\WDAC\PowerShellPolicy.xml" `
|
||||
-BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b"
|
||||
|
||||
# Reboot to apply policy
|
||||
Restart-Computer
|
||||
```
|
||||
|
||||
## Code Signing
|
||||
|
||||
### Why Sign Scripts?
|
||||
|
||||
- Verify script integrity
|
||||
- Meet organizational security policies
|
||||
- Enable WDAC enforcement
|
||||
- Prevent tampering
|
||||
|
||||
### Signing a Script
|
||||
|
||||
```powershell
|
||||
# Get code signing certificate
|
||||
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
|
||||
|
||||
# Sign script
|
||||
Set-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1" -Certificate $cert
|
||||
|
||||
# Verify signature
|
||||
$signature = Get-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1"
|
||||
$signature.Status # Should be "Valid"
|
||||
```
|
||||
|
||||
### Execution Policy
|
||||
|
||||
```powershell
|
||||
# Check current execution policy
|
||||
Get-ExecutionPolicy
|
||||
|
||||
# Set execution policy (requires admin)
|
||||
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
|
||||
|
||||
# Bypass for single script (testing only)
|
||||
PowerShell.exe -ExecutionPolicy Bypass -File "script.ps1"
|
||||
```
|
||||
|
||||
## Constrained Language Mode
|
||||
|
||||
### What is Constrained Language Mode?
|
||||
|
||||
Restricts PowerShell language features to prevent malicious code execution.
|
||||
|
||||
```powershell
|
||||
# Check current language mode
|
||||
$ExecutionContext.SessionState.LanguageMode
|
||||
# Output: FullLanguage (admin) or ConstrainedLanguage (standard user)
|
||||
|
||||
# Set system-wide constrained language mode
|
||||
# Via Environment Variable or Group Policy
|
||||
# Set: __PSLockdownPolicy = 4
|
||||
|
||||
# Test constrained mode behavior
|
||||
# FullLanguage allows:
|
||||
[System.Net.WebClient]::new() # ✅ Allowed
|
||||
|
||||
# ConstrainedLanguage blocks:
|
||||
[System.Net.WebClient]::new() # ❌ Blocked
|
||||
Add-Type -TypeDefinition "..." # ❌ Blocked
|
||||
```
|
||||
|
||||
## Script Block Logging
|
||||
|
||||
### Enable Logging
|
||||
|
||||
```powershell
|
||||
# Enable via Group Policy or Registry
|
||||
# HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
|
||||
New-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" `
|
||||
-Name "EnableScriptBlockLogging" -Value 1 -PropertyType DWord
|
||||
|
||||
# Log location: Windows Event Log
|
||||
# Event Viewer > Applications and Services Logs > Microsoft > Windows > PowerShell > Operational
|
||||
```
|
||||
|
||||
### Review Logs
|
||||
|
||||
```powershell
|
||||
# Query script block logs
|
||||
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
|
||||
Where-Object { $_.Id -eq 4104 } | # Script Block Logging event
|
||||
Select-Object TimeCreated, Message |
|
||||
Out-GridView
|
||||
```
|
||||
|
||||
## Input Validation
|
||||
|
||||
### Prevent Injection Attacks
|
||||
|
||||
```powershell
|
||||
# ❌ WRONG - No validation
|
||||
function Get-UserData {
|
||||
param($Username)
|
||||
Invoke-Sqlcmd -Query "SELECT * FROM Users WHERE Username = '$Username'"
|
||||
}
|
||||
# Vulnerable to SQL injection
|
||||
|
||||
# ✅ CORRECT - Parameterized queries
|
||||
function Get-UserData {
|
||||
param(
|
||||
[ValidatePattern('^[a-zA-Z0-9_-]+$')]
|
||||
[string]$Username
|
||||
)
|
||||
Invoke-Sqlcmd -Query "SELECT * FROM Users WHERE Username = @Username" `
|
||||
-Variable @{Username=$Username}
|
||||
}
|
||||
|
||||
# ✅ CORRECT - ValidateSet for known values
|
||||
function Restart-AppService {
|
||||
param(
|
||||
[ValidateSet('Web', 'API', 'Worker')]
|
||||
[string]$ServiceName
|
||||
)
|
||||
Restart-Service -Name "App${ServiceName}Service"
|
||||
}
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### Script Development
|
||||
|
||||
- [ ] Never hardcode credentials (use SecretManagement)
|
||||
- [ ] Use parameterized queries for SQL operations
|
||||
- [ ] Validate all user input with `[ValidatePattern]`, `[ValidateSet]`, etc.
|
||||
- [ ] Enable `Set-StrictMode -Version Latest`
|
||||
- [ ] Use `try/catch` for error handling
|
||||
- [ ] Avoid `Invoke-Expression` with user input
|
||||
- [ ] Sign production scripts
|
||||
- [ ] Enable Script Block Logging
|
||||
|
||||
### Automation
|
||||
|
||||
- [ ] Use Managed Identity or Service Principal (never passwords)
|
||||
- [ ] Store secrets in SecretManagement or Azure Key Vault
|
||||
- [ ] Implement JEA for delegated admin tasks
|
||||
- [ ] Enable audit logging for all privileged operations
|
||||
- [ ] Use least privilege principle
|
||||
- [ ] Rotate credentials regularly
|
||||
- [ ] Monitor failed authentication attempts
|
||||
|
||||
### Production Environments
|
||||
|
||||
- [ ] Implement WDAC policies for script control
|
||||
- [ ] Use Constrained Language Mode for non-admin users
|
||||
- [ ] Enable PowerShell logging (Script Block + Transcription)
|
||||
- [ ] Require signed scripts (via execution policy)
|
||||
- [ ] Regular security audits
|
||||
- [ ] Keep PowerShell updated (7.5+)
|
||||
- [ ] Use JEA for remote administration
|
||||
|
||||
## Resources
|
||||
|
||||
- [SecretManagement Documentation](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretmanagement)
|
||||
- [JEA Documentation](https://learn.microsoft.com/en-us/powershell/scripting/security/remoting/jea/overview)
|
||||
- [WDAC Documentation](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control)
|
||||
- [PowerShell Security Best Practices](https://learn.microsoft.com/en-us/powershell/scripting/security/securing-powershell)
|
||||
- [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/)
|
||||
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