PowerShell Automation for System Administrators: Advanced Techniques

Master advanced PowerShell automation techniques for Windows system administration. Learn about DSC, remote management, and enterprise-grade scripting.

14 min read
2.658 words

PowerShell Automation for System Administrators: Advanced Techniques#

PowerShell has evolved from a simple command-line shell to a powerful automation platform. This comprehensive guide explores advanced PowerShell techniques that enterprise system administrators use to manage complex Windows environments efficiently.

Advanced PowerShell Fundamentals#

1. PowerShell Classes and Custom Objects#

# Define a custom class for server management class ServerManager { [string]$ServerName [string]$Environment [datetime]$LastPatched [bool]$IsOnline # Constructor ServerManager([string]$Name, [string]$Env) { $this.ServerName = $Name $this.Environment = $Env $this.LastPatched = (Get-Date).AddDays(-30) $this.IsOnline = $this.TestConnection() } # Method to test server connectivity [bool] TestConnection() { try { $result = Test-NetConnection -ComputerName $this.ServerName -Port 5985 -InformationLevel Quiet return $result } catch { return $false } } # Method to get system information [hashtable] GetSystemInfo() { if (-not $this.IsOnline) { throw "Server $($this.ServerName) is not accessible" } $session = New-PSSession -ComputerName $this.ServerName $info = Invoke-Command -Session $session -ScriptBlock { @{ OS = (Get-CimInstance Win32_OperatingSystem).Caption Memory = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2) CPU = (Get-CimInstance Win32_Processor).Name Uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime DiskSpace = Get-CimInstance Win32_LogicalDisk | Where-Object DriveType -eq 3 | Select-Object DeviceID, @{n='SizeGB';e={[math]::Round($_.Size/1GB,2)}}, @{n='FreeGB';e={[math]::Round($_.FreeSpace/1GB,2)}} } } Remove-PSSession $session return $info } # Method to install Windows updates [void] InstallUpdates() { if (-not $this.IsOnline) { throw "Server $($this.ServerName) is not accessible" } $session = New-PSSession -ComputerName $this.ServerName Invoke-Command -Session $session -ScriptBlock { # Install PSWindowsUpdate module if not present if (-not (Get-Module -ListAvailable PSWindowsUpdate)) { Install-Module PSWindowsUpdate -Force -Scope AllUsers } # Install updates Get-WUInstall -AcceptAll -AutoReboot } Remove-PSSession $session $this.LastPatched = Get-Date } } # Usage example $server = [ServerManager]::new("WEB-01", "Production") $serverInfo = $server.GetSystemInfo() Write-Output "Server: $($server.ServerName)" Write-Output "OS: $($serverInfo.OS)" Write-Output "Memory: $($serverInfo.Memory) GB"

2. Advanced Error Handling and Logging#

# Advanced logging class class Logger { [string]$LogPath [string]$LogLevel Logger([string]$Path, [string]$Level = "INFO") { $this.LogPath = $Path $this.LogLevel = $Level # Create log directory if it doesn't exist $logDir = Split-Path $Path -Parent if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } } [void] WriteLog([string]$Message, [string]$Level = "INFO") { $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "[$timestamp] [$Level] $Message" # Write to file Add-Content -Path $this.LogPath -Value $logEntry # Write to console with color coding switch ($Level) { "ERROR" { Write-Host $logEntry -ForegroundColor Red } "WARN" { Write-Host $logEntry -ForegroundColor Yellow } "INFO" { Write-Host $logEntry -ForegroundColor Green } "DEBUG" { Write-Host $logEntry -ForegroundColor Cyan } } } [void] Error([string]$Message) { $this.WriteLog($Message, "ERROR") } [void] Warn([string]$Message) { $this.WriteLog($Message, "WARN") } [void] Info([string]$Message) { $this.WriteLog($Message, "INFO") } [void] Debug([string]$Message) { $this.WriteLog($Message, "DEBUG") } } # Advanced error handling function function Invoke-WithRetry { param( [scriptblock]$ScriptBlock, [int]$MaxRetries = 3, [int]$DelaySeconds = 5, [Logger]$Logger ) $attempt = 1 do { try { $Logger.Info("Attempt $attempt of $MaxRetries") $result = & $ScriptBlock $Logger.Info("Operation completed successfully") return $result } catch { $Logger.Error("Attempt $attempt failed: $($_.Exception.Message)") if ($attempt -eq $MaxRetries) { $Logger.Error("All retry attempts exhausted") throw $_ } $Logger.Warn("Waiting $DelaySeconds seconds before retry...") Start-Sleep -Seconds $DelaySeconds $attempt++ } } while ($attempt -le $MaxRetries) } # Usage example $logger = [Logger]::new("C:\Logs\automation.log") $result = Invoke-WithRetry -ScriptBlock { # Potentially failing operation Get-Service -Name "NonExistentService" -ErrorAction Stop } -MaxRetries 3 -DelaySeconds 2 -Logger $logger

