esPreSso.psm1

#Region '.\Private\ConvertKey-ToSendKeys.ps1' -1

function ConvertKey-ToSendKeys {
    <#
    .SYNOPSIS
    Converts a chosen key to appropriate format for use with the WScript SendKeys method
    .DESCRIPTION
    Most keys from the keyboard can simply be passed to SendKeys but some require special formatting. This function is meant to handle the special circumstances
    .PARAMETER Key
    the key to convert
    #>

    [CmdletBinding()]
    param (
        [String]$Key
    )


    $SpecialKeys = @{
        ')'             = '{)}'
        '('             = '{(}'
        '~'             = '{~}'
        '^'             = '{^}'
        '+'             = '{+}'
        '%'             = '{%}'
        Shift           = '+'
        Backspace       = '{Backspace}'
        Break           = '{Break}'
        CapsLock        = '{CapsLock}'
        Delete          = '{Delete}'
        Down            = '{Down}'
        End             = '{End}'
        Enter           = '{Enter}'
        Esc             = '{Esc}'
        Help            = '{Help}'
        Home            = '{Home}'
        Insert          = '{Insert}'
        Left            = '{Left}'
        Numlock         = '{Numlock}'
        PageDown        = '{PGDN}'
        PageUp          = '{PGUP}'
        PrintScreen     = '{PRTSC}'
        Right           = '{Right}'
        ScrollLock      = '{ScrollLock}'
        Tab             = '{Tab}'
        Up              = '{Up}'
        F1              = '{F1}'
        F2              = '{F2}'
        F3              = '{F3}'
        F4              = '{F4}'
        F5              = '{F5}'
        F6              = '{F6}'
        F7              = '{F7}'
        F8              = '{F8}'
        F9              = '{F9}'
        F10             = '{F10}'
        F11             = '{F11}'
        F12             = '{F12}'
        F13             = '{F13}'
        F14             = '{F14}'
        F15             = '{F15}'
        F16             = '{F16}'
    }

    if ($SpecialKeys[$Key]) {
        $SpecialKeys[$Key]
    } else {
        $Key
    }
}
#EndRegion '.\Private\ConvertKey-ToSendKeys.ps1' 68
#Region '.\Public\Get-KeepAwake.ps1' -1

function Get-KeepAwake {
    <#
    .SYNOPSIS
    Checks the registered scheduled tasks for the esPreSso job that runs Start-KeepAwake
    .DESCRIPTION
    Checks the registered scheduled tasks for the esPreSso job that runs Start-KeepAwake and return an object representing its settings
    .EXAMPLE
    PS> Get-KeepAwake
 
    TaskPath TaskName State
    -------- -------- -----
    \Microsoft\Windows\PowerShell\ScheduledJobs\ esPreSso Ready
 
    #>

    [CmdletBinding()]
    param ()

    $TaskName = "esPreSso"
    $TaskPath = "\Microsoft\Windows\PowerShell\ScheduledJobs\"
    try {
        Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction Stop
    } catch {
        Write-Verbose "esPreSso scheduled task not found"
    }
}
#EndRegion '.\Public\Get-KeepAwake.ps1' 26
#Region '.\Public\Get-Keys.ps1' -1

function Get-Keys {
    <#
    .SYNOPSIS
    Return an array of keys that be specified with the -Key parameter of Start-KeepAwake
    .DESCRIPTION
    Returns an array of possible keys that can be used with the -Key parameter of Start-KeepAwake
    .EXAMPLE
    PS> Get-Keys
    !
    "
    #
    $
    %
    &
    '
    (
    )
    *
    +
    ...
 
    truncated example output as this will print 131 possible keys. This public function is mostly used in conjunction with Start-KeepAwake to support auto completion with the -Key parameter
    #>

    [Cmdletbinding()]
    param ()

    $Keys = [System.Collections.ArrayList]::new()
    33..126 | Foreach-Object {
        [Void]$Keys.Add([char]$_)
    }
    $SpecialKeys = @(
        "Shift",
        "Backspace",
        "Break",
        "CapsLock",
        "Delete",
        "Down",
        "End",
        "Enter",
        "Esc",
        "Help",
        "Home",
        "Insert",
        "Left",
        "Numlock",
        "PageDown",
        "PageUp",
        "PrintScreen",
        "Right",
        "ScrollLock",
        "Tab",
        "Up",
        "F1",
        "F2",
        "F3",
        "F4",
        "F5",
        "F6",
        "F7",
        "F8",
        "F9",
        "F10",
        "F11",
        "F12",
        "F13",
        "F14",
        "F15",
        "F16"
    )
    $SpecialKeys | Foreach-Object {
        [Void]$Keys.Add($_)
    }
    return $Keys
}
#EndRegion '.\Public\Get-Keys.ps1' 75
#Region '.\Public\Register-KeepAwake.ps1' -1

