scripts/Suspend-Action.ps1

<#
.SYNOPSIS
    Suspends (sleeps or hibernates) the local computer.
 
.DESCRIPTION
    Self-contained suspend implementation for Bsn.SystemManagement. It is
    executed directly on the local computer and shipped as-is to remote
    computers by Suspend-Computer via Invoke-Command -FilePath, so it must
    not depend on any other module file.
 
    Platform implementation:
    - Windows: P/Invoke of the documented SetSuspendState API in powrprof.dll.
    - Linux: systemctl suspend/hibernate, loginctl as fallback.
    - macOS: pmset sleepnow (hibernate is not supported).
 
.NOTES
    Part of Bsn.SystemManagement - do not dot-source from the module loader.
#>

[CmdletBinding()]
param(
    [bool]$Hibernate,

    [bool]$Force
)

$ErrorActionPreference = 'Stop'

function Get-TargetPlatform {
    # PSVersionTable keys 'Platform' and 'OS' do not exist on Windows
    # PowerShell 5.1 - guard with ContainsKey to stay StrictMode-safe.
    $hasPlatformKey = $PSVersionTable.ContainsKey('Platform')

    if ($hasPlatformKey -and ($PSVersionTable['Platform'] -eq 'Win32NT')) {
        return 'Windows'
    }

    if ($hasPlatformKey -and ($PSVersionTable['Platform'] -eq 'Unix')) {
        if ($PSVersionTable.ContainsKey('OS') -and ($PSVersionTable['OS'] -match 'Darwin')) {
            return 'macOS'
        }
        return 'Linux'
    }

    # Windows PowerShell 5.1 does not define $PSVersionTable.Platform.
    if ($env:OS -eq 'Windows_NT') {
        return 'Windows'
    }

    return 'Unknown'
}

$platform = Get-TargetPlatform

switch ($platform) {
    'Windows' {
        if (-not ('Bsn.SystemManagement.Interop.PowerProf' -as [type])) {
            Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
 
namespace Bsn.SystemManagement.Interop
{
    /// <summary>P/Invoke wrapper for the documented Windows power APIs.</summary>
    public static class PowerProf
    {
        [DllImport("powrprof.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
    }
}
'@

        }

        $succeeded = [Bsn.SystemManagement.Interop.PowerProf]::SetSuspendState($Hibernate, $Force, $false)
        if (-not $succeeded) {
            $errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
            throw ("SetSuspendState failed with Win32 error code {0}." -f $errorCode)
        }
    }

    'Linux' {
        $action = if ($Hibernate) { 'hibernate' } else { 'suspend' }

        if ($Hibernate) {
            $powerStates = Get-Content -LiteralPath '/sys/power/state' -ErrorAction 'SilentlyContinue'
            if (-not ($powerStates -match 'disk')) {
                throw 'Hibernate was requested but this Linux system does not expose the "disk" power state (hibernation is not available).'
            }
        }

        foreach ($controller in @('systemctl', 'loginctl')) {
            $controllerCommand = Get-Command -Name $controller -ErrorAction 'SilentlyContinue'
            if (-not $controllerCommand) {
                continue
            }

            & $controllerCommand.Source $action
            if ($LASTEXITCODE -ne 0) {
                throw ("{0} {1} failed with exit code {2}." -f $controller, $action, $LASTEXITCODE)
            }
            return
        }

        throw 'No supported suspend mechanism was found (expected systemctl or loginctl).'
    }

    'macOS' {
        if ($Hibernate) {
            throw 'Hibernate is not supported by Suspend-Computer on macOS; use sleep instead.'
        }

        & pmset sleepnow
        if ($LASTEXITCODE -ne 0) {
            throw ("pmset sleepnow failed with exit code {0}." -f $LASTEXITCODE)
        }
    }

    default {
        throw ("Suspend is not supported on this platform ('{0}')." -f $platform)
    }
}