PowerShell Desired State Configuration (DSC)#

1. Custom DSC Resources#

# Custom DSC resource for IIS configuration [DscResource()] class IISWebsiteConfig { [DscProperty(Key)] [string]$SiteName [DscProperty(Mandatory)] [string]$PhysicalPath [DscProperty()] [string]$Port = "80" [DscProperty()] [string]$Protocol = "http" [DscProperty()] [string]$AppPool = "DefaultAppPool" [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [IISWebsiteConfig] Get() { $website = Get-IISSite -Name $this.SiteName -ErrorAction SilentlyContinue if ($website) { $this.Ensure = [Ensure]::Present $this.PhysicalPath = $website.Applications[0].VirtualDirectories[0].PhysicalPath $this.Port = $website.Bindings[0].bindingInformation.Split(':')[1] $this.Protocol = $website.Bindings[0].protocol } else { $this.Ensure = [Ensure]::Absent } return $this } [bool] Test() { $current = $this.Get() if ($this.Ensure -eq [Ensure]::Present) { return ($current.Ensure -eq [Ensure]::Present -and $current.PhysicalPath -eq $this.PhysicalPath -and $current.Port -eq $this.Port -and $current.Protocol -eq $this.Protocol) } else { return ($current.Ensure -eq [Ensure]::Absent) } } [void] Set() { if ($this.Ensure -eq [Ensure]::Present) { # Create physical path if it doesn't exist if (-not (Test-Path $this.PhysicalPath)) { New-Item -ItemType Directory -Path $this.PhysicalPath -Force } # Create or update website $website = Get-IISSite -Name $this.SiteName -ErrorAction SilentlyContinue if (-not $website) { New-IISSite -Name $this.SiteName -PhysicalPath $this.PhysicalPath -Port $this.Port -Protocol $this.Protocol } else { Set-IISSite -Name $this.SiteName -PhysicalPath $this.PhysicalPath } # Configure application pool if ($this.AppPool -ne "DefaultAppPool") { Set-IISSiteBinding -Name $this.SiteName -ApplicationPool $this.AppPool } } else { # Remove website $website = Get-IISSite -Name $this.SiteName -ErrorAction SilentlyContinue if ($website) { Remove-IISSite -Name $this.SiteName -Confirm:$false } } } } # DSC Configuration using custom resource Configuration WebServerConfig { param( [string[]]$ComputerName = "localhost" ) Import-DscResource -ModuleName PSDesiredStateConfiguration Import-DscResource -ModuleName IISWebsiteConfig Node $ComputerName { # Install IIS WindowsFeature IIS { Ensure = "Present" Name = "IIS-WebServerRole" } WindowsFeature IISManagement { Ensure = "Present" Name = "IIS-ManagementConsole" DependsOn = "[WindowsFeature]IIS" } # Configure websites IISWebsiteConfig MainWebsite { SiteName = "MainSite" PhysicalPath = "C:\inetpub\mainsite" Port = "80" Protocol = "http" Ensure = "Present" DependsOn = "[WindowsFeature]IIS" } IISWebsiteConfig APIWebsite { SiteName = "APISite" PhysicalPath = "C:\inetpub\apisite" Port = "8080" Protocol = "http" Ensure = "Present" DependsOn = "[WindowsFeature]IIS" } } } # Compile and apply configuration WebServerConfig -ComputerName "WEB-01", "WEB-02" Start-DscConfiguration -Path .\WebServerConfig -Wait -Verbose

2. DSC Pull Server Configuration#

