Private/Invoke-ScheduledTask.ps1

function Invoke-ScheduledTask {

    #Requires -Version 3
    
    [cmdletbinding()]
    Param(
    [Parameter(Mandatory = $true)][ScriptBlock]$ScriptBlock,
    [Parameter(Mandatory = $false)][Object[]]$ArgumentList,
    [Parameter(Mandatory = $false)][System.Management.Automation.PSCredential]$AsCredential,
    [Parameter(Mandatory = $false)][Switch]$AsSystem,
    [Parameter(Mandatory = $false)][String]$AsGMSA,
    [Parameter(Mandatory = $false)][Switch]$RunElevated

    )

    Begin { 
    
        $JobName = [guid]::NewGuid().Guid 

    }

    Process {
    
        Try {

            $JobParameters = @{ }
            $JobParameters['Name'] = $JobName
            If ($RunElevated)  { $JobParameters['ScheduledJobOption'] = New-ScheduledJobOption -RunElevated }

            $JobArgumentList = @{ }
            If ($ScriptBlock)  { $JobArgumentList['ScriptBlock']  = $ScriptBlock }
            If ($ArgumentList) { $JobArgumentList['ArgumentList'] = $ArgumentList }

            # Little bit of inception to get $Using variables to work.
            # Collect $Using:variables, Rename and set new variables inside the job.

            # Inspired by Boe Prox, and his https://github.com/proxb/PoshRSJob module
            # and by Warren Framem and his https://github.com/RamblingCookieMonster/Invoke-Parallel module

            $JobArgumentList['Using'] = @()
            $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True)
            If ($UsingVariables) {

                $ScriptText = $ScriptBlock.Ast.Extent.Text
                $ScriptOffSet = $ScriptBlock.Ast.Extent.StartOffset
                ForEach ($SubExpression in ($UsingVariables.SubExpression | Sort-Object { $_.Extent.StartOffset } -Descending)) {

                    $Name = '__using_{0}' -f (([Guid]::NewGuid().guid) -Replace '-')
                    $Expression = $SubExpression.Extent.Text.Replace('$Using:','$').Replace('${Using:','${'); 
                    $Value = [System.Management.Automation.PSSerializer]::Serialize((Invoke-Expression $Expression))
                    $JobArgumentList['Using'] += [PSCustomObject]@{ Name = $Name; Value = $Value } 
                    $ScriptText = $ScriptText.Substring(0, ($SubExpression.Extent.StartOffSet - $ScriptOffSet)) + "`${Using:$Name}" + $ScriptText.Substring(($SubExpression.Extent.EndOffset - $ScriptOffSet))

                }
                $JobArgumentList['ScriptBlock'] = [ScriptBlock]::Create($ScriptText.TrimStart("{").TrimEnd("}"))
            }

            $JobScriptBlock = [ScriptBlock]::Create(@"
 
                Param(`$Parameters)
 
                `$JobParameters = @{}
                If (`$Parameters.ScriptBlock) { `$JobParameters['ScriptBlock'] = [ScriptBlock]::Create(`$Parameters.ScriptBlock) }
                If (`$Parameters.ArgumentList) { `$JobParameters['ArgumentList'] = `$Parameters.ArgumentList }
 
                If (`$Parameters.Using) {
                    `$Parameters.Using | % { Set-Variable -Name `$_.Name -Value ([System.Management.Automation.PSSerializer]::Deserialize(`$_.Value)) }
                    Start-Job @JobParameters | Receive-Job -Wait -AutoRemoveJob
                } Else {
                    Invoke-Command @JobParameters
                }
"@
)

            Write-Verbose "Register-ScheduledJob: $($JobParameters['Name'])"
            $ScheduledJob = Register-ScheduledJob @JobParameters -ScriptBlock $JobScriptBlock -ArgumentList $JobArgumentList -ErrorAction Stop

            If (($AsCredential) -or ($AsSystem) -or ($AsGMSA)) {

                # Use ScheduledTask to execute the ScheduledJob to execute with the desired credentials.

                If (Get-Command 'Register-ScheduledTask' -ErrorAction SilentlyContinue) {

                    # For Windows 8 / Server 2012 and Newer

                    Write-Verbose "Register-ScheduledTask"
                    $TaskParameters = @{ TaskName = $ScheduledJob.Name }
                    $TaskParameters['Action'] = New-ScheduledTaskAction -Execute $ScheduledJob.PSExecutionPath -Argument $ScheduledJob.PSExecutionArgs
                    $RunLevel = If ($RunElevated) { 'Highest' } Else { 'Limited' }
                    If ($AsCredential) {
                        $TaskParameters['User'] = $AsCredential.GetNetworkCredential().UserName
                        $TaskParameters['Password'] = $AsCredential.GetNetworkCredential().Password
                    } ElseIf ($AsSystem) {
                        $TaskParameters['Principal'] = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel $RunLevel
                    } ElseIf ($AsGMSA) {
                        $TaskParameters['Principal'] = New-ScheduledTaskPrincipal -UserID $AsGMSA -LogonType Password -RunLevel $RunLevel
                    }
                    $ScheduledTask = Register-ScheduledTask @TaskParameters -ErrorAction Stop

                    Write-Verbose "Start-ScheduledTask"
                    $CimJob = $ScheduledTask | Start-ScheduledTask -AsJob -ErrorAction Stop
                    $CimJob | Wait-Job | Remove-Job -Force -Confirm:$False

                } Else {

                    # For Windows 7 / Server 2008 R2

                    Write-Verbose "Register-ScheduledTask"
                    $ScheduleService = New-Object -ComObject("Schedule.Service")
                    $ScheduleService.Connect()
                    $ScheduleTaskFolder = $ScheduleService.GetFolder("\")
                    $TaskDefinition = $ScheduleService.NewTask(0) 
                    $TaskAction = $TaskDefinition.Actions.Create(0)
                    $TaskAction.Path = $ScheduledJob.PSExecutionPath
                    $TaskAction.Arguments = $ScheduledJob.PSExecutionArgs

                    If ($AsCredential) {
                        $Username = $AsCredential.GetNetworkCredential().UserName
                        $Password = $AsCredential.GetNetworkCredential().Password
                        $LogonType = 1
                    } ElseIf ($AsSystem) {
                        $Username = "System"
                        $Password = $null
                        $LogonType = 5
                    } ElseIf ($AsGMSA) {
                        # Needs to be tested
                        $Username = $AsGMSA
                        $Password = $null
                        $LogonType = 5
                    }

                    $TaskDefinition = $ScheduleTaskFolder.RegisterTaskDefinition($ScheduledJob.Name,$TaskDefinition,6,$Username,$Password,$LogonType)

                    Write-Verbose "Start-ScheduledTask"
                    $TaskInfo = $TaskDefinition.Run($null)
                    
                    $ScheduledTask = Get-ScheduledTask -TaskName $TaskInfo.Name -TaskPath "\"

                }

                Write-Verbose "Get-ScheduledTaskInfo"
                $ScheduledTaskInfo = $ScheduledTask | Get-ScheduledTaskInfo
                If ($ScheduledTaskInfo.LastRunTime.Year -gt 1999) {

                    Write-Verbose "Get-ScheduledJob"
                    While (-Not($Job = Get-Job -Name $ScheduledJob.Name -ErrorAction SilentlyContinue)) { Start-Sleep -Milliseconds 200 }
    
                    Write-Verbose "Receive-ScheduledJob"
                    $Job | Wait-Job | Receive-Job -Wait -AutoRemoveJob

                } Else {

                    Write-Error 'Task was unable to be executed.'

                }

            } Else {

                # It no other credentials where provided, execute the ScheduledJob as is.
                Write-Verbose "Start-ScheduledTask"
                $Job = $ScheduledJob.StartJob()

                Write-Verbose "Receive-ScheduledJob"
                $Job  | Receive-Job -Wait -AutoRemoveJob

            }

        } Catch { Write-Error $_ }

    }

    End {

        If ($ScheduledTask) {
            Write-Verbose "Unregister ScheduledTask"
            # For Windows 8 / Server 2012 and Newer
            Try { $ScheduledTask | Unregister-ScheduledTask -Confirm:$False } Catch { $Null }
            # For Windows 7 / Server 2008 R2
            Try { $ScheduleTaskFolder.DeleteTask($ScheduledTask.Name, 0) | Out-Null } Catch { $Null }
        }

        If ($ScheduledJob) {
            Write-Verbose "Unregister ScheduledJob"
            # For Windows 8 / Server 2012 and Newer
            Try { $ScheduledJob | Unregister-ScheduledJob -Force -Confirm:$False | Out-Null } Catch { $Null } 
            # For Windows 7 / Server 2008 R2
            Try { $ScheduleTaskFolder.DeleteTask($ScheduledJob.Name, 0) | Out-Null } Catch { $Null }
        }

    }

}