PowerShell Master
π¨ CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (
) in file paths, NOT forward slashes (/
).
Examples:
-
β WRONG: D:/repos/project/file.tsx
-
β CORRECT: D:\repos\project\file.tsx
This applies to:
-
Edit tool file_path parameter
-
Write tool file_path parameter
-
All file operations on Windows systems
Documentation Guidelines
NEVER create new documentation files unless explicitly requested by the user.
-
Priority: Update existing README.md files rather than creating new documentation
-
Repository cleanliness: Keep repository root clean - only README.md unless user requests otherwise
-
Style: Documentation should be concise, direct, and professional - avoid AI-generated tone
-
User preference: Only create additional .md files when user specifically asks for documentation
Complete PowerShell expertise across all platforms for scripting, automation, CI/CD, and cloud management.
π― When to Activate
PROACTIVELY activate for ANY PowerShell-related task:
-
β PowerShell Scripts - Creating, reviewing, optimizing any .ps1 file
-
β Cmdlets & Modules - Finding, installing, using any PowerShell modules
-
β Cross-Platform - Windows, Linux, macOS PowerShell tasks
-
β CI/CD Integration - GitHub Actions, Azure DevOps, Bitbucket Pipelines
-
β Cloud Automation - Azure (Az), AWS, Microsoft 365 (Microsoft.Graph)
-
β Module Management - PSGallery search, installation, updates
-
β Script Debugging - Troubleshooting, performance, security
-
β Best Practices - Code quality, standards, production-ready scripts
π PowerShell Overview
PowerShell Versions & Platforms
PowerShell 7+ (Recommended)
-
Cross-platform: Windows, Linux, macOS
-
Open source, actively developed
-
Better performance than PowerShell 5.1
-
UTF-8 by default
-
Parallel execution support
-
Ternary operators, null-coalescing
Windows PowerShell 5.1 (Legacy)
-
Windows-only
-
Ships with Windows
-
UTF-16LE default encoding
-
Required for some Windows-specific modules
Installation Locations:
-
Windows: C:\Program Files\PowerShell\7
(PS7) or C:\Windows\System32\WindowsPowerShell\v1.0
(5.1) -
Linux: /opt/microsoft/powershell/7/ or /usr/bin/pwsh
-
macOS: /usr/local/microsoft/powershell/7/ or /usr/local/bin/pwsh
π§ Cross-Platform Best Practices
- Path Handling
DO:
Use Join-Path for cross-platform paths
$configPath = Join-Path -Path $PSScriptRoot -ChildPath "config.json"
Use [System.IO.Path] for path manipulation
$fullPath = [System.IO.Path]::Combine($home, "documents", "file.txt")
Forward slashes work on all platforms in PowerShell 7+
$path = "$PSScriptRoot/subfolder/file.txt"
DON'T:
Hardcoded backslashes (Windows-only)
$path = "C:\Users\Name\file.txt"
Assume case-insensitive file systems
Get-ChildItem "MyFile.txt" # Works on Windows, fails on Linux/macOS if casing is wrong
- Platform Detection
Use automatic variables
if ($IsWindows) { # Windows-specific code $env:Path -split ';' } elseif ($IsLinux) { # Linux-specific code $env:PATH -split ':' } elseif ($IsMacOS) { # macOS-specific code $env:PATH -split ':' }
Check PowerShell version
if ($PSVersionTable.PSVersion.Major -ge 7) { # PowerShell 7+ features }
- Avoid Aliases in Scripts
DON'T use aliases (they may differ across platforms)
ls | ? {$.Length -gt 1MB} | % {$.Name}
DO use full cmdlet names
Get-ChildItem | Where-Object {$.Length -gt 1MB} | ForEach-Object {$.Name}
Why: On Linux/macOS, aliases might invoke native commands instead of PowerShell cmdlets, causing unexpected results.
- Text Encoding
PowerShell 7+ uses UTF-8 by default
"Hello" | Out-File -FilePath output.txt
For PowerShell 5.1 compatibility, specify encoding
"Hello" | Out-File -FilePath output.txt -Encoding UTF8
Best practice: Always specify encoding for cross-platform scripts
$content | Set-Content -Path $file -Encoding UTF8NoBOM
- Environment Variables (Cross-Platform)
BEST PRACTICE: Use .NET Environment class for cross-platform compatibility
[Environment]::UserName # Works on all platforms [Environment]::MachineName # Works on all platforms [IO.Path]::GetTempPath() # Works on all platforms
AVOID: These are platform-specific
$env:USERNAME # Windows only $env:USER # Linux/macOS only
Environment variable names are CASE-SENSITIVE on Linux/macOS
$env:PATH # Correct on Linux/macOS $env:Path # May not work on Linux/macOS
- Shell Detection (Windows: PowerShell vs Git Bash)
CRITICAL: On Windows, distinguish between PowerShell and Git Bash/MSYS2 environments:
PowerShell detection (most reliable)
if ($env:PSModulePath -and ($env:PSModulePath -split ';').Count -ge 3) { Write-Host "Running in PowerShell" }
Platform-specific automatic variables (PowerShell 7+)
if ($IsWindows) { # Windows-specific code } elseif ($IsLinux) { # Linux-specific code } elseif ($IsMacOS) { # macOS-specific code }
Git Bash/MSYS2 Detection:
Bash detection - check MSYSTEM environment variable
if [ -n "$MSYSTEM" ]; then echo "Running in Git Bash/MSYS2: $MSYSTEM" # MSYSTEM values: MINGW64, MINGW32, MSYS fi
When to Use Each Shell:
-
PowerShell: Windows automation, Azure/M365, PSGallery modules, object pipelines
-
Git Bash: Git operations, Unix tools (sed/awk/grep), POSIX scripts, text processing
Path Handling Differences:
-
PowerShell: C:\Users\John or C:/Users/John (both work in PS 7+)
-
Git Bash: /c/Users/John (Unix-style, auto-converts to Windows when calling Windows tools)
See powershell-shell-detection skill for comprehensive cross-shell guidance.
- Line Endings
PowerShell handles line endings automatically
But be explicit for git or cross-platform tools
git config core.autocrlf input # Linux/macOS git config core.autocrlf true # Windows
π¦ Module Management (PSResourceGet & PSGallery)
PSResourceGet - Modern Package Manager (2025)
PSResourceGet is 2x faster than PowerShellGet and actively maintained:
PSResourceGet ships with PowerShell 7.4+ (or install manually)
Install-Module -Name Microsoft.PowerShell.PSResourceGet -Force
Modern commands (PSResourceGet)
Install-PSResource -Name Az -Scope CurrentUser # 2x faster Find-PSResource -Name "Azure" # Faster search Update-PSResource -Name Az # Batch updates Get-InstalledPSResource # List installed Uninstall-PSResource -Name OldModule # Clean uninstall
Compatibility: Your old Install-Module commands still work
They automatically call PSResourceGet internally
Install-Module -Name Az -Scope CurrentUser # Works, uses PSResourceGet
Finding Modules
PSResourceGet (Modern)
Find-PSResource -Name "Azure" Find-PSResource -Tag "Security" Find-PSResource -Name Az | Select-Object Name, Version, PublishedDate
Legacy PowerShellGet (still works)
Find-Module -Name "Azure" Find-Command -Name Get-AzVM
Installing Modules
RECOMMENDED: PSResourceGet (2x faster)
Install-PSResource -Name Az -Scope CurrentUser -TrustRepository Install-PSResource -Name Microsoft.Graph -Version 2.32.0
Legacy: PowerShellGet (slower, but still works)
Install-Module -Name Az -Scope CurrentUser -Force Install-Module -Name Pester -Scope AllUsers # Requires elevation
Managing Installed Modules
List installed (PSResourceGet)
Get-InstalledPSResource Get-InstalledPSResource -Name Az
Update modules (PSResourceGet)
Update-PSResource -Name Az Update-PSResource # Updates all
Uninstall (PSResourceGet)
Uninstall-PSResource -Name OldModule -AllVersions
Import module
Import-Module -Name Az.Accounts
Offline Installation
Save module (works with both)
Save-PSResource -Name Az -Path C:\OfflineModules
Or: Save-Module -Name Az -Path C:\OfflineModules
Install from saved location
Install-PSResource -Name Az -Path C:\OfflineModules
π Popular PowerShell Modules
Azure (Az Module 14.5.0)
Latest: Az 14.5.0 (October 2025) with zone redundancy and symbolic links
Install Azure module 14.5.0
Install-PSResource -Name Az -Scope CurrentUser
Or: Install-Module -Name Az -Scope CurrentUser -Force
Connect to Azure
Connect-AzAccount
Common operations
Get-AzVM Get-AzResourceGroup New-AzResourceGroup -Name "MyRG" -Location "EastUS"
NEW in Az 14.5: Zone redundancy for storage
New-AzStorageAccount -ResourceGroupName "MyRG" -Name "storage123" ` -Location "EastUS" -SkuName "Standard_LRS" -EnableZoneRedundancy
NEW in Az 14.5: Symbolic links in NFS File Share
New-AzStorageFileSymbolicLink -Context $ctx -ShareName "nfsshare" ` -Path "symlink" -Target "/target/path"
Key Submodules:
-
Az.Accounts
-
Authentication (MFA required Sep 2025+)
-
Az.Compute
-
VMs, scale sets
-
Az.Storage
-
Storage accounts (zone redundancy support)
-
Az.Network
-
Virtual networks, NSGs
-
Az.KeyVault
-
Key Vault operations
-
Az.Resources
-
Resource groups, deployments
Microsoft Graph (Microsoft.Graph 2.32.0)
CRITICAL: MSOnline and AzureAD modules retired (March-May 2025). Use Microsoft.Graph instead.
Install Microsoft Graph 2.32.0 (October 2025)
Install-PSResource -Name Microsoft.Graph -Scope CurrentUser
Or: Install-Module -Name Microsoft.Graph -Scope CurrentUser
Connect with required scopes
Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All"
Common operations
Get-MgUser Get-MgGroup New-MgUser -DisplayName "John Doe" -UserPrincipalName "john@domain.com" -MailNickname "john" Get-MgTeam
Migration from AzureAD/MSOnline
OLD: Connect-AzureAD / Connect-MsolService
NEW: Connect-MgGraph
OLD: Get-AzureADUser / Get-MsolUser
NEW: Get-MgUser
PnP PowerShell (SharePoint/Teams)
Install PnP PowerShell
Install-Module -Name PnP.PowerShell -Scope CurrentUser
Connect to SharePoint Online
Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/site" -Interactive
Common operations
Get-PnPList Get-PnPFile -Url "/sites/site/Shared Documents/file.docx" Add-PnPListItem -List "Tasks" -Values @{"Title"="New Task"}
AWS Tools for PowerShell
Install AWS Tools
Install-Module -Name AWS.Tools.Installer -Force Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3
Configure credentials
Set-AWSCredential -AccessKey $accessKey -SecretKey $secretKey -StoreAs default
Common operations
Get-EC2Instance Get-S3Bucket New-S3Bucket -BucketName "my-bucket"
Other Popular Modules
Pester (Testing framework)
Install-Module -Name Pester -Force
PSScriptAnalyzer (Code analysis)
Install-Module -Name PSScriptAnalyzer
ImportExcel (Excel manipulation without Excel)
Install-Module -Name ImportExcel
PowerShellGet 3.x (Modern package management)
Install-Module -Name Microsoft.PowerShell.PSResourceGet
π CI/CD Integration
GitHub Actions
name: PowerShell CI
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install PowerShell modules
shell: pwsh
run: |
Install-Module -Name Pester -Force -Scope CurrentUser
Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser
- name: Run Pester tests
shell: pwsh
run: |
Invoke-Pester -Path ./tests -OutputFormat NUnitXml -OutputFile TestResults.xml
- name: Run PSScriptAnalyzer
shell: pwsh
run: |
Invoke-ScriptAnalyzer -Path . -Recurse -ReportSummary
Multi-Platform Matrix:
jobs: test: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Test on ${{ matrix.os }} shell: pwsh run: | ./test-script.ps1
Azure DevOps Pipelines
trigger:
- main
pool: vmImage: 'ubuntu-latest'
steps:
-
task: PowerShell@2 inputs: targetType: 'inline' script: | Install-Module -Name Pester -Force -Scope CurrentUser Invoke-Pester -Path ./tests -OutputFormat NUnitXml displayName: 'Run Pester Tests'
-
task: PowerShell@2 inputs: filePath: '$(System.DefaultWorkingDirectory)/build.ps1' arguments: '-Configuration Release' displayName: 'Run Build Script'
-
task: PublishTestResults@2 inputs: testResultsFormat: 'NUnit' testResultsFiles: '**/TestResults.xml'
Cross-Platform Pipeline:
strategy: matrix: linux: imageName: 'ubuntu-latest' windows: imageName: 'windows-latest' mac: imageName: 'macos-latest'
pool: vmImage: $(imageName)
steps:
- pwsh: | Write-Host "Running on $($PSVersionTable.OS)" ./test-script.ps1 displayName: 'Cross-platform test'
Bitbucket Pipelines
image: mcr.microsoft.com/powershell:latest
pipelines: default: - step: name: Test with PowerShell script: - pwsh -Command "Install-Module -Name Pester -Force" - pwsh -Command "Invoke-Pester -Path ./tests"
- step:
name: Deploy
deployment: production
script:
- pwsh -File ./deploy.ps1
π» PowerShell Syntax & Cmdlets
Cmdlet Structure
Verb-Noun pattern
Get-ChildItem Set-Location New-Item Remove-Item
Common parameters (available on all cmdlets)
Get-Process -Verbose Set-Content -Path file.txt -WhatIf Remove-Item -Path folder -Confirm Invoke-RestMethod -Uri $url -ErrorAction Stop
Variables & Data Types
Variables (loosely typed)
$string = "Hello World" $number = 42 $array = @(1, 2, 3, 4, 5) $hashtable = @{Name="John"; Age=30}
Strongly typed
[string]$name = "John" [int]$age = 30 [datetime]$date = Get-Date
Special variables
$PSScriptRoot # Directory containing the script $PSCommandPath # Full path to the script $args # Script arguments $_ # Current pipeline object
Operators
Comparison operators
-eq # Equal -ne # Not equal -gt # Greater than -lt # Less than -match # Regex match -like # Wildcard match -contains # Array contains
Logical operators
-and -or -not
PowerShell 7+ ternary operator
$result = $condition ? "true" : "false"
Null-coalescing (PS 7+)
$value = $null ?? "default"
Control Flow
If-ElseIf-Else
if ($condition) { # Code } elseif ($otherCondition) { # Code } else { # Code }
Switch
switch ($value) { 1 { "One" } 2 { "Two" } {$_ -gt 10} { "Greater than 10" } default { "Other" } }
Loops
foreach ($item in $collection) { # Process item }
for ($i = 0; $i -lt 10; $i++) { # Loop code }
while ($condition) { # Loop code }
do { # Loop code } while ($condition)
Functions
function Get-Something { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Name,
[Parameter()]
[int]$Count = 1,
[Parameter(ValueFromPipeline=$true)]
[string[]]$InputObject
)
begin {
# Initialization
}
process {
# Process each pipeline object
foreach ($item in $InputObject) {
# Work with $item
}
}
end {
# Cleanup
return $result
}
}
Pipeline & Filtering
Pipeline basics
Get-Process | Where-Object {$_.CPU -gt 100} | Select-Object Name, CPU
Simplified syntax (PS 3.0+)
Get-Process | Where CPU -gt 100 | Select Name, CPU
ForEach-Object
Get-ChildItem | ForEach-Object { Write-Host $_.Name }
Simplified (PS 4.0+)
Get-ChildItem | % Name
Group, Sort, Measure
Get-Process | Group-Object ProcessName Get-Service | Sort-Object Status Get-ChildItem | Measure-Object -Property Length -Sum
Error Handling
Try-Catch-Finally
try { Get-Content -Path "nonexistent.txt" -ErrorAction Stop } catch [System.IO.FileNotFoundException] { Write-Error "File not found" } catch { Write-Error "An error occurred: $_" } finally { # Cleanup code }
Error action preference
$ErrorActionPreference = "Stop" # Treat all errors as terminating $ErrorActionPreference = "Continue" # Default $ErrorActionPreference = "SilentlyContinue" # Suppress errors
π Security Best Practices (2025 Standards)
Modern Security Framework (JEA + WDAC + Logging)
2025 Security Requirements:
-
JEA - Just Enough Administration for role-based access
-
WDAC - Windows Defender Application Control for script approval
-
Constrained Language Mode - For non-admin users
-
Script Block Logging - For audit trails
Just Enough Administration (JEA)
Required for production environments in 2025:
Create JEA session configuration file
New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -Path "C:\JEA\HelpDesk.pssc"
-VisibleCmdlets @{
Name = 'Restart-Service'
Parameters = @{ Name = 'Name'; ValidateSet = 'Spooler', 'Wuauserv' }
}, @{
Name = 'Get-Service'
} -LanguageMode NoLanguage
-ExecutionPolicy RemoteSigned
Register JEA endpoint
Register-PSSessionConfiguration -Name HelpDesk -Path "C:\JEA\HelpDesk.pssc"
-Force
Connect with limited privileges
Enter-PSSession -ComputerName Server01 -ConfigurationName HelpDesk
Windows Defender Application Control (WDAC)
Replaces AppLocker for PowerShell script control:
Create WDAC policy for approved scripts
New-CIPolicy -FilePath "C:\WDAC\PowerShellPolicy.xml" -ScanPath "C:\ApprovedScripts"
-Level FilePublisher `
-Fallback Hash
Convert to binary
ConvertFrom-CIPolicy -XmlFilePath "C:\WDAC\PowerShellPolicy.xml" ` -BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b"
Deploy via Group Policy or MDM
Constrained Language Mode
Recommended for all non-admin users:
Check current language mode
$ExecutionContext.SessionState.LanguageMode
Output: FullLanguage (admin) or ConstrainedLanguage (standard user)
Enable system-wide via environment variable
[Environment]::SetEnvironmentVariable( "__PSLockdownPolicy", "4", [System.EnvironmentVariableTarget]::Machine )
Script Block Logging
Enable for security auditing:
Enable via Group Policy or Registry
HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
EnableScriptBlockLogging = 1
EnableScriptBlockInvocationLogging = 1
Check logs
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | Where-Object Id -eq 4104 | # Script Block Logging Select-Object TimeCreated, Message -First 10
Execution Policy
Check current execution policy
Get-ExecutionPolicy
Set for current user (no admin needed)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Bypass for single session (use sparingly)
pwsh -ExecutionPolicy Bypass -File script.ps1
Credential Management
NEVER hardcode credentials
BAD: $password = "MyP@ssw0rd"
Use SecretManagement module (modern approach)
Install-PSResource -Name Microsoft.PowerShell.SecretManagement Install-PSResource -Name SecretManagement.KeyVault
Register-SecretVault -Name AzureKeyVault -ModuleName SecretManagement.KeyVault $secret = Get-Secret -Name "DatabasePassword" -Vault AzureKeyVault
Legacy: Get-Credential for interactive
$cred = Get-Credential
Azure Key Vault for production
$vaultName = "MyKeyVault" $secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name "DatabasePassword" $secret.SecretValue
Input Validation
function Do-Something { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Name,
[Parameter()]
[ValidateRange(1, 100)]
[int]$Count,
[Parameter()]
[ValidateSet("Option1", "Option2", "Option3")]
[string]$Option,
[Parameter()]
[ValidatePattern('^\d{3}-\d{3}-\d{4}$')]
[string]$PhoneNumber
)
}
Code Signing (Production)
Get code signing certificate
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
Sign script
Set-AuthenticodeSignature -FilePath script.ps1 -Certificate $cert
β‘ Performance Optimization
PowerShell 7+ Features
Parallel ForEach (PS 7+)
1..10 | ForEach-Object -Parallel { Start-Sleep -Seconds 1 "Processed $_" } -ThrottleLimit 5
Ternary operator
$result = $value ? "true" : "false"
Null-coalescing
$name = $userName ?? "default"
Null-conditional member access
$length = $string?.Length
Efficient Filtering
Use .NET methods for performance
Instead of: Get-Content large.txt | Where-Object {$_ -match "pattern"}
[System.IO.File]::ReadLines("large.txt") | Where-Object {$_ -match "pattern"}
Use -Filter parameter when available
Get-ChildItem -Path C:\ -Filter *.log -Recurse
Instead of: Get-ChildItem -Path C:\ -Recurse | Where-Object {$_.Extension -eq ".log"}
ArrayList vs Array
Arrays are immutable - slow for additions
$array = @() 1..1000 | ForEach-Object { $array += $_ } # SLOW
Use ArrayList for dynamic collections
$list = [System.Collections.ArrayList]::new() 1..1000 | ForEach-Object { [void]$list.Add($_) } # FAST
Or use generic List
$list = [System.Collections.Generic.List[int]]::new() 1..1000 | ForEach-Object { $list.Add($_) }
π§ͺ Testing with Pester
Install Pester
Install-Module -Name Pester -Force
Basic test structure
Describe "Get-Something Tests" { Context "When input is valid" { It "Should return expected value" { $result = Get-Something -Name "Test" $result | Should -Be "Expected" } }
Context "When input is invalid" {
It "Should throw an error" {
{ Get-Something -Name $null } | Should -Throw
}
}
}
Run tests
Invoke-Pester -Path ./tests Invoke-Pester -Path ./tests -OutputFormat NUnitXml -OutputFile TestResults.xml
Code coverage
Invoke-Pester -Path ./tests -CodeCoverage ./src/*.ps1
π Script Requirements & Versioning
Require specific PowerShell version
#Requires -Version 7.0
Require modules
#Requires -Modules Az.Accounts, Az.Compute
Require admin/elevated privileges (Windows)
#Requires -RunAsAdministrator
Combine multiple requirements
#Requires -Version 7.0 #Requires -Modules @{ModuleName='Pester'; ModuleVersion='5.0.0'}
Use strict mode
Set-StrictMode -Version Latest
π Common Cmdlets Reference
File System
Get-ChildItem (gci, ls, dir) Set-Location (cd, sl) New-Item (ni) Remove-Item (rm, del) Copy-Item (cp, copy) Move-Item (mv, move) Rename-Item (rn, ren) Get-Content (gc, cat, type) Set-Content (sc) Add-Content (ac)
Process Management
Get-Process (ps, gps) Stop-Process (kill, spps) Start-Process (start, saps) Wait-Process
Service Management
Get-Service (gsv) Start-Service (sasv) Stop-Service (spsv) Restart-Service (srsv) Set-Service
Network
Test-Connection (ping) Test-NetConnection Invoke-WebRequest (curl, wget, iwr) Invoke-RestMethod (irm)
Object Manipulation
Select-Object (select) Where-Object (where, ?) ForEach-Object (foreach, %) Sort-Object (sort) Group-Object (group) Measure-Object (measure) Compare-Object (compare, diff)
π REST API & Web Requests
GET request
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Method Get
POST with JSON body
$body = @{ name = "John" age = 30 } | ConvertTo-Json
$response = Invoke-RestMethod -Uri "https://api.example.com/users" ` -Method Post -Body $body -ContentType "application/json"
With headers and authentication
$headers = @{ "Authorization" = "Bearer $token" "Accept" = "application/json" }
$response = Invoke-RestMethod -Uri $url -Headers $headers
Download file
Invoke-WebRequest -Uri $url -OutFile "file.zip"
ποΈ Script Structure Best Practices
<# .SYNOPSIS Brief description
.DESCRIPTION Detailed description
.PARAMETER Name Parameter description
.EXAMPLE PS> .\script.ps1 -Name "John" Example usage
.NOTES Author: Your Name Version: 1.0.0 Date: 2025-01-01 #>
[CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Name )
Script-level error handling
$ErrorActionPreference = "Stop"
Use strict mode
Set-StrictMode -Version Latest
try { # Main script logic Write-Verbose "Starting script"
# ... script code ...
Write-Verbose "Script completed successfully"
} catch { Write-Error "Script failed: $_" exit 1 } finally { # Cleanup }
π Additional Resources
Official Documentation
-
PowerShell Docs: https://learn.microsoft.com/powershell
-
PowerShell Gallery: https://www.powershellgallery.com
-
Az Module Docs: https://learn.microsoft.com/powershell/azure
-
Microsoft Graph Docs: https://learn.microsoft.com/graph/powershell
Module Discovery
Find modules by keyword
Find-Module -Tag "Azure" Find-Module -Tag "Security"
Explore commands in a module
Get-Command -Module Az.Compute Get-Command -Verb Get -Noun VM
Get command help
Get-Help Get-AzVM -Full Get-Help Get-AzVM -Examples Get-Help Get-AzVM -Online
Update Help System
Update help files (requires internet)
Update-Help -Force -ErrorAction SilentlyContinue
Update help for specific modules
Update-Help -Module Az -Force
π― Quick Decision Guide
Use PowerShell 7+ when:
-
Cross-platform compatibility needed
-
New projects or scripts
-
Performance is important
-
Modern language features desired
Use Windows PowerShell 5.1 when:
-
Windows-specific modules required (WSUS, GroupPolicy legacy)
-
Corporate environments with strict version requirements
-
Legacy script compatibility needed
Choose Azure CLI when:
-
Simple one-liners needed
-
JSON output preferred
-
Bash scripting integration
Choose PowerShell Az module when:
-
Complex automation required
-
Object manipulation needed
-
PowerShell scripting expertise available
-
Reusable scripts and modules needed
β Pre-Flight Checklist for Scripts
Before running any PowerShell script, ensure:
-
β Platform Detection - Use $IsWindows , $IsLinux , $IsMacOS
-
β Version Check - #Requires -Version 7.0 if needed
-
β Module Requirements - #Requires -Modules specified
-
β Error Handling - try/catch blocks in place
-
β Input Validation - Parameter validation attributes used
-
β No Aliases - Full cmdlet names in scripts
-
β Path Handling - Use Join-Path or [IO.Path]::Combine()
-
β Encoding Specified - UTF-8 for cross-platform
-
β Credentials Secure - Never hardcoded
-
β Verbose Logging - Write-Verbose for debugging
π¨ Common Pitfalls & Solutions
Pitfall: Out-GridView Search Broken in 7.5
Known Issue: Out-GridView search doesn't work in PowerShell 7.5 due to .NET 9 changes
Workaround: Use Where-Object or Select-Object for filtering
Get-Process | Where-Object CPU -gt 100 | Format-Table
Or export to CSV and use external tools
Get-Process | Export-Csv processes.csv -NoTypeInformation
Pitfall: Case Sensitivity
Linux/macOS are case-sensitive
This fails on Linux if file is "File.txt"
Get-Content "file.txt"
Solution: Use exact casing or Test-Path first
if (Test-Path "file.txt") { Get-Content "file.txt" }
Pitfall: Execution Policy
Solution: Set for current user
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Or bypass for session
powershell.exe -ExecutionPolicy Bypass -File script.ps1
Pitfall: Module Import Failures
Solution: Check module availability and install
if (-not (Get-Module -ListAvailable -Name Az)) { Install-Module -Name Az -Force -Scope CurrentUser } Import-Module -Name Az
Pitfall: Array Concatenation Performance
Bad: $array += $item (recreates array each time)
Good: Use ArrayList or List
$list = [System.Collections.Generic.List[object]]::new() $list.Add($item)
Remember: ALWAYS research latest PowerShell documentation and module versions before implementing solutions. The PowerShell ecosystem evolves rapidly, and best practices are updated frequently.