# Configure DSC Pull Server Configuration DSCPullServer { param( [string[]]$ComputerName = "localhost", [string]$CertificateThumbprint ) Import-DscResource -ModuleName PSDesiredStateConfiguration Import-DscResource -ModuleName xPSDesiredStateConfiguration Node $ComputerName { WindowsFeature DSCServiceFeature { Ensure = "Present" Name = "DSC-Service" } xDscWebService PSDSCPullServer { Ensure = "Present" EndpointName = "PSDSCPullServer" Port = 8080 PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer" CertificateThumbPrint = $CertificateThumbprint ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" ConfigurationPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration" State = "Started" DependsOn = "[WindowsFeature]DSCServiceFeature" } File RegistrationKeyFile { Ensure = "Present" Type = "File" DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt" Contents = "694e0325-dcaa-4b9c-8b8b-a8b6b8b8b8b8" } } } # Client configuration for pull mode [DSCLocalConfigurationManager()] Configuration PullClientConfig { param( [string]$ComputerName = "localhost", [string]$PullServerURL = "https://dsc-pull.company.com:8080/PSDSCPullServer.svc" ) Node $ComputerName { Settings { RefreshMode = "Pull" RefreshFrequencyMins = 30 RebootNodeIfNeeded = $true } ConfigurationRepositoryWeb PullServer { ServerURL = $PullServerURL RegistrationKey = "694e0325-dcaa-4b9c-8b8b-a8b6b8b8b8b8" ConfigurationNames = @("WebServerConfig") } ReportServerWeb PullServerReports { ServerURL = $PullServerURL RegistrationKey = "694e0325-dcaa-4b9c-8b8b-a8b6b8b8b8b8" } } }

Advanced Remote Management#

1. Secure Remote Session Management#

# Advanced remote session manager class RemoteSessionManager { [hashtable]$Sessions = @{} [string]$CredentialPath [Logger]$Logger RemoteSessionManager([string]$CredPath, [Logger]$Log) { $this.CredentialPath = $CredPath $this.Logger = $Log } [PSCredential] GetCredential([string]$ComputerName) { $credFile = Join-Path $this.CredentialPath "$ComputerName.xml" if (Test-Path $credFile) { return Import-Clixml $credFile } else { # Prompt for credentials and save securely $cred = Get-Credential -Message "Enter credentials for $ComputerName" $cred | Export-Clixml $credFile return $cred } } [PSSession] CreateSession([string]$ComputerName, [string]$ConfigurationName = $null) { try { $this.Logger.Info("Creating session to $ComputerName") $sessionParams = @{ ComputerName = $ComputerName Credential = $this.GetCredential($ComputerName) EnableNetworkAccess = $true } if ($ConfigurationName) { $sessionParams.ConfigurationName = $ConfigurationName } $session = New-PSSession @sessionParams $this.Sessions[$ComputerName] = $session $this.Logger.Info("Session created successfully to $ComputerName") return $session } catch { $this.Logger.Error("Failed to create session to $ComputerName`: $($_.Exception.Message)") throw } } [object] InvokeCommand([string]$ComputerName, [scriptblock]$ScriptBlock, [hashtable]$ArgumentList = @{}) { if (-not $this.Sessions.ContainsKey($ComputerName)) { $this.CreateSession($ComputerName) } $session = $this.Sessions[$ComputerName] try { $this.Logger.Debug("Executing command on $ComputerName") $result = Invoke-Command -Session $session -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList $this.Logger.Debug("Command executed successfully on $ComputerName") return $result } catch { $this.Logger.Error("Command execution failed on $ComputerName`: $($_.Exception.Message)") # Try to recreate session if it's broken if ($session.State -ne "Opened") { $this.Logger.Warn("Session to $ComputerName is broken, attempting to recreate") $this.RemoveSession($ComputerName) $this.CreateSession($ComputerName) # Retry the command $result = Invoke-Command -Session $this.Sessions[$ComputerName] -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList return $result } throw } } [void] RemoveSession([string]$ComputerName) { if ($this.Sessions.ContainsKey($ComputerName)) { $this.Logger.Info("Removing session to $ComputerName") Remove-PSSession $this.Sessions[$ComputerName] $this.Sessions.Remove($ComputerName) } } [void] RemoveAllSessions() { foreach ($computerName in $this.Sessions.Keys) { $this.RemoveSession($computerName) } } } # Usage example $logger = [Logger]::new("C:\Logs\remote-management.log") $sessionManager = [RemoteSessionManager]::new("C:\Credentials", $logger) # Execute commands on multiple servers $servers = @("WEB-01", "WEB-02", "DB-01") foreach ($server in $servers) { $systemInfo = $sessionManager.InvokeCommand($server, { @{ ComputerName = $env:COMPUTERNAME OS = (Get-CimInstance Win32_OperatingSystem).Caption Memory = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2) Services = (Get-Service | Where-Object Status -eq "Running").Count } }) Write-Output "Server: $($systemInfo.ComputerName)" Write-Output "OS: $($systemInfo.OS)" Write-Output "Memory: $($systemInfo.Memory) GB" Write-Output "Running Services: $($systemInfo.Services)" Write-Output "---" } # Cleanup $sessionManager.RemoveAllSessions()

