Last active
December 15, 2024 20:55
-
-
Save davidlu1001/b99c4b3a6e52bfbf69e36af09857a422 to your computer and use it in GitHub Desktop.
Service State Monitor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Enhanced Service State Monitor with Automatic gMSA Detection | |
| # Version: 1.0 - PowerShell 5 Compatible | |
| # Supports automated execution via Task Scheduler | |
| #Requires -Version 5.0 | |
| #Requires -RunAsAdministrator | |
| #Requires -Modules ActiveDirectory | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(ValueFromPipeline = $true)] | |
| [string]$serviceName = ".", | |
| [Parameter(ValueFromPipeline = $true)] | |
| [string]$serviceNameNotInclude = "", | |
| [ValidateSet('Running', 'Stopped', 'StartPending', 'StopPending', 'ContinuePending', 'PausePending', 'Paused')] | |
| [string]$checkState, | |
| [ValidateSet('list', 'start', 'stop')] | |
| [string]$ops = "list", | |
| [ValidateScript({ | |
| if ([string]::IsNullOrEmpty($_)) { return $true } | |
| $path = Split-Path -Path $_ -Parent | |
| if (-not (Test-Path -Path $path)) { | |
| New-Item -ItemType Directory -Path $path -Force | Out-Null | |
| } | |
| return $true | |
| })] | |
| [string]$logFile = "C:\temp\scripts\logs\checkServiceState.log", | |
| [switch]$help, | |
| [Parameter(Mandatory = $false)] | |
| [ValidateRange(1, 10)] | |
| [int]$gMSARetryAttempts = 2, | |
| [Parameter(Mandatory = $false)] | |
| [ValidateRange(5, 60)] | |
| [int]$retryDelay = 10, | |
| [Parameter(Mandatory = $false)] | |
| [switch]$includeDependencies, | |
| [Parameter(Mandatory = $false)] | |
| [int]$serviceTimeout = 30 | |
| ) | |
| function ShowHelp { | |
| @" | |
| Windows Service Monitor with gMSA Support | |
| Version 2.1 - PowerShell 5.1 Compatible | |
| DESCRIPTION | |
| Monitors and manages Windows Services with special handling for gMSA accounts. | |
| Detects gMSA services automatically and handles credential refresh when needed. | |
| Supports service dependencies and status monitoring. | |
| SYNTAX | |
| .\ServiceMonitor.ps1 | |
| [-serviceName <String>] | |
| [-serviceNameNotInclude <String>] | |
| [-checkState <String>] | |
| [-ops <String>] | |
| [-logFile <String>] | |
| [-gMSARetryAttempts <Int>] | |
| [-retryDelay <Int>] | |
| [-includeDependencies] | |
| [-serviceTimeout <Int>] | |
| [-help] | |
| PARAMETERS | |
| -serviceName | |
| Service name or pattern to match. Supports regular expressions. | |
| Default: "." (matches all services) | |
| -serviceNameNotInclude | |
| Pattern to exclude services. Supports regular expressions. | |
| Example: "Update|Search" excludes services containing these words | |
| -checkState | |
| Filter services by specific state. | |
| Valid values: Running, Stopped, StartPending, StopPending, | |
| ContinuePending, PausePending, Paused | |
| -ops | |
| Operation to perform. | |
| Valid values: list, start, stop | |
| Default: list | |
| -logFile | |
| Path for the log file. | |
| Default: "C:\temp\scripts\logs\checkServiceState.log" | |
| -gMSARetryAttempts | |
| Number of retry attempts for gMSA reset. | |
| Range: 1-10 | |
| Default: 2 | |
| -retryDelay | |
| Delay between retry attempts in seconds. | |
| Range: 5-60 | |
| Default: 10 | |
| -includeDependencies | |
| Process service dependencies. | |
| Default: False | |
| -serviceTimeout | |
| Timeout in seconds for service operations. | |
| Default: 30 | |
| -help | |
| Displays this help information. | |
| EXAMPLES | |
| # List all services | |
| .\ServiceMonitor.ps1 | |
| # List specific service | |
| .\ServiceMonitor.ps1 -serviceName "SQLServer" | |
| # List all stopped SQL services | |
| .\ServiceMonitor.ps1 -serviceName "SQL" -checkState Stopped -ops list | |
| # Start a specific service | |
| .\ServiceMonitor.ps1 -serviceName "SQLServer" -ops start | |
| # Start service with dependencies | |
| .\ServiceMonitor.ps1 -serviceName "SQLServer" -ops start -includeDependencies | |
| # Stop services matching pattern | |
| .\ServiceMonitor.ps1 -serviceName "SQL" -ops stop | |
| # List services excluding pattern | |
| .\ServiceMonitor.ps1 -serviceName "Windows" -serviceNameNotInclude "Update" | |
| # Start service with custom retry settings | |
| .\ServiceMonitor.ps1 -serviceName "SQLServer" -ops start -gMSARetryAttempts 5 -retryDelay 15 | |
| # Monitor services with custom timeout | |
| .\ServiceMonitor.ps1 -serviceName "Critical" -serviceTimeout 60 | |
| NOTES | |
| - Requires administrative privileges | |
| - Requires ActiveDirectory PowerShell module | |
| - Automatically detects and handles gMSA accounts | |
| - Creates detailed logs of all operations | |
| - Supports regular expressions for service name matching | |
| - Thread-safe operations for concurrent execution | |
| - Handles service dependencies if specified | |
| - Provides detailed operation logging | |
| - Supports automatic retry for failed operations | |
| - Maintains operation status counts | |
| "@ | Write-Output | |
| } | |
| if ($help) { | |
| ShowHelp | |
| return | |
| } | |
| # Initialize script-level variables | |
| $script:serviceCache = @{} | |
| $script:serviceCacheLock = New-Object System.Object | |
| $script:ErrorActionPreference = 'Stop' | |
| $script:VerbosePreference = 'Continue' | |
| # Enhanced logging function with error handling | |
| function Write-Log { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Message, | |
| [ValidateSet('Information', 'Warning', 'Error', 'Debug', 'Success')] | |
| [string]$Level = 'Information', | |
| [switch]$NoConsole | |
| ) | |
| try { | |
| [System.Threading.Monitor]::Enter($script:serviceCacheLock) | |
| $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff" | |
| $logMessage = "$timestamp [$Level] $Message" | |
| if (-not [string]::IsNullOrEmpty($logFile)) { | |
| $logDir = Split-Path -Path $logFile -Parent | |
| if (-not (Test-Path -Path $logDir)) { | |
| New-Item -Path $logDir -ItemType Directory -Force | Out-Null | |
| } | |
| Add-Content -Path $logFile -Value $logMessage -ErrorAction Stop | |
| } | |
| if (-not $NoConsole) { | |
| switch ($Level) { | |
| 'Error' { Write-Host $logMessage -ForegroundColor Red } | |
| 'Warning' { Write-Host $logMessage -ForegroundColor Yellow } | |
| 'Success' { Write-Host $logMessage -ForegroundColor Green } | |
| 'Debug' { Write-Verbose $logMessage } | |
| default { Write-Host $logMessage } | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Error "Failed to write log: $_" | |
| } | |
| finally { | |
| [System.Threading.Monitor]::Exit($script:serviceCacheLock) | |
| } | |
| } | |
| # Thread-safe cache operations | |
| function Get-CacheValue { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Key | |
| ) | |
| try { | |
| [System.Threading.Monitor]::Enter($script:serviceCacheLock) | |
| return $script:serviceCache[$Key] | |
| } | |
| finally { | |
| [System.Threading.Monitor]::Exit($script:serviceCacheLock) | |
| } | |
| } | |
| function Set-CacheValue { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Key, | |
| [Parameter(Mandatory = $true)] | |
| $Value | |
| ) | |
| try { | |
| [System.Threading.Monitor]::Enter($script:serviceCacheLock) | |
| $script:serviceCache[$Key] = $Value | |
| } | |
| finally { | |
| [System.Threading.Monitor]::Exit($script:serviceCacheLock) | |
| } | |
| } | |
| # Function to wait for service status change | |
| function Wait-ServiceStatus { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$ServiceName, | |
| [Parameter(Mandatory = $true)] | |
| [string]$DesiredStatus, | |
| [int]$TimeoutSeconds = 30 | |
| ) | |
| try { | |
| $service = Get-Service -Name $ServiceName | |
| $timer = [System.Diagnostics.Stopwatch]::StartNew() | |
| while ($service.Status -ne $DesiredStatus -and $timer.Elapsed.TotalSeconds -lt $TimeoutSeconds) { | |
| Start-Sleep -Milliseconds 500 | |
| $service.Refresh() | |
| Write-Log -Message "Waiting for service $ServiceName to reach $DesiredStatus state (Current: $($service.Status))" -Level Debug -NoConsole | |
| } | |
| $timer.Stop() | |
| if ($service.Status -ne $DesiredStatus) { | |
| Write-Log -Message "Service $ServiceName did not reach $DesiredStatus state within $TimeoutSeconds seconds" -Level Warning | |
| return $false | |
| } | |
| return $true | |
| } | |
| catch { | |
| Write-Log -Message "Error waiting for service status: $_" -Level Error | |
| return $false | |
| } | |
| } | |
| # Function to detect if an account is a gMSA | |
| function Test-IsGMSAAccount { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$AccountName | |
| ) | |
| try { | |
| if ([string]::IsNullOrEmpty($AccountName)) { | |
| return $false | |
| } | |
| $cacheKey = "GMSA_$AccountName" | |
| $cachedValue = Get-CacheValue -Key $cacheKey | |
| if ($null -ne $cachedValue) { | |
| return $cachedValue | |
| } | |
| $cleanAccountName = $AccountName -replace '^.*\\', '' -replace '\$$', '' | |
| $account = Get-ADServiceAccount -Identity $cleanAccountName -ErrorAction SilentlyContinue | |
| $isGMSA = ($null -ne $account) | |
| Set-CacheValue -Key $cacheKey -Value $isGMSA | |
| return $isGMSA | |
| } | |
| catch { | |
| Write-Log -Message "Error checking if $AccountName is a gMSA: $_" -Level Debug | |
| return $false | |
| } | |
| } | |
| # Function to get service account | |
| function Get-ServiceAccount { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$ServiceName | |
| ) | |
| try { | |
| $cacheKey = "SVC_$ServiceName" | |
| $cachedValue = Get-CacheValue -Key $cacheKey | |
| if ($null -ne $cachedValue) { | |
| return $cachedValue | |
| } | |
| $service = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" -ErrorAction Stop | |
| if ($null -ne $service) { | |
| Set-CacheValue -Key $cacheKey -Value $service.StartName | |
| return $service.StartName | |
| } | |
| } | |
| catch { | |
| Write-Log -Message "Error getting service account for $ServiceName : $_" -Level Error | |
| } | |
| return $null | |
| } | |
| # Function to get service dependencies | |
| function Get-ServiceDependencies { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$ServiceName | |
| ) | |
| try { | |
| $service = Get-Service -Name $ServiceName -ErrorAction Stop | |
| $dependencies = New-Object System.Collections.ArrayList | |
| function Get-DependenciesRecursive { | |
| param ($ServiceObj) | |
| foreach ($depService in $ServiceObj.ServicesDependedOn) { | |
| if (-not $dependencies.Contains($depService.Name)) { | |
| [void]$dependencies.Add($depService.Name) | |
| Get-DependenciesRecursive $depService | |
| } | |
| } | |
| } | |
| Get-DependenciesRecursive $service | |
| return $dependencies.ToArray() | |
| } | |
| catch { | |
| Write-Log -Message "Error getting dependencies for $ServiceName : $_" -Level Error | |
| return @() | |
| } | |
| } | |
| # Function to reset gMSA credentials | |
| function Reset-ServiceGMSACredentials { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$ServiceName, | |
| [Parameter(Mandatory = $true)] | |
| [string]$ServiceAccount, | |
| [Parameter(Mandatory = $false)] | |
| [int]$MaxRetryAttempts = 3, | |
| [Parameter(Mandatory = $false)] | |
| [int]$RetryDelaySeconds = 5, | |
| [Parameter(Mandatory = $false)] | |
| [int]$TimeoutSeconds = 30 | |
| ) | |
| try { | |
| Write-Log -Message "Starting gMSA credential reset for service: $ServiceName" -Level Information | |
| # Validate service exists | |
| $service = Get-Service -Name $ServiceName -ErrorAction Stop | |
| Write-Log -Message "Current service status: $($service.Status)" -Level Debug | |
| # Check if service is using the specified gMSA account | |
| $currentAccount = Get-ServiceAccount -ServiceName $ServiceName | |
| if ($currentAccount -ne $ServiceAccount) { | |
| Write-Log -Message "Service account mismatch. Current: $currentAccount, Expected: $ServiceAccount" -Level Warning | |
| return $false | |
| } | |
| # Stop service and its dependencies | |
| if ($service.Status -ne 'Stopped') { | |
| # Handle dependencies first | |
| if ($includeDependencies) { | |
| $dependencies = Get-ServiceDependencies -ServiceName $ServiceName | |
| foreach ($dep in $dependencies) { | |
| Write-Log -Message "Stopping dependency: $dep" -Level Debug | |
| Stop-Service -Name $dep -Force -ErrorAction SilentlyContinue | |
| Wait-ServiceStatus -ServiceName $dep -DesiredStatus 'Stopped' -TimeoutSeconds $TimeoutSeconds | |
| } | |
| } | |
| # Stop main service | |
| Write-Log -Message "Stopping service $ServiceName" | |
| Stop-Service -Name $ServiceName -Force -ErrorAction Stop | |
| if (-not (Wait-ServiceStatus -ServiceName $ServiceName -DesiredStatus 'Stopped' -TimeoutSeconds $TimeoutSeconds)) { | |
| throw "Failed to stop service $ServiceName within timeout period" | |
| } | |
| } | |
| # Reset service password using sc.exe | |
| Write-Log -Message "Resetting service password" | |
| $scCommand = "sc.exe config `"$ServiceName`" obj= `"$ServiceAccount`" password= `"`"" | |
| $scResult = Invoke-Expression -Command $scCommand 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to reset service password. Error: $scResult" | |
| } | |
| # Attempt to start the service with retries | |
| $attempt = 1 | |
| $serviceStarted = $false | |
| while (-not $serviceStarted -and $attempt -le $MaxRetryAttempts) { | |
| Write-Log -Message "Start attempt $attempt of $MaxRetryAttempts" | |
| try { | |
| # Start dependencies first if needed | |
| if ($includeDependencies) { | |
| foreach ($dep in $dependencies) { | |
| Start-Service -Name $dep -ErrorAction SilentlyContinue | |
| Wait-ServiceStatus -ServiceName $dep -DesiredStatus 'Running' -TimeoutSeconds $TimeoutSeconds | |
| } | |
| } | |
| # Start main service | |
| Start-Service -Name $ServiceName -ErrorAction Stop | |
| if (Wait-ServiceStatus -ServiceName $ServiceName -DesiredStatus 'Running' -TimeoutSeconds $TimeoutSeconds) { | |
| $serviceStarted = $true | |
| Write-Log -Message "Service started successfully" -Level Success | |
| break | |
| } | |
| } | |
| catch { | |
| Write-Log -Message "Attempt $attempt failed: $_" -Level Warning | |
| if ($attempt -lt $MaxRetryAttempts) { | |
| Start-Sleep -Seconds $RetryDelaySeconds | |
| } | |
| } | |
| $attempt++ | |
| } | |
| if (-not $serviceStarted) { | |
| throw "Failed to start service after $MaxRetryAttempts attempts" | |
| } | |
| return $true | |
| } | |
| catch { | |
| Write-Log -Message "Error during gMSA credential reset: $_" -Level Error | |
| return $false | |
| } | |
| } | |
| # Function to manage service operations | |
| function Manage-Service { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$ServiceName, | |
| [Parameter(Mandatory = $true)] | |
| [ValidateSet('start', 'stop', 'list')] | |
| [string]$Operation, | |
| [Parameter(Mandatory = $true)] | |
| [string]$DesiredState | |
| ) | |
| try { | |
| $service = Get-Service -Name $ServiceName | |
| $serviceAccount = Get-ServiceAccount -ServiceName $ServiceName | |
| $isGMSA = Test-IsGMSAAccount -AccountName $serviceAccount | |
| Write-Log -Message "Processing service: $ServiceName, Account: $serviceAccount, Is gMSA: $isGMSA" -Level Debug | |
| switch ($Operation) { | |
| 'start' { | |
| if ($service.Status -eq 'Stopped') { | |
| if ($includeDependencies) { | |
| $dependencies = Get-ServiceDependencies -ServiceName $ServiceName | |
| foreach ($dep in $dependencies) { | |
| Manage-Service -ServiceName $dep -Operation 'start' -DesiredState 'Running' | |
| } | |
| } | |
| Write-Log -Message "Attempting to start service: $ServiceName" | |
| Start-Service -Name $ServiceName -ErrorAction SilentlyContinue | |
| Start-Sleep -Seconds 2 | |
| $service.Refresh() | |
| if ($service.Status -ne 'Running' -and $isGMSA) { | |
| Write-Log -Message "Normal start failed, attempting gMSA reset" | |
| for ($i = 1; $i -le $gMSARetryAttempts; $i++) { | |
| Write-Log -Message "gMSA reset attempt $i of $gMSARetryAttempts" -Level Debug | |
| if (Reset-ServiceGMSACredentials -ServiceName $ServiceName -ServiceAccount $serviceAccount) { | |
| return $true | |
| } | |
| if ($i -lt $gMSARetryAttempts) { | |
| Start-Sleep -Seconds $retryDelay | |
| } | |
| } | |
| return $false | |
| } | |
| $startResult = Wait-ServiceStatus -ServiceName $ServiceName -DesiredStatus 'Running' -TimeoutSeconds $serviceTimeout | |
| return $startResult | |
| } | |
| return $true | |
| } | |
| 'stop' { | |
| if ($service.Status -eq 'Running') { | |
| if ($includeDependencies) { | |
| $dependentServices = Get-Service | | |
| Where-Object { $_.ServicesDependedOn | | |
| Where-Object { $_.Name -eq $ServiceName } | |
| } | |
| foreach ($depService in $dependentServices) { | |
| Stop-Service -Name $depService.Name -Force -ErrorAction SilentlyContinue | |
| Wait-ServiceStatus -ServiceName $depService.Name -DesiredStatus 'Stopped' -TimeoutSeconds $serviceTimeout | |
| } | |
| } | |
| Stop-Service -Name $ServiceName -Force -ErrorAction Stop | |
| $stopResult = Wait-ServiceStatus -ServiceName $ServiceName -DesiredStatus 'Stopped' -TimeoutSeconds $serviceTimeout | |
| return $stopResult | |
| } | |
| return $true | |
| } | |
| 'list' { return $true } | |
| } | |
| } | |
| catch { | |
| Write-Log -Message "Error managing service $ServiceName : $_" -Level Error | |
| return $false | |
| } | |
| } | |
| # Show help if requested | |
| if ($help) { | |
| . $PSScriptRoot\ShowHelp.ps1 | |
| return | |
| } | |
| # Main execution block continued | |
| try { | |
| Write-Log -Message "Script started with operation: $ops" | |
| # Input validation | |
| if ($serviceName -match '[^\w\d\.\-\*\?\[\]\(\)\|\\]') { | |
| throw "Invalid service name pattern detected. Please use valid characters only." | |
| } | |
| # Get matching services with error handling | |
| $services = @() | |
| if (-not [string]::IsNullOrEmpty($serviceNameNotInclude)) { | |
| $services = @(Get-Service | Where-Object { | |
| $_.DisplayName -match $serviceName -and | |
| $_.DisplayName -notmatch $serviceNameNotInclude | |
| }) | |
| } | |
| else { | |
| $services = @(Get-Service | Where-Object { | |
| $_.DisplayName -match $serviceName | |
| }) | |
| } | |
| if ($services.Count -eq 0) { | |
| Write-Log -Message "No services found matching criteria" -Level Warning | |
| return | |
| } | |
| Write-Log -Message "Found $($services.Count) matching services" -Level Debug | |
| # Process services based on operation | |
| switch ($ops) { | |
| "list" { | |
| $targetServices = @() | |
| if ($checkState) { | |
| $targetServices = @($services | Where-Object { $_.Status -eq $checkState }) | |
| } | |
| else { | |
| $targetServices = $services | |
| } | |
| foreach ($svc in $targetServices) { | |
| $account = Get-ServiceAccount -ServiceName $svc.Name | |
| $isGMSA = Test-IsGMSAAccount -AccountName $account | |
| $dependencies = @() | |
| if ($includeDependencies) { | |
| $dependencies = Get-ServiceDependencies -ServiceName $svc.Name | |
| } | |
| $dependenciesText = if ($dependencies.Count -gt 0) { | |
| $dependencies -join ', ' | |
| } | |
| else { | |
| 'None' | |
| } | |
| $serviceDetails = @" | |
| Service Details: | |
| Name: $($svc.DisplayName) | |
| Service Name: $($svc.Name) | |
| Status: $($svc.Status) | |
| Account: $account | |
| Is gMSA: $isGMSA | |
| Dependencies: $dependenciesText | |
| "@ | |
| Write-Log -Message $serviceDetails | |
| } | |
| Write-Log -Message "Listed $($targetServices.Count) services" -Level Success | |
| } | |
| "start" { | |
| $servicesToStart = @($services | Where-Object { $_.Status -eq 'Stopped' }) | |
| if ($servicesToStart.Count -gt 0) { | |
| $successCount = 0 | |
| $failCount = 0 | |
| foreach ($svc in $servicesToStart) { | |
| Write-Log -Message "Processing start operation for $($svc.DisplayName)" | |
| $result = Manage-Service -ServiceName $svc.Name -Operation 'start' -DesiredState 'Running' | |
| if ($result) { | |
| $successCount++ | |
| Write-Log -Message "Successfully started service: $($svc.DisplayName)" -Level Success | |
| } | |
| else { | |
| $failCount++ | |
| Write-Log -Message "Failed to start service: $($svc.DisplayName)" -Level Error | |
| } | |
| } | |
| Write-Log -Message "Start operations completed. Success: $successCount, Failed: $failCount" -Level Information | |
| } | |
| else { | |
| Write-Log -Message "No stopped services found to start" -Level Information | |
| } | |
| } | |
| "stop" { | |
| $servicesToStop = @($services | Where-Object { $_.Status -eq 'Running' }) | |
| if ($servicesToStop.Count -gt 0) { | |
| $successCount = 0 | |
| $failCount = 0 | |
| foreach ($svc in $servicesToStop) { | |
| Write-Log -Message "Processing stop operation for $($svc.DisplayName)" | |
| $result = Manage-Service -ServiceName $svc.Name -Operation 'stop' -DesiredState 'Stopped' | |
| if ($result) { | |
| $successCount++ | |
| Write-Log -Message "Successfully stopped service: $($svc.DisplayName)" -Level Success | |
| } | |
| else { | |
| $failCount++ | |
| Write-Log -Message "Failed to stop service: $($svc.DisplayName)" -Level Error | |
| } | |
| } | |
| Write-Log -Message "Stop operations completed. Success: $successCount, Failed: $failCount" -Level Information | |
| } | |
| else { | |
| Write-Log -Message "No running services found to stop" -Level Information | |
| } | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Log -Message "Critical error in script execution: $_" -Level Error | |
| throw $_ | |
| } | |
| finally { | |
| # Cleanup | |
| try { | |
| if ($script:serviceCache) { | |
| $script:serviceCache.Clear() | |
| } | |
| Write-Log -Message "Script execution completed" -Level Information | |
| } | |
| catch { | |
| Write-Error "Error during cleanup: $_" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment