SleepManager.psm1
<#
=========================================================================== Created with: SAPIEN Technologies, Inc., PowerShell Studio 2025 v5.9.256 Created on: 5/27/2025 8:51 PM Created by: aaturpin Organization: Filename: SleepManager.psm1 ------------------------------------------------------------------------- Module Name: SleepManager =========================================================================== #> # Import required modules Import-Module RunLog -Force # Module-level logger variable $script:Logger = $null $script:LoggerIsOwned = $false # Initialize module logger (default behavior if no logger is provided) function Initialize-DefaultLogger { if (-not $script:Logger) { $script:Logger = New-RunLogger -LogFilePath "$env:TEMP\SleepManager.log" -MinimumLogLevel Information $script:LoggerIsOwned = $true } } # Function to set an external logger function Set-SleepManagerLogger { <# .SYNOPSIS Sets a custom RunLogger instance for the SleepManager module. .DESCRIPTION Allows you to provide your own RunLogger instance for the SleepManager module to use instead of creating its own default logger. This enables integration with existing logging infrastructure and custom log configurations. .PARAMETER Logger An existing RunLogger instance to use for logging. .EXAMPLE # Create your own logger with custom settings $myLogger = New-RunLogger -LogFilePath "C:\Logs\MyApp.log" -MinimumLogLevel Debug Set-SleepManagerLogger -Logger $myLogger # Now all SleepManager operations will use your logger Disable-ComputerSleep .EXAMPLE # Use the same logger across multiple modules $sharedLogger = New-RunLogger -LogFilePath "C:\Logs\SharedApp.log" Set-SleepManagerLogger -Logger $sharedLogger # Other modules could also use $sharedLogger for unified logging #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNull()] $Logger ) # Validate that the provided object is actually a RunLogger instance if ($Logger.GetType().Name -ne 'RunLogger') { throw "Logger parameter must be a RunLogger instance. Use New-RunLogger to create one." } $script:Logger = $Logger $script:LoggerIsOwned = $false $script:Logger.Information("External logger configured for SleepManager module") } # Function to get the current logger (useful for debugging or sharing) function Get-SleepManagerLogger { <# .SYNOPSIS Gets the current RunLogger instance used by SleepManager. .DESCRIPTION Returns the RunLogger instance currently being used by the SleepManager module. This can be useful for debugging or for sharing the same logger with other components. .EXAMPLE $currentLogger = Get-SleepManagerLogger $currentLogger.Information("This message will appear in the SleepManager log") #> [CmdletBinding()] param () if (-not $script:Logger) { Initialize-DefaultLogger } return $script:Logger } # Initialize default logger if none is set Initialize-DefaultLogger # Define the Windows API type for sleep control Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; public class SleepControl { [DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags); // Constants for SetThreadExecutionState public const uint ES_CONTINUOUS = 0x80000000; public const uint ES_SYSTEM_REQUIRED = 0x00000001; public const uint ES_DISPLAY_REQUIRED = 0x00000002; } "@ $script:Logger.Information("SleepManager module loaded successfully") function Disable-ComputerSleep { <# .SYNOPSIS Prevents the computer from going to sleep. .DESCRIPTION Uses Windows SetThreadExecutionState API to prevent the system from entering sleep mode. This is process-scoped and will automatically be cleared when PowerShell exits. Also sets up try/finally and trap handlers for additional protection. .PARAMETER KeepDisplayOn If specified, also prevents the display from turning off. .EXAMPLE Disable-ComputerSleep Prevents system sleep but allows display to turn off. .EXAMPLE Disable-ComputerSleep -KeepDisplayOn Prevents both system sleep and display from turning off. #> [CmdletBinding()] param ( [switch]$KeepDisplayOn ) # Ensure logger is initialized if (-not $script:Logger) { Initialize-DefaultLogger } $script:Logger.Information("Attempting to disable computer sleep. KeepDisplayOn: $KeepDisplayOn") try { $flags = [SleepControl]::ES_CONTINUOUS -bor [SleepControl]::ES_SYSTEM_REQUIRED if ($KeepDisplayOn) { $flags = $flags -bor [SleepControl]::ES_DISPLAY_REQUIRED $script:Logger.Debug("Display sleep prevention enabled") } $script:Logger.Debug("Calling SetThreadExecutionState with flags: $flags") $result = [SleepControl]::SetThreadExecutionState($flags) if ($result -eq 0) { $script:Logger.Warning("SetThreadExecutionState returned 0 - failed to disable sleep mode") Write-Warning "Failed to disable sleep mode" return $false } $script:Logger.Information("Successfully disabled computer sleep mode") return $true } catch { $script:Logger.Error("Failed to disable computer sleep", $_.Exception) Write-Error "Failed to disable computer sleep: $($_.Exception.Message)" return $false } } function Enable-ComputerSleep { <# .SYNOPSIS Re-enables computer sleep mode. .DESCRIPTION Clears the thread execution state to allow the system to return to normal power management behavior, including sleep and display timeout. .EXAMPLE Enable-ComputerSleep Restores normal sleep behavior. #> [CmdletBinding()] param () # Ensure logger is initialized if (-not $script:Logger) { Initialize-DefaultLogger } $script:Logger.Information("Attempting to re-enable computer sleep") try { # Clear all execution state flags by setting only ES_CONTINUOUS $script:Logger.Debug("Calling SetThreadExecutionState to clear execution state") $result = [SleepControl]::SetThreadExecutionState([SleepControl]::ES_CONTINUOUS) if ($result -eq 0) { $script:Logger.Warning("SetThreadExecutionState returned 0 - failed to restore sleep mode") Write-Warning "Failed to restore sleep mode" return $false } $script:Logger.Information("Successfully re-enabled computer sleep mode") return $true } catch { $script:Logger.Error("Failed to enable computer sleep", $_.Exception) Write-Error "Failed to enable computer sleep: $($_.Exception.Message)" return $false } } # Register multiple cleanup events to restore sleep when PowerShell exits $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { try { [SleepControl]::SetThreadExecutionState([SleepControl]::ES_CONTINUOUS) | Out-Null if ($script:Logger) { $script:Logger.Information("PowerShell exiting - sleep mode restored via cleanup event") } } catch { # Silent cleanup - don't throw exceptions during shutdown } } -SupportEvent # Also register for process exit (additional safety net) try { $null = Register-ObjectEvent -InputObject ([System.AppDomain]::CurrentDomain) -EventName ProcessExit -Action { try { [SleepControl]::SetThreadExecutionState([SleepControl]::ES_CONTINUOUS) | Out-Null if ($script:Logger) { $script:Logger.Information("Process exiting - sleep mode restored via ProcessExit event") } } catch { # Silent cleanup - don't throw exceptions during shutdown } } -SupportEvent $script:Logger.Debug("ProcessExit event handler registered successfully") } catch { $script:Logger.Warning("ProcessExit event registration failed - continuing without it", $_.Exception) } function Invoke-WithoutSleep { <# .SYNOPSIS Executes a script block with sleep disabled, guaranteeing cleanup. .DESCRIPTION Disables sleep, executes the provided script block, and always re-enables sleep afterward, even if the script block throws an exception. .PARAMETER ScriptBlock The script block to execute with sleep disabled. .PARAMETER KeepDisplayOn If specified, also prevents the display from turning off. .EXAMPLE Invoke-WithoutSleep { Write-Host "Sleep is disabled during this block" Start-Sleep 30 } .EXAMPLE Invoke-WithoutSleep -KeepDisplayOn { # Long running process here & "some-long-process.exe" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ScriptBlock]$ScriptBlock, [switch]$KeepDisplayOn ) # Ensure logger is initialized if (-not $script:Logger) { Initialize-DefaultLogger } $script:Logger.Information("Starting Invoke-WithoutSleep execution. KeepDisplayOn: $KeepDisplayOn") try { $disableResult = Disable-ComputerSleep -KeepDisplayOn:$KeepDisplayOn if (-not $disableResult) { $script:Logger.Warning("Failed to disable sleep, continuing with script block execution anyway") Write-Warning "Failed to disable sleep, continuing anyway..." } $script:Logger.Debug("Executing user script block") # Execute the user's script block & $ScriptBlock $script:Logger.Debug("User script block execution completed") } catch { $script:Logger.Error("Exception occurred during script block execution", $_.Exception) throw } finally { # This ALWAYS runs, even if ScriptBlock throws an exception $script:Logger.Debug("Executing cleanup - re-enabling sleep") $enableResult = Enable-ComputerSleep if ($enableResult) { $script:Logger.Information("Invoke-WithoutSleep completed successfully - sleep mode restored") } else { $script:Logger.Warning("Invoke-WithoutSleep completed but failed to restore sleep mode") } } } # Module cleanup when unloaded $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { try { [SleepControl]::SetThreadExecutionState([SleepControl]::ES_CONTINUOUS) | Out-Null if ($script:Logger) { $script:Logger.Information("SleepManager module unloaded - sleep mode restored") } } catch { # Silent cleanup } } # Export functions Export-ModuleMember -Function Disable-ComputerSleep, Enable-ComputerSleep, Invoke-WithoutSleep, Set-SleepManagerLogger, Get-SleepManagerLogger |