Table of Contents
- Default Parameter Values
- Environment and Path Management
- Dynamic Object Creation
- Performance Optimization
- Error Handling Best Practices
- Advanced Function Development
- Pipeline Techniques
- Debugging and Troubleshooting
- PowerShell Profiles
- Module Management
- Security Best Practices
- Cross-Platform Considerations
- References
Overview
This comprehensive collection of PowerShell tips and tricks covers practical techniques, productivity enhancements, and solutions to common challenges encountered in PowerShell development and administration. These techniques have been tested in real-world scenarios and can significantly improve your PowerShell workflow.
For foundational PowerShell concepts, see the main PowerShell documentation.
Default Parameter Values
Use the $PSDefaultParameterValues preference variable to set custom default values for cmdlets and advanced functions that you frequently use. The parameters and the default values are stored as a hash table.
This feature is useful when you must specify the same parameter value nearly every time you use a cmdlet or when a particular parameter value is difficult to remember, such as an certificate thumbprint or Azure Subscription GUID.
Set a parameter default value:
$PSDefaultParameterValues=@{“<CmdletName>:<ParameterName>”=”<DefaultValue>”}
Set several parameter default values:
$PSDefaultParameterValues=@{
“<CmdletName>:<ParameterName1>”=”<DefaultValue>”
“<CmdletName>:<ParameterName2>”=”<DefaultValue>”
“<CmdletName>:<ParameterName3>”=”<DefaultValue>”
“<CmdletName>:<ParameterName4>”=”<DefaultValue>”
}
Set a parameter default value based on conditions using a script block:
$PSDefaultParameterValues=@{“<CmdletName>:<ParameterName>”={<ScriptBlock>}}
Use the Add() method to add preferences to an existing hash table.
$PSDefaultParameterValues.Add({“<CmdletName>:<ParameterName>”,”<DefaultValue>”})
Use the Remove() method to remove preferences from an existing hash table.
$PSDefaultParameterValues.Remove(“<CmdletName>:<ParameterName>”)
Use the Clear() method to remove all of the preferences from the hash table.
$PSDefaultParameterValues.Clear()
Example: Setting Default Parameters for Connect-Exchange Online:
$PSDefaultParameterValues = @{
"Connect-ExchangeOnline:UserPrincipalName" = "username@domainname.com"
"Connect-ExchangeOnline:ShowBanner" = $false
"Connect-ExchangeOnline:ShowProgress" = $false
}
about_Parameters_Default_Values
The $PSDefaultParameterValues preference variable lets you specify custom default values for any cmdlet or advanced function. Cmdlets and advanced functions use the custom default value unless you specify another value in the command.
The authors of cmdlets and advanced functions set standard default values for their parameters. Typically, the standard default values are useful, but they might not be appropriate for all environments.
This feature is especially useful when you must specify the same alternate parameter value nearly every time you use the command or when a particular parameter value is difficult to remember, such as an email server name or project GUID.
If the desired default value varies predictably, you can specify a script block that provides different default values for a parameter under different conditions.
$PSDefaultParameterValues = @{"CmdletName:ParameterName" = "DefaultValue"}
$PSDefaultParameterValues = @{"CmdletName:ParameterName" = {<ScriptBlock>}}
$PSDefaultParameterValues["Disabled"] = $true | $false
Environment and Path Management
PowerShell Paths with Folder Redirection
When Group Policy configures folder redirection for profile directories, including "Documents" where the default $PSModulePath
and $Profile
are located, performance issues can occur when using PowerShell remotely over VPN. This happens because PowerShell takes time to search $PSModulePath
to auto-load modules from network locations.
Solution: Redirect PowerShell paths to local directories for better performance.
# Check current module path
Write-Host "Current PSModulePath:" -ForegroundColor Yellow
$env:PSModulePath -split ';' | ForEach-Object { Write-Host " $_" }
# Update module path to local directory
$NetworkPath = "\\host.domain.com\Home\$env:USERNAME\Documents\PowerShell\Modules"
$LocalPath = "$env:USERPROFILE\Documents\PowerShell\Modules"
if ($env:PSModulePath -like "*$NetworkPath*")
{
Write-Host "Updating PSModulePath from network to local path..." -ForegroundColor Green
$env:PSModulePath = $env:PSModulePath.Replace($NetworkPath, $LocalPath)
}
# Function to fix profile path permanently
function Set-LocalPowerShellProfile
{
[CmdletBinding()]
param()
$NetworkPattern = "\\\\[^\\]+\\.*"
$LocalModulePath = "$env:USERPROFILE\Documents\PowerShell\Modules"
if ($env:PSModulePath -match $NetworkPattern)
{
Write-Host "Updating module path..." -ForegroundColor Green
$env:PSModulePath = $env:PSModulePath -replace $NetworkPattern, $LocalModulePath
Write-Host "PSModulePath updated to: $LocalModulePath" -ForegroundColor Green
}
$ProfilePath = $profile.CurrentUserCurrentHost
if ($ProfilePath -like "\\*")
{
Write-Warning "Profile is on network path: $ProfilePath"
Write-Host "Updating registry to use local Documents folder..." -ForegroundColor Yellow
try
{
$ProcessName = (Get-Process -Id $PID).ProcessName
$RegistryCommand = "& New-ItemProperty 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' Personal -Value '%USERPROFILE%\Documents' -Type ExpandString -Force"
if ($ProcessName -eq "pwsh")
{
pwsh -NoProfile -Command $RegistryCommand
}
else
{
powershell -NoProfile -Command $RegistryCommand
}
Write-Host "Restart command: $ProcessName" -ForegroundColor Yellow
Write-Host "Registry updated. Please restart PowerShell for changes to take effect." -ForegroundColor Green
}
catch
{
Write-Error "Failed to update registry: $($_.Exception.Message)"
}
}
}
# Call the function
Set-LocalPowerShellProfile
Environment Variable Management
Setting temporary environment variables:
$env:API_KEY = "your-secret-key"
$env:DEBUG = "true"
Setting persistent environment variables:
# Set for current user
[Environment]::SetEnvironmentVariable("API_KEY", "your-secret-key", "User")
# Set for all users (requires admin)
[Environment]::SetEnvironmentVariable("GLOBAL_SETTING", "value", "Machine")
# Set for current process only
[Environment]::SetEnvironmentVariable("TEMP_VAR", "value", "Process")
Safely modifying PATH variable:
function Add-ToPath
{
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({Test-Path $_})]
[string]$Directory,
[ValidateSet("User", "Machine")]
[string]$Scope = "User"
)
if (-not (Test-Path $Directory))
{
Write-Warning "Directory does not exist: $Directory"
return
}
$CurrentPath = [Environment]::GetEnvironmentVariable("PATH", $Scope)
if ($CurrentPath -split ';' -notcontains $Directory)
{
$NewPath = "$CurrentPath;$Directory"
[Environment]::SetEnvironmentVariable("PATH", $NewPath, $Scope)
Write-Host "Added to PATH: $Directory" -ForegroundColor Green
}
else
{
Write-Host "Directory already in PATH: $Directory" -ForegroundColor Yellow
}
}
# Usage
Add-ToPath -Directory "C:\Tools\MyApp" -Scope User
Dynamic Object Creation
This section demonstrates creating custom objects dynamically from various data sources, which is a common requirement in PowerShell development.
Important Performance Note: Avoid using +=
operator for arrays as it creates a new array each time, which is inefficient for large datasets.
# Efficient approach using a list
$PSObjectList = [System.Collections.Generic.List[PSCustomObject]]@()
foreach ($Object in $Results)
{
$CustomObject = [PSCustomObject]@{}
foreach ($Property in $Object.Properties.PropertyNames)
{
$Value = $Object.Properties.Item($Property)
# Handle array values - take first element or entire array based on count
if ($Value -is [Array] -and $Value.Count -eq 1)
{
$CustomObject | Add-Member -MemberType NoteProperty -Name $Property -Value $Value[0] -Force
}
else
{
$CustomObject | Add-Member -MemberType NoteProperty -Name $Property -Value $Value -Force
}
}
$PSObjectList.Add($CustomObject)
}
# Convert to array if needed
$PSObject = $PSObjectList.ToArray()
Performance Optimization
Collection Performance Tips
Use Generic Lists Instead of Arrays for Dynamic Collections:
# Slow - creates new array each time
$SlowArray = @()
1..1000 | ForEach-Object { $SlowArray += $_ }
# Fast - uses efficient list structure
$FastList = [System.Collections.Generic.List[int]]@()
1..1000 | ForEach-Object { $FastList.Add($_) }
# Fastest - use pipeline when possible
$FastestResult = 1..1000 | ForEach-Object { $_ }
Optimize Where-Object Usage:
# Slower - pipeline filtering
Get-Process | Where-Object { $_.Name -eq "notepad" }
# Faster - parameter filtering when available
Get-Process -Name "notepad"
# For complex filtering, use scriptblock optimization
Get-Process | Where-Object Name -eq "notepad" # Faster than { $_.Name -eq "notepad" }
String Building Optimization
# Slow for many concatenations
$Result = ""
1..1000 | ForEach-Object { $Result += "Line $_`n" }
# Fast - use StringBuilder for many operations
$StringBuilder = [System.Text.StringBuilder]::new()
1..1000 | ForEach-Object { $StringBuilder.AppendLine("Line $_") | Out-Null }
$Result = $StringBuilder.ToString()
# Alternative - use -join for simple cases
$Result = (1..1000 | ForEach-Object { "Line $_" }) -join "`n"
Error Handling Best Practices
Use Structured Error Handling:
function Get-SafeUserInformation
{
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$UserName
)
try
{
$User = Get-ADUser $UserName -ErrorAction Stop
Write-Output $User
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
Write-Warning "User '$UserName' not found in Active Directory"
return $null
}
catch [System.Security.Authentication.AuthenticationException]
{
Write-Error "Authentication failed - check your credentials"
throw
}
catch
{
Write-Error "Unexpected error retrieving user '$UserName': $($_.Exception.Message)"
throw
}
}
Advanced Error Handling with Finally:
function Invoke-WithCleanup
{
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[scriptblock]$ScriptBlock,
[Parameter(Mandatory)]
[scriptblock]$CleanupScript
)
try
{
& $ScriptBlock
}
catch
{
Write-Error "Operation failed: $($_.Exception.Message)"
throw
}
finally
{
Write-Verbose "Executing cleanup operations..."
& $CleanupScript
}
}
# Usage
Invoke-WithCleanup -ScriptBlock {
# Main operation
$Connection = Connect-Database
Get-DatabaseData -Connection $Connection
} -CleanupScript {
# Cleanup operations
if ($Connection) { $Connection.Close() }
}
Advanced Function Development
Function Template with Best Practices
function Get-EnhancedSystemInfo
{
<#
.SYNOPSIS
Retrieves comprehensive system information from local or remote computers.
.DESCRIPTION
This function gathers detailed system information including hardware,
operating system, and performance data. Supports pipeline input and
parallel processing for multiple computers.
.PARAMETER ComputerName
One or more computer names to query. Accepts pipeline input.
.PARAMETER Credential
Credentials to use for remote connections.
.PARAMETER IncludePerformance
Include performance counters in the output.
.EXAMPLE
Get-EnhancedSystemInfo -ComputerName "Server01"
Retrieves system information from Server01.
.EXAMPLE
"Server01", "Server02" | Get-EnhancedSystemInfo -IncludePerformance
Retrieves system information with performance data from multiple servers.
.NOTES
Requires WinRM to be enabled on target computers for remote queries.
Author: Your Name
Version: 1.0
Last Modified: 2025-07-21
#>
[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[Alias("CN", "MachineName")]
[string[]]$ComputerName,
[Parameter()]
[PSCredential]$Credential,
[Parameter()]
[switch]$IncludePerformance
)
begin
{
Write-Verbose "Starting system information collection..."
$Results = [System.Collections.Generic.List[PSCustomObject]]@()
}
process
{
foreach ($Computer in $ComputerName)
{
Write-Verbose "Processing computer: $Computer"
try
{
$SystemInfo = [PSCustomObject]@{
ComputerName = $Computer
OperatingSystem = $null
TotalMemoryGB = $null
ProcessorCount = $null
LastBootTime = $null
ErrorMessage = $null
}
# Build scriptblock for remote execution
$ScriptBlock = {
$OS = Get-WmiObject -Class Win32_OperatingSystem
$CS = Get-WmiObject -Class Win32_ComputerSystem
[PSCustomObject]@{
OperatingSystem = $OS.Caption
TotalMemoryGB = [Math]::Round($CS.TotalPhysicalMemory / 1GB, 2)
ProcessorCount = $CS.NumberOfProcessors
LastBootTime = $OS.ConvertToDateTime($OS.LastBootUpTime)
}
}
# Execute locally or remotely
if ($Computer -eq $env:COMPUTERNAME -or $Computer -eq "localhost")
{
$Result = & $ScriptBlock
}
else
{
$InvokeParams = @{
ComputerName = $Computer
ScriptBlock = $ScriptBlock
ErrorAction = "Stop"
}
if ($Credential)
{
$InvokeParams.Credential = $Credential
}
$Result = Invoke-Command @InvokeParams
}
# Update system info object
$SystemInfo.OperatingSystem = $Result.OperatingSystem
$SystemInfo.TotalMemoryGB = $Result.TotalMemoryGB
$SystemInfo.ProcessorCount = $Result.ProcessorCount
$SystemInfo.LastBootTime = $Result.LastBootTime
$Results.Add($SystemInfo)
}
catch
{
Write-Warning "Failed to retrieve information from $Computer`: $($_.Exception.Message)"
$SystemInfo.ErrorMessage = $_.Exception.Message
$Results.Add($SystemInfo)
}
}
}
end
{
Write-Verbose "System information collection completed. Processed $($Results.Count) computers."
return $Results
}
}
Pipeline Techniques
Advanced Pipeline Processing
# Efficient pipeline processing with ForEach-Object -Parallel (PowerShell 7+)
1..100 | ForEach-Object -Parallel {
Start-Sleep -Seconds 1
"Processed item $_"
} -ThrottleLimit 10
# Custom pipeline functions
function ConvertTo-Base64
{
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string]$InputString
)
process
{
$Bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString)
$EncodedString = [System.Convert]::ToBase64String($Bytes)
[PSCustomObject]@{
Original = $InputString
Base64 = $EncodedString
Length = $EncodedString.Length
}
}
}
# Usage
"Hello", "World", "PowerShell" | ConvertTo-Base64
Debugging and Troubleshooting
Interactive Debugging
# Set breakpoints in scripts
Set-PSBreakpoint -Script "C:\Scripts\MyScript.ps1" -Line 25
# Set conditional breakpoints
Set-PSBreakpoint -Script "C:\Scripts\MyScript.ps1" -Line 25 -Condition '$counter -gt 10'
# Set variable breakpoints
Set-PSBreakpoint -Variable "ImportantVariable" -Mode Write
# Debug with custom actions
Set-PSBreakpoint -Script "C:\Scripts\MyScript.ps1" -Line 25 -Action {
Write-Host "Counter value: $counter" -ForegroundColor Yellow
}
Tracing Command Execution
# Trace cmdlet discovery
Trace-Command -Name CommandDiscovery -Expression { Get-Process } -PSHost
# Trace parameter binding
Trace-Command -Name ParameterBinding -Expression { Get-ChildItem -Path C:\ -Recurse } -PSHost
# Trace multiple categories
Trace-Command -Name CommandDiscovery, ParameterBinding -Expression {
Get-Service | Where-Object Status -eq "Running"
} -PSHost
Performance Analysis
# Measure script execution time
Measure-Command -Expression {
Get-ChildItem -Path C:\ -Recurse -ErrorAction SilentlyContinue
}
# Profile function performance
function Test-Performance
{
param([int]$Iterations = 1000)
$Results = @{}
# Test Array += operator
$Results['Array'] = Measure-Command {
$Array = @()
1..$Iterations | ForEach-Object { $Array += $_ }
}
# Test ArrayList
$Results['ArrayList'] = Measure-Command {
$ArrayList = [System.Collections.ArrayList]@()
1..$Iterations | ForEach-Object { $ArrayList.Add($_) | Out-Null }
}
# Test Generic List
$Results['GenericList'] = Measure-Command {
$List = [System.Collections.Generic.List[int]]@()
1..$Iterations | ForEach-Object { $List.Add($_) }
}
return $Results
}
# Run performance comparison
$PerfResults = Test-Performance -Iterations 5000
$PerfResults | Format-Table -AutoSize
PowerShell Profiles
Profile Types and Locations
# Display all profile paths
$profile | Get-Member -MemberType NoteProperty | ForEach-Object {
[PSCustomObject]@{
Profile = $_.Name
Path = $profile.($_.Name)
Exists = Test-Path $profile.($_.Name)
}
} | Format-Table -AutoSize
# Create profile if it doesn't exist
if (-not (Test-Path $profile.CurrentUserCurrentHost))
{
New-Item -Path $profile.CurrentUserCurrentHost -ItemType File -Force
Write-Host "Created profile: $($profile.CurrentUserCurrentHost)" -ForegroundColor Green
}
Essential Profile Functions
# Add to your PowerShell profile
# Quick navigation functions
function Set-LocationToProfile { Set-Location (Split-Path $profile.CurrentUserCurrentHost) }
function Set-LocationToModules { Set-Location ($env:PSModulePath -split ';')[0] }
Set-Alias -Name prof -Value Set-LocationToProfile
Set-Alias -Name psmod -Value Set-LocationToModules
# Enhanced directory listing
function Get-DirectoryInfo
{
param([string]$Path = ".")
Get-ChildItem -Path $Path -Force | ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Type = if ($_.PSIsContainer) { "Directory" } else { "File" }
Size = if (-not $_.PSIsContainer) { "{0:N2} KB" -f ($_.Length / 1KB) } else { "" }
LastWrite = $_.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
Hidden = $_.Attributes -match "Hidden"
}
} | Format-Table -AutoSize
}
Set-Alias -Name ll -Value Get-DirectoryInfo
# Quick module reload
function Import-ModuleForce
{
param([string]$ModuleName)
Remove-Module $ModuleName -Force -ErrorAction SilentlyContinue
Import-Module $ModuleName -Force
}
Set-Alias -Name reimport -Value Import-ModuleForce
# System information shortcuts
function Get-SystemLoad
{
Get-Counter -Counter "\Processor(_Total)\% Processor Time", "\Memory\Available MBytes" -SampleInterval 1 -MaxSamples 1 |
Select-Object -ExpandProperty CounterSamples |
ForEach-Object {
[PSCustomObject]@{
Counter = $_.Path
Value = [Math]::Round($_.CookedValue, 2)
Timestamp = $_.Timestamp
}
}
}
# Enhanced prompt with git status
function prompt
{
$Location = Get-Location
$GitBranch = ""
if (Get-Command git -ErrorAction SilentlyContinue)
{
try
{
$GitStatus = git status --porcelain 2>$null
$Branch = git branch --show-current 2>$null
if ($Branch)
{
$Changes = if ($GitStatus) { "*" } else { "" }
$GitBranch = " [$Branch$Changes]"
}
}
catch { }
}
Write-Host "PS " -NoNewline -ForegroundColor Green
Write-Host $Location -NoNewline -ForegroundColor Blue
Write-Host $GitBranch -NoNewline -ForegroundColor Yellow
Write-Host ">" -NoNewline -ForegroundColor Green
return " "
}
Module Management
Module Discovery and Installation
# Find modules by capability
Find-Module -Tag "ActiveDirectory", "Exchange" | Select-Object Name, Description, Version
# Install modules with dependency checking
function Install-ModuleWithDependencies
{
param(
[Parameter(Mandatory)]
[string]$ModuleName,
[string]$Repository = "PSGallery"
)
try
{
$Module = Find-Module -Name $ModuleName -Repository $Repository -ErrorAction Stop
Write-Host "Installing module: $($Module.Name) v$($Module.Version)" -ForegroundColor Green
# Check for dependencies
if ($Module.Dependencies)
{
Write-Host "Dependencies found:" -ForegroundColor Yellow
$Module.Dependencies | ForEach-Object {
Write-Host " - $($_.Name) (>= $($_.MinimumVersion))" -ForegroundColor Gray
}
}
Install-Module -Name $ModuleName -Repository $Repository -Scope CurrentUser -Force
Write-Host "Module installed successfully!" -ForegroundColor Green
# Import and verify
Import-Module $ModuleName -Force
$ImportedModule = Get-Module $ModuleName
Write-Host "Imported $($ImportedModule.ExportedCommands.Count) commands" -ForegroundColor Cyan
}
catch
{
Write-Error "Failed to install module '$ModuleName': $($_.Exception.Message)"
}
}
Module Development Helpers
# Quick module manifest creation
function New-QuickModuleManifest
{
param(
[Parameter(Mandatory)]
[string]$ModuleName,
[string]$Path = ".",
[string]$Author = $env:USERNAME,
[string]$Description = "PowerShell module"
)
$ManifestPath = Join-Path $Path "$ModuleName.psd1"
$ManifestParams = @{
Path = $ManifestPath
RootModule = "$ModuleName.psm1"
ModuleVersion = "1.0.0"
GUID = [System.Guid]::NewGuid().ToString()
Author = $Author
Description = $Description
PowerShellVersion = "5.1"
FunctionsToExport = @()
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
}
New-ModuleManifest @ManifestParams
Write-Host "Created module manifest: $ManifestPath" -ForegroundColor Green
}
# Module testing helper
function Test-ModuleQuick
{
param(
[Parameter(Mandatory)]
[string]$ModulePath
)
try
{
# Test manifest
$Manifest = Test-ModuleManifest -Path $ModulePath -ErrorAction Stop
Write-Host "✓ Manifest validation passed" -ForegroundColor Green
# Test import
Import-Module $ModulePath -Force -ErrorAction Stop
Write-Host "✓ Module import successful" -ForegroundColor Green
# Display exported functions
$Module = Get-Module (Split-Path $ModulePath -LeafBase)
Write-Host "Exported Functions: $($Module.ExportedFunctions.Count)" -ForegroundColor Cyan
$Module.ExportedFunctions.Keys | ForEach-Object {
Write-Host " - $_" -ForegroundColor Gray
}
Remove-Module $Module.Name -Force
}
catch
{
Write-Error "Module test failed: $($_.Exception.Message)"
}
}
Security Best Practices
Secure Credential Handling
# Never store passwords in plain text
# Bad:
# $Password = "MyPassword123"
# Good - prompt for credentials
$Credential = Get-Credential -Message "Enter your credentials"
# Good - use secure strings
$SecurePassword = Read-Host -AsSecureString -Prompt "Enter password"
$Credential = New-Object System.Management.Automation.PSCredential("username", $SecurePassword)
# Convert secure string back to plain text (when absolutely necessary)
function ConvertFrom-SecureStringToPlainText
{
param([System.Security.SecureString]$SecureString)
try
{
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
}
finally
{
if ($BSTR)
{
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
}
}
}
Input Validation and Sanitization
function Invoke-SafeCommand
{
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidatePattern('^[a-zA-Z0-9\-_\.]+$')] # Only allow alphanumeric and safe characters
[string]$ComputerName,
[Parameter(Mandatory)]
[ValidateSet('Get-Service', 'Get-Process', 'Get-EventLog')] # Whitelist allowed commands
[string]$Command,
[Parameter()]
[ValidateScript({
# Custom validation for parameters
$_ -match '^[a-zA-Z0-9\s\-]+$' -and $_.Length -le 100
})]
[string]$Parameters
)
# Construct and execute safe command
$FullCommand = "$Command"
if ($Parameters)
{
$FullCommand += " $Parameters"
}
Write-Verbose "Executing: $FullCommand on $ComputerName"
try
{
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
param($Cmd, $Params)
Invoke-Expression "$Cmd $Params"
} -ArgumentList $Command, $Parameters -ErrorAction Stop
}
catch
{
Write-Error "Command execution failed: $($_.Exception.Message)"
}
}
Execution Policy Management
# Check current execution policy
Get-ExecutionPolicy -List
# Set execution policy safely
function Set-ExecutionPolicyScoped
{
param(
[Parameter(Mandatory)]
[Microsoft.PowerShell.ExecutionPolicy]$ExecutionPolicy,
[Microsoft.PowerShell.ExecutionPolicyScope]$Scope = "CurrentUser"
)
$CurrentPolicy = Get-ExecutionPolicy -Scope $Scope
if ($CurrentPolicy -ne $ExecutionPolicy)
{
Write-Host "Changing execution policy from $CurrentPolicy to $ExecutionPolicy for scope $Scope" -ForegroundColor Yellow
Set-ExecutionPolicy -ExecutionPolicy $ExecutionPolicy -Scope $Scope -Force
Write-Host "Execution policy updated successfully" -ForegroundColor Green
}
else
{
Write-Host "Execution policy already set to $ExecutionPolicy for scope $Scope" -ForegroundColor Green
}
}
# Usage
Set-ExecutionPolicyScoped -ExecutionPolicy RemoteSigned -Scope CurrentUser
Cross-Platform Considerations
Platform Detection
function Get-PlatformInfo
{
[PSCustomObject]@{
IsWindows = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)
IsLinux = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)
IsMacOS = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
Architecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString()
OSDescription = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
}
}
# Platform-specific code execution
$Platform = Get-PlatformInfo
if ($Platform.IsWindows)
{
# Windows-specific code
Get-WmiObject -Class Win32_ComputerSystem
}
elseif ($Platform.IsLinux)
{
# Linux-specific code
Get-Content /proc/version
}
elseif ($Platform.IsMacOS)
{
# macOS-specific code
system_profiler SPHardwareDataType
}
Cross-Platform File Operations
function New-CrossPlatformPath
{
param([string[]]$Path)
# Use Join-Path for cross-platform compatibility
$Result = $Path[0]
for ($i = 1; $i -lt $Path.Length; $i++)
{
$Result = Join-Path -Path $Result -ChildPath $Path[$i]
}
return $Result
}
# Platform-agnostic temporary directory
function Get-TempDirectory
{
if ($IsWindows -or $env:OS -eq "Windows_NT")
{
return $env:TEMP
}
else
{
return "/tmp"
}
}
# Cross-platform environment variable access
function Get-EnvironmentVariable
{
param(
[Parameter(Mandatory)]
[string]$Name,
[string]$Default = ""
)
$Value = [System.Environment]::GetEnvironmentVariable($Name)
return if ($Value) { $Value } else { $Default }
}
References
Official Documentation
- PowerShell Best Practices and Style Guide
- about_Parameters_Default_Values
- about_Advanced_Functions
- about_Error_Handling
- PowerShell Performance Best Practices
Related Documentation
For comprehensive PowerShell learning resources, see:
- PowerShell Development Guide - Main documentation hub
- PowerShell Functions - Function development best practices
- PowerShell Modules - Module development guide
- PowerShell Scripts - Script development standards
- PowerShell Troubleshooting - Debugging and problem-solving
Community Resources
This document provides practical tips and techniques for PowerShell development and administration. For foundational concepts and comprehensive guides, refer to the main PowerShell documentation.