Introduction
PowerShell has become the essential tool for automating system administration and DevOps tasks. In 2026, the focus is on robust, maintainable, and reusable scripts. This tutorial guides you step by step through creating advanced scripts that leverage modern PowerShell 7 features: functions with CmdletBinding, POO classes, centralized error handling, and professional module creation. Every example is immediately executable and suitable for production environments.
Prerequisites
- PowerShell 7.4 or higher installed
- Intermediate PowerShell knowledge (functions, pipelines)
- VS Code editor with PowerShell extension
- Script execution rights (Set-ExecutionPolicy)
Advanced Function with CmdletBinding
function Get-SystemInfo {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string[]]$ComputerName,
[switch]$IncludeProcesses
)
begin {
Write-Verbose "Starting information collection"
}
process {
foreach ($computer in $ComputerName) {
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer
$result = [PSCustomObject]@{
ComputerName = $computer
OSVersion = $os.Version
Uptime = (Get-Date) - $os.LastBootUpTime
}
if ($IncludeProcesses) {
$result | Add-Member -MemberType NoteProperty -Name Processes -Value (Get-Process -ComputerName $computer)
}
Write-Output $result
}
}
}This function uses CmdletBinding to support -Verbose and -Debug. Parameters are strongly typed and the pipeline is handled via process. The returned object is structured for later use.
Advanced Parameter Validation
function Get-SystemInfo {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidatePattern('^[A-Za-z0-9-]+$')]
[string[]]$ComputerName,
[ValidateRange(1, 100)]
[int]$Timeout = 30
)
# Function body unchanged
}ValidatePattern and ValidateRange prevent invalid input before execution. These attributes provide clear, automatic error messages.
Centralized Error Handling
function Invoke-SafeCommand {
[CmdletBinding()]
param([scriptblock]$ScriptBlock)
try {
& $ScriptBlock
}
catch [System.Management.Automation.RuntimeException] {
Write-Error "Execution error: $($_.Exception.Message)" -ErrorAction Stop
}
catch {
Write-Error "Unexpected error: $($_.Exception.Message)" -ErrorAction Stop
}
finally {
Write-Verbose "Execution completed"
}
}The try/catch/finally structure with specific exception typing ensures precise handling and the ability to stop the script via ErrorAction Stop.
Defining a PowerShell Class
class SystemReport {
[string]$ComputerName
[datetime]$Timestamp
hidden [int]$InternalId
SystemReport([string]$name) {
$this.ComputerName = $name
$this.Timestamp = Get-Date
$this.InternalId = Get-Random
}
[string] ToString() {
return "Report for $($this.ComputerName) generated on $($this.Timestamp)"
}
}Classes enable an object-oriented approach with encapsulation. The hidden keyword protects internal properties while maintaining code readability.
Creating a Complete PowerShell Module
function Get-SystemInfo { <# function code here #> }
function Invoke-SafeCommand { <# code here #> }
Export-ModuleMember -Function Get-SystemInfo, Invoke-SafeCommand
# Associated .psd1 file:
# @{ ModuleVersion = '1.0.0'; RootModule = 'MyAdminTools.psm1'; FunctionsToExport = '*' }A well-structured module exposes only public functions via Export-ModuleMember. The .psd1 manifest file versions and documents the module for professional distribution.
Best Practices
- Always use [CmdletBinding()] and typed parameters
- Implement explicit error handling with try/catch
- Document every function with help comments
- Version modules and use a .psd1 manifest
- Systematically test with Pester before deployment
Common Mistakes to Avoid
- Omitting pipeline support in advanced functions
- Using Write-Host instead of Write-Output or Write-Verbose
- Failing to validate user input with Validate* attributes
- Ignoring remote session management with Enter-PSSession or Invoke-Command
Going Further
Deepen your skills with our certified training on PowerShell automation and DevOps. Discover our Learni courses.