2. Parallel Execution Framework#

# Parallel execution with progress tracking function Invoke-ParallelCommand { param( [string[]]$ComputerName, [scriptblock]$ScriptBlock, [hashtable]$ArgumentList = @{}, [int]$ThrottleLimit = 10, [PSCredential]$Credential, [switch]$ShowProgress ) $jobs = @() $totalComputers = $ComputerName.Count $completedJobs = 0 # Start jobs foreach ($computer in $ComputerName) { $job = Start-Job -ScriptBlock { param($Computer, $Script, $Args, $Cred) try { $session = New-PSSession -ComputerName $Computer -Credential $Cred $result = Invoke-Command -Session $session -ScriptBlock $Script -ArgumentList $Args Remove-PSSession $session return @{ ComputerName = $Computer Success = $true Result = $result Error = $null } } catch { return @{ ComputerName = $Computer Success = $false Result = $null Error = $_.Exception.Message } } } -ArgumentList $computer, $ScriptBlock, $ArgumentList, $Credential $jobs += $job # Throttle job creation while ((Get-Job -State Running).Count -ge $ThrottleLimit) { Start-Sleep -Milliseconds 100 } } # Wait for jobs and collect results $results = @() while ($jobs) { $completedJob = $jobs | Wait-Job -Any $result = Receive-Job $completedJob Remove-Job $completedJob $results += $result $jobs = $jobs | Where-Object { $_.Id -ne $completedJob.Id } $completedJobs++ if ($ShowProgress) { $percentComplete = ($completedJobs / $totalComputers) * 100 Write-Progress -Activity "Executing commands" -Status "Completed $completedJobs of $totalComputers" -PercentComplete $percentComplete } } if ($ShowProgress) { Write-Progress -Activity "Executing commands" -Completed } return $results } # Usage example $computers = @("WEB-01", "WEB-02", "DB-01", "APP-01", "APP-02") $credential = Get-Credential $results = Invoke-ParallelCommand -ComputerName $computers -ScriptBlock { # Get Windows Update status $updates = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 5 $lastUpdate = $updates[0].InstalledOn @{ LastUpdate = $lastUpdate RecentUpdates = $updates.Count Uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime PendingReboot = Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" } } -Credential $credential -ShowProgress # Process results foreach ($result in $results) { if ($result.Success) { Write-Host "✓ $($result.ComputerName)" -ForegroundColor Green Write-Host " Last Update: $($result.Result.LastUpdate)" Write-Host " Pending Reboot: $($result.Result.PendingReboot)" } else { Write-Host "✗ $($result.ComputerName): $($result.Error)" -ForegroundColor Red } }

Enterprise Automation Patterns#

1. Configuration Management Pipeline#

