Runtime/OSDAppSleepPrevention.ps1

# Shared by the standalone runtimes and the isolated development-PC test.
# Loading this file does not compile native code or create a power request.
function Get-OSDAppPreventSleep {
    param([Parameter(Mandatory)]$Runtime)

    $property = $Runtime.PSObject.Properties['PreventSleep']
    if ($null -eq $property) { return $true }
    if ($property.Value -isnot [bool]) {
        throw 'Device manifest Runtime.PreventSleep must be a JSON boolean.'
    }
    return $property.Value
}

function New-OSDAppPowerRequest {
    param([Parameter(Mandatory)][string]$Reason)

    if (-not ('OSDApps.Runtime.SystemPowerRequest' -as [type])) {
        Add-Type -TypeDefinition @'
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
 
namespace OSDApps.Runtime
{
    public sealed class SystemPowerRequest : SafeHandleZeroOrMinusOneIsInvalid
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct DetailedReason
        {
            public IntPtr Module;
            public uint ReasonId;
            public uint StringCount;
            public IntPtr Strings;
        }
 
        [StructLayout(LayoutKind.Explicit)]
        private struct ReasonUnion
        {
            [FieldOffset(0)] public IntPtr Simple;
            [FieldOffset(0)] public DetailedReason Detailed;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct ReasonContext
        {
            public uint Version;
            public uint Flags;
            public ReasonUnion Reason;
        }
 
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr PowerCreateRequest(ref ReasonContext context);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool PowerSetRequest(SystemPowerRequest request, int type);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool PowerClearRequest(SystemPowerRequest request, int type);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);
 
        private SystemPowerRequest() : base(true) { }
 
        public static SystemPowerRequest Create(string reason)
        {
            var request = new SystemPowerRequest();
            IntPtr text = Marshal.StringToHGlobalUni(reason);
            try
            {
                var context = new ReasonContext();
                context.Flags = 1; // POWER_REQUEST_CONTEXT_SIMPLE_STRING
                context.Reason.Simple = text;
                request.SetHandle(PowerCreateRequest(ref context));
                if (request.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
                // SystemRequired only: allow the display to turn off normally.
                if (!PowerSetRequest(request, 1)) throw new Win32Exception(Marshal.GetLastWin32Error());
                return request;
            }
            catch
            {
                request.Dispose();
                throw;
            }
            finally { Marshal.FreeHGlobal(text); }
        }
 
        public void Release()
        {
            if (IsClosed || IsInvalid) return;
            try
            {
                if (!PowerClearRequest(this, 1)) throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            finally { Dispose(); }
        }
 
        // Also closes the handle if the caller fails to dispose this object.
        // On forced process termination Windows closes its owned handles.
        protected override bool ReleaseHandle() { return CloseHandle(handle); }
    }
}
'@
 -ErrorAction Stop
    }
    [OSDApps.Runtime.SystemPowerRequest]::Create($Reason)
}

function Write-OSDAppSleepPreventionLog {
    param([scriptblock]$Log, [string]$EventName, [string]$Level = 'Info', [string]$Message)

    # Logging must never leak an acquired request or change a deployment exit code.
    try {
        if ($Log) { $null = & $Log $EventName $Level $Message }
        elseif ($Level -eq 'Warning') { Write-Warning "$EventName`: $Message" -WarningAction Continue }
    }
    catch { Write-Warning "$EventName`: $Message (Logging failed: $($_.Exception.Message))" -WarningAction Continue }
}

function Start-OSDAppSleepPrevention {
    [CmdletBinding()]
    param(
        [bool]$Enabled = $true,
        [Parameter(Mandatory)][string]$Reason,
        [scriptblock]$Log
    )

    if (-not $Enabled) {
        Write-OSDAppSleepPreventionLog -Log $Log -EventName 'SleepPreventionDisabled' -Message 'Automatic sleep prevention is disabled for this deployment.'
        return $null
    }
    try { $request = New-OSDAppPowerRequest -Reason $Reason }
    catch {
        Write-OSDAppSleepPreventionLog -Log $Log -EventName 'SleepPreventionFailed' -Level Warning -Message "Could not request automatic sleep prevention; continuing deployment. $($_.Exception.Message)"
        return $null
    }
    Write-OSDAppSleepPreventionLog -Log $Log -EventName 'SleepPreventionStarted' -Message $Reason
    return $request
}

function Stop-OSDAppSleepPrevention {
    [CmdletBinding()]
    param([AllowNull()]$Request, [scriptblock]$Log)

    if ($null -eq $Request) { return }
    try {
        $Request.Release()
        Write-OSDAppSleepPreventionLog -Log $Log -EventName 'SleepPreventionStopped' -Message 'The temporary power request was released.'
    }
    catch {
        Write-OSDAppSleepPreventionLog -Log $Log -EventName 'SleepPreventionReleaseFailed' -Level Warning -Message "Could not cleanly release the temporary power request. $($_.Exception.Message)"
    }
}