Public/Invoke-ECKScheduledTask.ps1

Function Invoke-ECKScheduledTask
    {
        # Version 3.4 - 17/02/2022 - Tasks run imedialtly are deleted once launched
        # Version 3.5 - 08/03/2022 - Fixed a bug in task detection with argument 'Now'

        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, Position=0)]
            [String]$HostScriptPath,
            [Parameter(Mandatory = $true, ParameterSetName = 'Command')]
            [String]$command,
            [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
            [string]$ScriptPath,
            [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
            [ScriptBlock]$ScriptBlock,
            [Parameter(Mandatory = $true)]
            [String]$TaskName,
            [Parameter(ParameterSetName = 'Command')]
            [string]$Parameters,
            [object]$triggerObject,
            [ValidateSet("System","SYSTEM","system","User","USER","user","Admin","ADMIN","admin")]
            [String]$Context = "System",
            [String]$TaskNamePrefix = "ECK",
            [switch]$Interactive,
            [string]$Description = "Scheduled task created from Powershell",
            [switch]$now,
            [switch]$AtStartup, #Machine
            [switch]$AtLogon, #User
            [switch]$DontAutokilltask,
            [int]$DefaultTaskExpiration = 120,
            [Switch]$NormalTaskName, #TaskName created without prefix and random GUID
            [Switch]$AllowUsersFullControl, #allow user to delete his own task
            [Switch]$ForceStandardPSConsole, #force use of Powershell.exe instead of Powershellw.exe
            [String]$AdminAccountName = 'SRVECK', #Temporary admin account used to run task as elevated user. (The account is created/managed by the function and does not need to exists already !)
            [String]$LogPath
        )

        Try
            {
                #Created Scheduled Task
                $TaskName = $TaskName.Replace(" ","_")
                $taskName = $TaskName + $(split-path $HostScriptPath -leaf).Replace("-","").replace(".ps1","").replace(" ","")
                $ToastGUID = ([guid]::NewGuid()).ToString().ToUpper()
                $Task_TimeToRun = (Get-Date).AddSeconds(5).ToString('s')
                $Task_Expiry = (Get-Date).AddSeconds($DefaultTaskExpiration).ToString('s')

                if ($Interactive) {$LogonType = "Interactive"} Else {$LogonType = "ServiceAccount"}

                If ($Context.ToUpper() -eq "SYSTEM")
                    {$Task_Principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType $LogonType -RunLevel Highest}
                elseif ($Context.ToUpper() -eq "ADMIN")
                    {
                        #Creat Admin Account
                        Add-Type -AssemblyName System.Web
                        $Newuserpassword = new-object System.Security.SecureString
                        foreach($char in $([System.Web.Security.Membership]::GeneratePassword(10,2)).ToCharArray()){$Newuserpassword.AppendChar($char)}

                        If (Get-LocalUser -Name $AdminAccountName -ErrorAction SilentlyContinue)
                            {Set-LocalUser -Name $AdminAccountName -Password $Newuserpassword}
                        Else
                            {New-LocalUser $AdminAccountName -Password $Newuserpassword -Description "Dev Account"|Out-Null}

                        $AdminAccountSID = (Get-LocalUser -Name $AdminAccountName).SID.value
                        Add-LocalGroupMember -SID 'S-1-5-32-544' -Member $AdminAccountName -ErrorAction SilentlyContinue

                        $Task_Principal = New-ScheduledTaskPrincipal -UserID $AdminAccountName -RunLevel Highest

                        #Update remaining task
                        $taskToUpdate = Get-ScheduledTask|Where-Object {$_.Principal.UserId -eq $AdminAccountName -or $_.Principal.UserId -eq $AdminAccountSID}
                        Foreach ($item in $taskToUpdate)
                            {
                                If ($LogPath) {Write-ECKLog "Updating credentials for task $($item.taskname)" -Path $LogPath}
                                Set-ScheduledTask -TaskName $item.taskname -Taskpath "\*" -User $AdminAccountName -Password $Newuserpassword -ErrorAction SilentlyContinue
                            }
                    }
                else
                    {$Task_Principal = New-ScheduledTaskPrincipal -GroupId "S-1-5-32-545" -RunLevel Limited}


                If ($triggerObject)
                    {$Task_Trigger = $triggerObject}
                elseif ($AtStartup)
                    {$Task_Trigger = New-ScheduledTaskTrigger -AtStartup ; $DontAutokilltask = $True}
                elseif ($AtLogon)
                    {$Task_Trigger = New-ScheduledTaskTrigger -AtLogOn ; $DontAutokilltask = $True}
                Else
                    {$Task_Trigger = New-ScheduledTaskTrigger -Once -At $Task_TimeToRun}

                If ($DontAutokilltask)
                    {$Task_Settings = New-ScheduledTaskSettingsSet -Compatibility Win8 -AllowStartIfOnBatteries -StartWhenAvailable}
                Else
                    {
                        $Task_Trigger.EndBoundary = $Task_Expiry
                        $Task_Settings = New-ScheduledTaskSettingsSet -Compatibility Win8 -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 600) -AllowStartIfOnBatteries -StartWhenAvailable
                    }


                If ($command)
                    {
                        If([String]::IsNullOrWhiteSpace($Parameters))
                            {$Task_Action = New-ScheduledTaskAction -Execute $command}
                        Else
                            {$Task_Action = New-ScheduledTaskAction -Execute $command -Argument $Parameters}
                    }

                If ($ScriptBlock)
                    {
                        $ScriptGuid = new-guid
                        If ($Context.ToUpper() -eq "USER")
                            {
                                $ExecContext = Get-ECKExecutionContext
                                $ScriptPath = "$($ExecContext.UserProfile)\AppData\Local\Temp\$ScriptGuid.ps1"
                            }
                        Else
                            {$ScriptPath = "$($ENV:TEMP)\$ScriptGuid.ps1"}
                        $ScriptBlock|Out-File -FilePath $ScriptPath -Encoding default
                        If ($LogPath) {Write-ECKLog "Script Block converted to file $ScriptPath" -Path $LogPath}
                    }

                If ($ScriptPath)
                    {
                        If (($ScriptPath.Substring($ScriptPath.Length-4)).toUpper() -ne ".PS1"){If ($LogPath) {Write-ECKLog "[Error] you must specify a powershell script, Aborting!!" -Path $LogPath}; Return}
                        If ((test-path "C:\Windows\System32\Windowspowershell\v1.0\powershellw.exe") -and (-not ($ForceStandardPSConsole)))
                            {$command = "C:\Windows\System32\Windowspowershell\v1.0\powershellw.exe"}
                        Else
                            {$command = "C:\Windows\System32\Windowspowershell\v1.0\powershell.exe"}

                        $Parameters = "-executionpolicy bypass -noprofile -WindowStyle Hidden -file ""$ScriptPath"""
                        $Task_Action = New-ScheduledTaskAction -Execute $command -Argument $Parameters
                    }


                $New_Task = New-ScheduledTask -Description $Description -Action $Task_Action -Principal $Task_Principal -Trigger $Task_Trigger -Settings $Task_Settings

                If ($NormalTaskName)
                    {$TaskFullName = $TaskName ; $PreviousTaskName = $TaskName}
                Else
                    {$TaskFullName = $($TaskNamePrefix + "_" + $TaskName + "_" + $ToastGuid) ; $PreviousTaskName = $($TaskNamePrefix + "_" + $TaskName + "_" + "*-*-*-*-*")}

                If ($LogPath) {Write-ECKLog "Created Task name: $TaskFullName" -Path $LogPath}
                If ($LogPath) {Write-ECKLog "Command to run is: $command $Parameters" -Path $LogPath}

                #Cleanup task with the same Name
                Get-ScheduledTask -TaskName $PreviousTaskName -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue

                If ($Context.ToUpper() -eq "ADMIN")
                    {Register-ScheduledTask -TaskName $TaskFullName -InputObject $New_Task -User $Task_Principal.UserID -Password $([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($NewuserPassword))) -ErrorAction SilentlyContinue|Out-Null}
                Else
                    {Register-ScheduledTask -TaskName $TaskFullName -InputObject $New_Task -ErrorAction SilentlyContinue|Out-Null}

                #Change Task ACL
                If($AllowUsersFullControl)
                    {
                        # David Segura to the rescue (Kind thanks)!!!
                        $Scheduler = New-Object -ComObject "Schedule.Service"
                        $Scheduler.Connect()
                        $GetTask = $Scheduler.GetFolder('\').GetTask($TaskFullname)
                        $GetSecurityDescriptor = $GetTask.GetSecurityDescriptor(0xF)
                        if ($GetSecurityDescriptor -notmatch 'A;;FA;;;AU')
                            {
                                $GetSecurityDescriptor = $GetSecurityDescriptor + '(A;;FA;;;AU)'
                                $GetTask.SetSecurityDescriptor($GetSecurityDescriptor, 0)
                            }
                    }


                If ($now)
                    {
                        Start-ScheduledTask -TaskName $TaskFullName

                        $Count = 0
                        While((Get-ScheduledTask $TaskFullName -ErrorAction SilentlyContinue).State -ne 'Running' -and $count -le 8){Start-Sleep -Seconds 1 ; $Count +=1 }
                        try
                            {
                                Get-ScheduledTask $TaskFullName -ErrorAction Stop
                                If ($LogPath) {Write-ECKLog "Task $TaskFullName now launched in $context context !" -Path $LogPath}
                            }
                        Catch {If ($LogPath) {Write-ECKLog "[ERROR] Unable to Launch Task $TaskFullName in $context context !" -Path $LogPath -Type 3}}
                        If (-Not ($DontAutokilltask)) {Unregister-ScheduledTask -TaskName $TaskFullName -Confirm:$false -ErrorAction SilentlyContinue}

                    }
                Else
                    {If ($LogPath) {Write-ECKLog "Task $TaskFullName scheduled sucessfully in $context context with a custom planed execution." -Path $LogPath}}

                Write-Output $TaskFullName
            }
        Catch
            {
                If ($LogPath) {Write-ECKLog   $_.Exception.Message.ToString() -Type 3 -Path $LogPath}
                If ($LogPath) {Write-ECKLog   $_.InvocationInfo.PositionMessage.ToString() -Type 3 -Path $LogPath}
                If ($LogPath) {Write-ECKLog "[Error], Unable to schedule task $taskName, Aborting !!!" -Type 3 -Path $LogPath}
                if ($Context.ToUpper() -eq "ADMIN"){Get-LocalUser $AdminAccountName -ErrorAction SilentlyContinue|Remove-LocalUser -Confirm:$false -ErrorAction SilentlyContinue}
                Write-Output "#ERROR"
            }
    }