# Configuration management pipeline class ConfigurationPipeline { [string]$ConfigPath [string]$EnvironmentPath [Logger]$Logger [hashtable]$Environments ConfigurationPipeline([string]$ConfigPath, [string]$EnvPath, [Logger]$Logger) { $this.ConfigPath = $ConfigPath $this.EnvironmentPath = $EnvPath $this.Logger = $Logger $this.LoadEnvironments() } [void] LoadEnvironments() { $this.Environments = @{} Get-ChildItem -Path $this.EnvironmentPath -Filter "*.json" | ForEach-Object { $envName = $_.BaseName $envConfig = Get-Content $_.FullName | ConvertFrom-Json $this.Environments[$envName] = $envConfig } $this.Logger.Info("Loaded $($this.Environments.Count) environment configurations") } [void] ValidateConfiguration([string]$Environment) { if (-not $this.Environments.ContainsKey($Environment)) { throw "Environment '$Environment' not found" } $env = $this.Environments[$Environment] # Validate required properties $requiredProperties = @("servers", "services", "configuration") foreach ($prop in $requiredProperties) { if (-not $env.PSObject.Properties.Name.Contains($prop)) { throw "Environment '$Environment' missing required property: $prop" } } # Validate server connectivity foreach ($server in $env.servers) { if (-not (Test-NetConnection -ComputerName $server -Port 5985 -InformationLevel Quiet)) { throw "Server '$server' is not accessible" } } $this.Logger.Info("Configuration validation passed for environment: $Environment") } [void] DeployConfiguration([string]$Environment, [string]$ConfigurationName) { $this.ValidateConfiguration($Environment) $env = $this.Environments[$Environment] $configFile = Join-Path $this.ConfigPath "$ConfigurationName.ps1" if (-not (Test-Path $configFile)) { throw "Configuration file not found: $configFile" } $this.Logger.Info("Starting deployment of '$ConfigurationName' to '$Environment'") # Load and execute configuration . $configFile # Generate MOF files $mofPath = Join-Path $env:TEMP "DSC_$Environment" if (Test-Path $mofPath) { Remove-Item $mofPath -Recurse -Force } & $ConfigurationName -ComputerName $env.servers -OutputPath $mofPath # Deploy to servers foreach ($server in $env.servers) { try { $this.Logger.Info("Deploying configuration to server: $server") Start-DscConfiguration -ComputerName $server -Path $mofPath -Wait -Verbose # Verify deployment $complianceStatus = Get-DscConfigurationStatus -ComputerName $server if ($complianceStatus.Status -eq "Success") { $this.Logger.Info("Configuration deployed successfully to: $server") } else { $this.Logger.Error("Configuration deployment failed on: $server") } } catch { $this.Logger.Error("Failed to deploy to $server`: $($_.Exception.Message)") } } $this.Logger.Info("Deployment completed for environment: $Environment") } [hashtable] GetComplianceReport([string]$Environment) { $env = $this.Environments[$Environment] $report = @{ Environment = $Environment Servers = @() OverallCompliance = $true } foreach ($server in $env.servers) { try { $status = Get-DscConfigurationStatus -ComputerName $server $serverReport = @{ ServerName = $server Status = $status.Status LastRun = $status.StartDate InDesiredState = $status.InDesiredState Errors = $status.ResourcesNotInDesiredState } if (-not $status.InDesiredState) { $report.OverallCompliance = $false } $report.Servers += $serverReport } catch { $report.Servers += @{ ServerName = $server Status = "Error" Error = $_.Exception.Message } $report.OverallCompliance = $false } } return $report } } # Usage example $logger = [Logger]::new("C:\Logs\config-pipeline.log") $pipeline = [ConfigurationPipeline]::new("C:\DSC\Configurations", "C:\DSC\Environments", $logger) # Deploy to development environment $pipeline.DeployConfiguration("Development", "WebServerConfig") # Get compliance report $complianceReport = $pipeline.GetComplianceReport("Development") $complianceReport | ConvertTo-Json -Depth 3 | Out-File "C:\Reports\compliance-report.json"

2. Automated Patching System#