function Register-KeepAwake {
    <#
    .SYNOPSIS
    registers a scheduled job to run Start-KeepAwake at user login
    .DESCRIPTION
    registers a scheduled job to run Start-KeepAwake at user login. Allows control over the Start-KeepAwake settings as well as some scheduled job settings.
    .EXAMPLE
    PS> Register-KeepAwake
 
    when ran in an administrative PowerShell session this will create a scheduled task that runs at logon and starts Start-KeepAwake with the -PowerControl parameter
    #>

    [CmdletBinding()]
    param ()

    # check if we're running as admin and quit if not

    $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
    if ($IsAdmin) {
        # get the currently logged in user in case the PowerShell session was elevated via a different username than the one logged in to the computer
        $UserName = (Get-CimInstance -Class Win32_ComputerSystem).Username
        $ActionArgs = 'powershell.exe -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -Command "& {Start-KeepAwake -PowerControl}"'
        $TaskSettings = @{
                    Action = $(New-ScheduledTaskAction -Execute "Conhost.exe" -Argument $ActionArgs)
                    Principal = $(New-ScheduledTaskPrincipal -UserId $Username -LogonType Interactive)
                    TaskName = "esPreSso"
                    TaskPath = "\Microsoft\Windows\PowerShell\ScheduledJobs\"
                    Trigger = $(New-ScheduledTaskTrigger -AtLogOn -User $UserName)
                }
        $ExistingTask = Get-KeepAwake
        if ($ExistingTask) {
            Remove-KeepAwake
        }
        Register-ScheduledTask @TaskSettings
    } else {
        Write-Warning "Registering a scheduled task requires elevation. Please re-run PowerShell with 'run as administrator' and try again."
    }
}
#EndRegion '.\Public\Register-KeepAwake.ps1' 38
#Region '.\Public\Remove-KeepAwake.ps1' -1

function Remove-KeepAwake {
    <#
    .SYNOPSIS
    Removes the esPreSso scheduled task from Task Scheduler
    .DESCRIPTION
    Unregisters the scheduled task created by Register-KeepAwake. There is no output.
    .EXAMPLE
    PS> Remove-KeepAwake
 
    if there is an existing scheduled task for esPreSso this will cleanly remove it from Task Scheduler.
    #>

    [CmdletBinding()]
    param ()

    $ScheduledTask = Get-KeepAwake
    if ($ScheduledTask) {
        $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
        if ($IsAdmin) {
            Write-Verbose "Removing scheduled task named $($ScheduledTask.TaskName) in $($ScheduledTask.TaskPath)"
            Unregister-ScheduledTask -InputObject $ScheduledTask -Confirm:$false
        } else {
            Write-Warning "Removing a scheduled task requires elevation. Please re-run PowerShell and 'run as administrator' and try again."
        }
    } else {
        Write-Warning "No scheduled task for esPreSso found"
    }
}
#EndRegion '.\Public\Remove-KeepAwake.ps1' 28
#Region '.\Public\Start-KeepAwake.ps1' -1

function Start-KeepAwake {
    <#
    .SYNOPSIS
    This function attempts to keep the computer awake by sending a key-press every 60 seconds (by default)
    .DESCRIPTION
    Using the WScript namespace to send keys this function will send a key combination of F15 every 60 seconds by default to keep the computer awake and preven the screensaver
    from activating.
    .PARAMETER Minutes
    Number of minutes to run the keep awake
    .PARAMETER Hours
    Number of hours to run the keep awake
    .PARAMETER Until
    A date time representing when to stop running the keep awake. Accepts common datetime formats
    .PARAMETER Interval
    The interval for how often a key press is sent. Default is 60 seconds and this should be fine for most scenarios.
    .PARAMETER Key
    Optionally can specify which keypress to send. The default, and most widely tested is F15, but just about any key on the keyboard can be specified. Supports tab completion to see which keys can be selected
    .PARAMETER PowerControl
    Switch parameter that tells Start-KeepAwake to register a request via SetThreadExecutionState to keep the display awake and prevent sleep.
    Cancelled with Ctrl+C or other terminating signal.
    .EXAMPLE
    PS> Start-KeepAwake
 
    When ran with no parameters the function will attempt to keep the computer awake indefinitely until cancelled.
    .EXAMPLE
    PS> Start-KeepAwake -Until "3:00pm"
 
    will send a keypress of F15 every minute until 3:00 pm the same day
    .EXAMPLE
    PS> Start-KeepAwake -Key Shift -Interval 10
 
    will send a press of the 'shift' key every 10 seconds
    .EXAMPLE
    PS> Start-KeepAwake -PowerControl
 
    Will also attempt to keep the computer awake indefinitely but won't send any key presses.
    .NOTES
    Credit to marioraulperez for his class definition: https://github.com/marioraulperez/keep-windows-awake
    #>

    [CmdletBinding(DefaultParameterSetName = 'Manual')]
    [Alias("nosleep","ka")]
    Param(
        [Parameter(Position=1, ParameterSetName='Manual')]
        [Alias("m")]
        [Int32]$Minutes,
        [Parameter(Position=0, ParameterSetName='Manual')]
        [Alias("h")]
        [Int32]$Hours,
        [Parameter(ParameterSetName='Until')]
        [Alias("u")]
        [DateTime]$Until,
        [Parameter(ParameterSetName='Manual')]
        [Parameter(ParameterSetName='Until')]
        [ValidateRange(1,86400)]
        [Int32]$Interval = 60,
        [Parameter(ParameterSetName='Manual')]
        [Parameter(ParameterSetName='Until')]
        [ArgumentCompleter({
            param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)
            $ValidValues = Get-Keys
            $ValidValues -like "$WordToComplete*"
        })]
        [ValidateScript({
            if ($_ -in (Get-Keys)) {
                $true
            } else {
                throw "$_ is not a valid key option"
            }
        })]
        [String]$Key = 'F15',
        [Parameter(ParameterSetName='PowerPlan')]
        [Switch]$PowerControl
    )

    if ($PowerControl) {
            try {
                $Definition = @"
using System;
using System.Runtime.InteropServices;
 
public class PowerCtrl {
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern uint SetThreadExecutionState(uint esFlags);
 
    public static void PreventSleep() {
        // ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
        SetThreadExecutionState(0x80000002 | 0x00000001 | 0x00000002);
    }
 
    public static void AllowSleep() {
        // ES_CONTINUOUS
        SetThreadExecutionState(0x80000000);
    }
}
"@

                Add-Type -Language CSharp -TypeDefinition $Definition
                Write-Verbose "Preventing sleep via PowerCtrl"
                $BackgroundJob = Start-Job -Name "esPreSso" -ScriptBlock {
                    Add-Type -Language CSharp -TypeDefinition $args[0]
                    [PowerCtrl]::PreventSleep()
                    while ($true) {
                        Start-Sleep -Seconds 60
                    }
                } -ArgumentList $Definition
                Wait-Job -Job $BackgroundJob
            } catch {
                Write-Error $_
            } finally {
                Write-Verbose "Removing PowerCtrl"
                Stop-Job -Job $BackgroundJob
                Remove-Job -Job $BackgroundJob
                [PowerCtrl]::AllowSleep()
            }

    } else {
        $TSParams = @{}
        switch ($PSBoundParameters.Keys) {
            'Minutes' {
                $TSParams.Add('Minutes', $Minutes)
                Write-Verbose "Adding $Minutes minutes of duration"
            }
            'Hours' {
                $TSParams.Add('Hours', $Hours)
                Write-Verbose "Adding $Hours hours of duration"
            }
            'Until' {
                Write-Verbose "Stopping time provided of: $($Until.ToShortTimeString())"
                $UntilDuration = ($Until - (Get-Date)).TotalMinutes
                If ($UntilDuration -lt 1) {
                    $UntilDuration = 1
                }
                Write-Verbose "Adding $UntilDuration minutes of duration"
                $TSParams.Add('Minutes', $UntilDuration)
            }
        }
        if (-not $TSParams.Count) {
            Write-Verbose "Defaulting to indefinite runtime"
            $Duration = $true
        } else {
            $Duration = (New-TimeSpan @TSParams).TotalMinutes
            Write-Verbose "Total duration is $Duration minutes"
        }

        $WShell = New-Object -ComObject WScript.Shell
        Write-Verbose $('Keeping computer awake by sending "{0}" every {1} seconds' -f $Key, $Interval)
        $SendKeyValue = ConvertKey-ToSendKeys -Key $Key
        while ($RunTime -le $Duration) {
            if ($Duration.GetType().Name -ne "Boolean") {
                Write-Verbose $('{0:n2} minutes remaining' -f ($Duration - $Runtime))
                $Runtime += $($Interval/60)
            }
            $WShell.SendKeys($SendKeyValue)
            Start-Sleep -seconds $Interval
        }
    }
}
#EndRegion '.\Public\Start-KeepAwake.ps1' 157