# Automated patching system with maintenance windows class PatchingSystem { [hashtable]$MaintenanceWindows [Logger]$Logger [string]$ReportPath PatchingSystem([Logger]$Logger, [string]$ReportPath) { $this.Logger = $Logger $this.ReportPath = $ReportPath $this.LoadMaintenanceWindows() } [void] LoadMaintenanceWindows() { # Load maintenance windows from configuration $this.MaintenanceWindows = @{ "Development" = @{ DayOfWeek = "Sunday" StartTime = "02:00" EndTime = "06:00" Servers = @("DEV-WEB-01", "DEV-DB-01") } "Staging" = @{ DayOfWeek = "Sunday" StartTime = "06:00" EndTime = "10:00" Servers = @("STG-WEB-01", "STG-DB-01") } "Production" = @{ DayOfWeek = "Sunday" StartTime = "22:00" EndTime = "02:00" Servers = @("PROD-WEB-01", "PROD-WEB-02", "PROD-DB-01") } } } [bool] IsInMaintenanceWindow([string]$Environment) { $window = $this.MaintenanceWindows[$Environment] $now = Get-Date # Check if today is the maintenance day if ($now.DayOfWeek -ne $window.DayOfWeek) { return $false } $startTime = [datetime]::ParseExact($window.StartTime, "HH:mm", $null) $endTime = [datetime]::ParseExact($window.EndTime, "HH:mm", $null) # Handle overnight maintenance windows if ($endTime -lt $startTime) { return ($now.TimeOfDay -ge $startTime.TimeOfDay -or $now.TimeOfDay -le $endTime.TimeOfDay) } else { return ($now.TimeOfDay -ge $startTime.TimeOfDay -and $now.TimeOfDay -le $endTime.TimeOfDay) } } [hashtable] GetAvailableUpdates([string]$ComputerName) { $session = New-PSSession -ComputerName $ComputerName $updates = Invoke-Command -Session $session -ScriptBlock { # Install PSWindowsUpdate module if not present if (-not (Get-Module -ListAvailable PSWindowsUpdate)) { Install-Module PSWindowsUpdate -Force -Scope AllUsers } Import-Module PSWindowsUpdate $availableUpdates = Get-WUList @{ TotalUpdates = $availableUpdates.Count CriticalUpdates = ($availableUpdates | Where-Object { $_.MsrcSeverity -eq "Critical" }).Count SecurityUpdates = ($availableUpdates | Where-Object { $_.Categories -match "Security" }).Count Updates = $availableUpdates | Select-Object Title, Size, MsrcSeverity, Categories } } Remove-PSSession $session return $updates } [hashtable] InstallUpdates([string]$ComputerName, [bool]$AutoReboot = $false) { $this.Logger.Info("Starting update installation on: $ComputerName") $session = New-PSSession -ComputerName $ComputerName $result = Invoke-Command -Session $session -ScriptBlock { param($AutoReboot) Import-Module PSWindowsUpdate $installResult = if ($AutoReboot) { Get-WUInstall -AcceptAll -AutoReboot -Verbose } else { Get-WUInstall -AcceptAll -Verbose } @{ InstalledUpdates = $installResult.Count RebootRequired = (Get-WURebootStatus -Silent) InstallationTime = Get-Date Details = $installResult | Select-Object Title, Result, Size } } -ArgumentList $AutoReboot Remove-PSSession $session $this.Logger.Info("Update installation completed on: $ComputerName") return $result } [void] ExecutePatchingCycle([string]$Environment) { if (-not $this.IsInMaintenanceWindow($Environment)) { $this.Logger.Warn("Not in maintenance window for environment: $Environment") return } $window = $this.MaintenanceWindows[$Environment] $this.Logger.Info("Starting patching cycle for environment: $Environment") $patchingReport = @{ Environment = $Environment StartTime = Get-Date Servers = @() } foreach ($server in $window.Servers) { try { $this.Logger.Info("Processing server: $server") # Get available updates $availableUpdates = $this.GetAvailableUpdates($server) $serverReport = @{ ServerName = $server AvailableUpdates = $availableUpdates.TotalUpdates CriticalUpdates = $availableUpdates.CriticalUpdates SecurityUpdates = $availableUpdates.SecurityUpdates Status = "Success" } if ($availableUpdates.TotalUpdates -gt 0) { # Install updates $installResult = $this.InstallUpdates($server, $true) $serverReport.InstalledUpdates = $installResult.InstalledUpdates $serverReport.RebootRequired = $installResult.RebootRequired $serverReport.InstallationTime = $installResult.InstallationTime } else { $serverReport.InstalledUpdates = 0 $serverReport.RebootRequired = $false $this.Logger.Info("No updates available for: $server") } $patchingReport.Servers += $serverReport } catch { $this.Logger.Error("Failed to patch server $server`: $($_.Exception.Message)") $patchingReport.Servers += @{ ServerName = $server Status = "Failed" Error = $_.Exception.Message } } } $patchingReport.EndTime = Get-Date $patchingReport.Duration = ($patchingReport.EndTime - $patchingReport.StartTime).TotalMinutes # Save report $reportFile = Join-Path $this.ReportPath "patching-report-$Environment-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" $patchingReport | ConvertTo-Json -Depth 3 | Out-File $reportFile $this.Logger.Info("Patching cycle completed for environment: $Environment") } } # Usage example $logger = [Logger]::new("C:\Logs\patching.log") $patchingSystem = [PatchingSystem]::new($logger, "C:\Reports\Patching") # Execute patching for all environments foreach ($environment in @("Development", "Staging", "Production")) { $patchingSystem.ExecutePatchingCycle($environment) }

Conclusion#

Advanced PowerShell automation enables system administrators to manage complex Windows environments efficiently and reliably. Key takeaways include:

Object-Oriented Approach:

  • Use PowerShell classes for reusable, maintainable code
  • Implement proper error handling and logging
  • Create custom DSC resources for specific requirements

Remote Management Excellence:

  • Implement secure session management
  • Use parallel execution for scalability
  • Handle network failures gracefully

Enterprise Patterns:

  • Build configuration management pipelines
  • Implement automated patching systems
  • Use maintenance windows and compliance reporting

Best Practices:

  • Always implement comprehensive logging
  • Use retry mechanisms for reliability
  • Validate configurations before deployment
  • Monitor and report on automation activities

These advanced techniques will help you build robust, scalable automation solutions that can handle the complexity of modern enterprise environments.

Related Posts