Pages/Dynamic/ScheduledTasksPage.ps1

$ScheduledTasksPageContent = {
    param($RemoteHost)

    $PUDRSSyncHT = $global:PUDRSSyncHT

    # Load PUDAdminCenter Module Functions Within ScriptBlock
    $ThisModuleFunctionsStringArray | Where-Object {$_ -ne $null} | foreach {Invoke-Expression $_ -ErrorAction SilentlyContinue}

    # For some reason, scriptblocks defined earlier can't be used directly here. They need to be a different objects before
    # they actually behave as expected. Not sure why.
    #$RecreatedDisconnectedPageContent = [scriptblock]::Create($DisconnectedPageContentString)

    $RHostIP = $($PUDRSSyncHT.RemoteHostList | Where-Object {$_.HostName -eq $RemoteHost}).IPAddressList[0]

    #region >> Ensure $RemoteHost is Valid

    if ($PUDRSSyncHT.RemoteHostList.HostName -notcontains $RemoteHost) {
        $ErrorText = "The Remote Host $($RemoteHost.ToUpper()) is not a valid Host Name!"
    }

    if ($ErrorText) {
        New-UDRow -Columns {
            New-UDColumn -Size 4 -Content {
                New-UDHeading -Text ""
            }
            New-UDColumn -Size 4 -Content {
                New-UDHeading -Text $ErrorText -Size 6
            }
            New-UDColumn -Size 4 -Content {
                New-UDHeading -Text ""
            }
        }
    }

    # If $RemoteHost isn't valid, don't load anything else
    if ($ErrorText) {
        return
    }

    #endregion >> Ensure $RemoteHost is Valid

    #region >> Loading Indicator

    New-UDRow -Columns {
        New-UDColumn -Endpoint {
            $Session:ScheduledTasksPageLoadingTracker = [System.Collections.ArrayList]::new()
        }
        New-UDColumn -AutoRefresh -RefreshInterval 5 -Endpoint {
            if ($Session:ScheduledTasksPageLoadingTracker -notcontains "FinishedLoading") {
                New-UDHeading -Text "Loading...Please wait..." -Size 5
                New-UDPreloader -Size small
            }
        }
    }

    #endregion >> Loading Indicator

    # Master Endpoint - All content will be within this Endpoint so that we can reference $Cache: and $Session: scope variables
    New-UDColumn -Size 12 -Endpoint {
        #region >> Ensure We Are Connected / Can Connect to $RemoteHost

        $PUDRSSyncHT = $global:PUDRSSyncHT

        # Load PUDAdminCenter Module Functions Within ScriptBlock
        $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -ne $null} | foreach {Invoke-Expression $_ -ErrorAction SilentlyContinue}

        # For some reason, scriptblocks defined earlier can't be used directly here. They need to be a different objects before
        # they actually behave as expected. Not sure why.
        #$RecreatedDisconnectedPageContent = [scriptblock]::Create($DisconnectedPageContentString)

        $RHostIP = $($PUDRSSyncHT.RemoteHostList | Where-Object {$_.HostName -eq $RemoteHost}).IPAddressList[0]

        if ($Session:CredentialHT.$RemoteHost.PSRemotingCreds -eq $null) {
            Invoke-UDRedirect -Url "/Disconnected/$RemoteHost"
        }

        try {
            $ConnectionStatus = Invoke-Command -ComputerName $RemoteHost -Credential $Session:CredentialHT.$RemoteHost.PSRemotingCreds -ScriptBlock {"Connected"}
        }
        catch {
            $ConnectionStatus = "Disconnected"
        }

        # If we're not connected to $RemoteHost, don't load anything else
        if ($ConnectionStatus -ne "Connected") {
            #Invoke-Command -ScriptBlock $RecreatedDisconnectedPageContent -ArgumentList $RemoteHost
            Invoke-UDRedirect -Url "/Disconnected/$RemoteHost"
        }
        else {
            New-UDRow -EndPoint {
                New-UDColumn -Size 3 -Content {
                    New-UDHeading -Text ""
                }
                New-UDColumn -Size 6 -Endpoint {
                    New-UDTable -Id "TrackingTable" -Headers @("RemoteHost","Status","DateTime") -AutoRefresh -RefreshInterval 2 -Endpoint {
                        $PUDRSSyncHT = $global:PUDRSSyncHT

                        # Load PUDAdminCenter Module Functions Within ScriptBlock
                        $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -ne $null} | foreach {Invoke-Expression $_ -ErrorAction SilentlyContinue}
                        
                        $RHostIP = $($PUDRSSyncHT.RemoteHostList | Where-Object {$_.HostName -eq $RemoteHost}).IPAddressList[0]

                        #$WSMan5985Available = $(TestPort -HostName $RHostIP -Port 5985).Open
                        #$WSMan5986Available = $(TestPort -HostName $RHostIP -Port 5986).Open

                        $ConnectionStatus = Invoke-Command -ComputerName $RemoteHost -Credential $Session:CredentialHT.$RemoteHost.PSRemotingCreds -ScriptBlock {"Connected"}

                        if ($ConnectionStatus -eq "Connected") {
                            $TableData = @{
                                RemoteHost      = $RemoteHost.ToUpper()
                                Status          = "Connected"
                            }
                        }
                        else {
                            <#
                            $TableData = @{
                                RemoteHost = $RemoteHost.ToUpper()
                                Status = "Disconnected"
                            }
                            #>

                            Invoke-UDRedirect -Url "/Disconnected/$RemoteHost"
                        }

                        # SUPER IMPORTANT NOTE: ALL Real-Time Enpoints on the Page reference LiveOutputClone!
                        if ($PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.LiveOutput.Count -gt 0) {
                            if ($PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataTracker.Previous -eq $null) {
                                $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataTracker.Previous = $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.LiveOutput.Clone()
                            }
                            if ($PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataTracker.Current.Count -gt 0) {
                                $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataTracker.Previous = $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataTracker.Current.Clone()
                            }
                            $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataTracker.Current = $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.LiveOutput.Clone()
                        }

                        $TableData.Add("DateTime",$(Get-Date -Format MM-dd-yy_hh:mm:sstt))

                        [PSCustomObject]$TableData | Out-UDTableData -Property @("RemoteHost","Status","DateTime")
                    }
                }
                New-UDColumn -Size 3 -Content {
                    New-UDHeading -Text ""
                }
            }
        }

        #endregion >> Ensure We Are Connected / Can Connect to $RemoteHost

        #region >> Gather Some Initial Info From $RemoteHost

        $GetScheduledTasksFunc = $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -match "function Get-ScheduledTasks" -and $_ -notmatch "function Get-PUDAdminCenter"}
        $StaticInfo = Invoke-Command -ComputerName $RemoteHosts -Credential $Session:CredentialHT.$RemoteHost.PSRemotingCreds -ScriptBlock {
            Invoke-Expression $using:GetScheduledTasksFunc
            
            $AllScheduledTasks = Get-ScheduledTasks

            [pscustomobject]@{
                AllScheduledTasks   = $AllScheduledTasks
            }
        }
        $Session:AllScheduledTasksStatic = $StaticInfo.AllScheduledTasks
        if ($PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.Keys -notcontains "AllScheduledTasks") {
            $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.Add("AllScheduledTasks",$Session:AllScheduledTasksStatic)
        }
        else {
            $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.AllScheduledTasks = $Session:AllScheduledTasksStatic
        }

        #endregion >> Gather Some Initial Info From $RemoteHost

        #region >> Page Name and Horizontal Nav

        New-UDRow -Endpoint {
            New-UDColumn -Content {
                New-UDHeading -Text "ScheduledTasks (In Progress)" -Size 3
                New-UDHeading -Text "NOTE: Domain Group Policy trumps controls with an asterisk (*)" -Size 6
            }
        }
        New-UDRow -Endpoint {
            New-UDColumn -Size 12 -Content {
                New-UDCollapsible -Items {
                    New-UDCollapsibleItem -Title "More Tools" -Icon laptop -Active -Endpoint {
                        New-UDRow -Endpoint {
                            foreach ($ToolName in $($Cache:DynamicPages | Where-Object {$_ -notmatch "PSRemotingCreds|ToolSelect"})) {
                                New-UDColumn -Endpoint {
                                    $ToolNameNoSpaces = $ToolName -replace "[\s]",""
                                    New-UDLink -Text $ToolName -Url "/$ToolNameNoSpaces/$RemoteHost" -Icon dashboard
                                }
                            }
                            #New-UDCard -Links $Links
                        }
                    }
                }
            }
        }

        #endregion >> Page Name and Horizontal Nav

        #region >> Setup LiveData

        <#
        New-UDColumn -Endpoint {
            $PUDRSSyncHT = $global:PUDRSSyncHT

            $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -ne $null} | foreach {Invoke-Expression $_ -ErrorAction SilentlyContinue}

            $RHostIP = $($PUDRSSyncHT.RemoteHostList | Where-Object {$_.HostName -eq $RemoteHost}).IPAddressList[0]

            # Remove Existing Runspace for LiveDataRSInfo if it exists as well as the PSSession Runspace within
            if ($PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo -ne $null) {
                $PSSessionRunspacePrep = @(
                    Get-Runspace | Where-Object {
                        $_.RunspaceIsRemote -and
                        $_.Id -gt $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.ThisRunspace.Id -and
                        $_.OriginalConnectionInfo.ComputerName -eq $RemoteHost
                    }
                )
                if ($PSSessionRunspacePrep.Count -gt 0) {
                    $PSSessionRunspace = $($PSSessionRunspacePrep | Sort-Object -Property Id)[0]
                }
                $PSSessionRunspace.Dispose()
                $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.ThisRunspace.Dispose()
            }

            # Create a Runspace that creates a PSSession to $RemoteHost that is used once every second to re-gather data from $RemoteHost
            $GetScheduledTasksOverviewFunc = $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -match "function Get-ScheduledTasksOverview" -and $_ -notmatch "function Get-PUDAdminCenter"}
            $GetScheduledTasksFunc = $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -match "function Get-ScheduledTasks" -and $_ -notmatch "function Get-PUDAdminCenter"}
            $LiveDataFunctionsToLoad = @($GetScheduledTasksOverviewFunc,$GetScheduledTasksFunc)
            
            # The New-Runspace function handles scope for you behind the scenes, so just pretend that everything within -ScriptBlock {} is in the current scope
            New-Runspace -RunspaceName "ScheduledTasks$RemoteHost`LiveData" -ScriptBlock {
                $PUDRSSyncHT = $global:PUDRSSyncHT
            
                $LiveDataPSSession = New-PSSession -Name "ScheduledTasks$RemoteHost`LiveData" -ComputerName $RemoteHost -Credential $Session:CredentialHT.$RemoteHost.PSRemotingCreds

                # Load needed functions in the PSSession
                Invoke-Command -Session $LiveDataPSSession -ScriptBlock {
                    $using:LiveDataFunctionsToLoad | foreach {Invoke-Expression $_}
                }

                $RSLoopCounter = 0

                while ($PUDRSSyncHT) {
                    # $LiveOutput is a special ArrayList created and used by the New-Runspace function that collects output as it occurs
                    # We need to limit the number of elements this ArrayList holds so we don't exhaust memory
                    if ($LiveOutput.Count -gt 1000) {
                        $LiveOutput.RemoveRange(0,800)
                    }

                    # Stream Results to $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.LiveOutput
                    Invoke-Command -Session $LiveDataPSSession -ScriptBlock {
                        # Place most resource intensive operations first

                        # Operations that you only want running once every 30 seconds go within this 'if; block
                        # Adjust the timing as needed with deference to $RemoteHost resource efficiency.
                        if ($using:RSLoopCounter -eq 0 -or $($using:RSLoopCounter % 30) -eq 0) {
                            #@{AllScheduledTaskss = Get-ScheduledTasks}
                        }

                        # Operations that you want to run once every second go here
                        @{ScheduledTasksSummary = Get-ScheduledTasksOverview -channel "Microsoft-Windows-ScheduledTaskservicesClient-Lifecycle-System*"}

                    } | foreach {$null = $LiveOutput.Add($_)}

                    $RSLoopCounter++

                    [GC]::Collect()

                    Start-Sleep -Seconds 1
                }
            }
            # The New-Runspace function outputs / continually updates a Global Scope variable called $global:RSSyncHash. The results of
            # the Runspace we just created can be found in $global:RSSyncHash's "ScheduledTasks$RemoteHost`LiveDataResult" Property - which is just
            # the -RunspaceName value plus the word 'Info'. By setting $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo equal to
            # $RSSyncHash."ScheduledTasks$RemoteHost`LiveDataResult", we can now reference $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo.LiveOutput
            # to get the latest data from $RemoteHost.
            $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.LiveDataRSInfo = $RSSyncHash."ScheduledTasks$RemoteHost`LiveDataResult"
        }
        #>


        #endregion >> Setup LiveData

        #region >> Controls

        # Static Data Element Example

        <#
            PS C:\Users\zeroadmin> $SchTsks[0].ScheduledTaskInfo

            LastRunTime : 8/28/2018 10:50:50 PM
            LastTaskResult : 0
            NextRunTime : 8/29/2018 10:50:50 PM
            NumberOfMissedRuns : 0
            TaskName : GoogleUpdateTaskMachineCore
            TaskPath : \
            PSComputerName :

            PS C:\Users\zeroadmin> $SchTsks[0].ScheduledTask

            TaskPath TaskName State
            -------- -------- -----
            \ GoogleUpdateTaskMachineCore Ready

            PS C:\Users\zeroadmin> $SchTsks[0].ScheduledTask | fl *

            status : Ready
            TriggersEx : {MSFT_TaskLogonTrigger, MSFT_TaskDailyTrigger}
            State : Ready
            Actions : {MSFT_TaskExecAction}
            Author :
            Date :
            Description : Keeps your Google software up to date. If this task is disabled or stopped, your Google software will not be kept up to date, meaning security vulnerabilities that may arise cannot be fixed and features may not
                                    work. This task uninstalls itself when there is no Google software using it.
            Documentation :
            Principal : MSFT_TaskPrincipal2
            SecurityDescriptor :
            Settings : MSFT_TaskSettings3
            Source :
            TaskName : GoogleUpdateTaskMachineCore
            TaskPath : \
            Triggers : {MSFT_TaskLogonTrigger, MSFT_TaskDailyTrigger}
            URI : \GoogleUpdateTaskMachineCore
            Version : 1.3.33.17
            PSComputerName :
            CimClass : Root/Microsoft/Windows/TaskScheduler:MSFT_ScheduledTask
            CimInstanceProperties : {Actions, Author, Date, Description...}
            CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties


        Trigger Value: $($($($($($($SchTsks.ScheduledTask[6].Triggers | gm).TypeName | Sort-Object | Get-Unique) | foreach {$_ -split "/"}) -match "Trigger") -replace "MSFT_Task") -replace "Trigger") -join ", "
        
        #>


        $AllScheduledTasksProperties = @("Name","Status","Triggers","NextRunTime","LastRunTime","LastRunResult","Author","Created")
        $AllScheduledTasksUDTableSplatParams = @{
            Headers         = $AllScheduledTasksProperties
            Properties      = $AllScheduledTasksProperties
            PageSize        = 20
        }
        New-UDGrid @AllScheduledTasksUDTableSplatParams -Endpoint {
            $PUDRSSyncHT = $global:PUDRSSyncHT

            $RHostIP = $($PUDRSSyncHT.RemoteHostList | Where-Object {$_.HostName -eq $RemoteHost}).IPAddressList[0]

            $GetScheduledTasksFunc = $Cache:ThisModuleFunctionsStringArray | Where-Object {$_ -match "function Get-ScheduledTasks" -and $_ -notmatch "function Get-PUDAdminCenter"}
            $SchTsksInfo = Invoke-Command -ComputerName $RemoteHost -Credential $Session:CredentialHT.$RemoteHost.PSRemotingCreds -ScriptBlock {
                Invoke-Expression $using:GetScheduledTasksFunc
                
                $AllScheduledTasks = Get-ScheduledTasks
    
                [pscustomobject]@{
                    AllScheduledTasks   = $AllScheduledTasks
                }
            }
            
            $Session:AllScheduledTasksStatic = foreach ($obj in $SchTsksInfo.AllScheduledTasks) {
                [array]$TriggersPrepA = @($obj.ScheduledTask.Triggers | Where-Object {$_})
                if ($TriggersPrepA.Count -gt 0) {
                    $TriggersPrep = $($($TriggersPrepA | Get-Member).TypeName | Sort-Object | Get-Unique) | foreach {$_ -split "/"}
                    $Triggers = $($($TriggersPrepA -match "Trigger") -replace "MSFT_Task") -replace "Trigger"
                }
                else {
                    $Triggers = $null
                }

                # LastRunResult Translation
                # From: https://en.wikipedia.org/wiki/Windows_Task_Scheduler
                $LastRunResult = switch ($obj.ScheduledTaskInfo.LastTaskResult) {
                    {$('{0:X}' -f $_) -eq '0'}          { "The operation completed successfully." }
                    {$('{0:X}' -f $_) -eq '1'}          { "Incorrect function called or unknown function called." }
                    {$('{0:X}' -f $_) -eq '2'}          { "File not found." }
                    {$('{0:X}' -f $_) -eq '10'}         { "The environment is incorrect." }
                    {$('{0:X}' -f $_) -eq '41300'}      { "Task is ready to run at its next scheduled time." }
                    {$('{0:X}' -f $_) -eq '41301'}      { "The task is currently running." }
                    {$('{0:X}' -f $_) -eq '41302'}      { "The task has been disabled." }
                    {$('{0:X}' -f $_) -eq '41303'}      { "The task has not yet run." }
                    {$('{0:X}' -f $_) -eq '41304'}      { "There are no more runs scheduled for this task." }
                    {$('{0:X}' -f $_) -eq '41305'}      { "One or more of the properties that are needed to run this task have not been set." }
                    {$('{0:X}' -f $_) -eq '41306'}      { "The last run of the task was terminated by the user." }
                    {$('{0:X}' -f $_) -eq '41307'}      { "Either the task has no triggers or the existing triggers are disabled or not set." }
                    {$('{0:X}' -f $_) -eq '41308'}      { "Event triggers do not have set run times." }
                    {$('{0:X}' -f $_) -eq '80010002'}   { "Call was canceled by the message filter." }
                    {$('{0:X}' -f $_) -eq '80041309'}   { "A task's trigger is not found." }
                    {$('{0:X}' -f $_) -eq '8004130A'}   { "One or more of the properties required to run this task have not been set." }
                    {$('{0:X}' -f $_) -eq '8004130B'}   { "There is no running instance of the task." }
                    {$('{0:X}' -f $_) -eq '8004130C'}   { "The Task Scheduler service is not installed on this computer." }
                    {$('{0:X}' -f $_) -eq '8004130D'}   { "The task object could not be opened." }
                    {$('{0:X}' -f $_) -eq '8004130E'}   { "The object is either an invalid task object or is not a task object." }
                    {$('{0:X}' -f $_) -eq '8004130F'}   { "No account information could be found in the Task Scheduler security database for the task indicated." }
                    {$('{0:X}' -f $_) -eq '80041310'}   { "Unable to establish existence of the account specified." }
                    {$('{0:X}' -f $_) -eq '80041311'}   { "Corruption was detected in the Task Scheduler security database." }
                    {$('{0:X}' -f $_) -eq '80041312'}   { "Task Scheduler security services are available only on Windows NT." }
                    {$('{0:X}' -f $_) -eq '80041313'}   { "The task object version is either unsupported or invalid." }
                    {$('{0:X}' -f $_) -eq '80041314'}   { "The task has been configured with an unsupported combination of account settings and run time options." }
                    {$('{0:X}' -f $_) -eq '80041315'}   { "The Task Scheduler Service is not running." }
                    {$('{0:X}' -f $_) -eq '80041316'}   { "The task XML contains an unexpected node." }
                    {$('{0:X}' -f $_) -eq '80041317'}   { "The task XML contains an element or attribute from an unexpected namespace." }
                    {$('{0:X}' -f $_) -eq '80041318'}   { "The task XML contains a value which is incorrectly formatted or out of range." }
                    {$('{0:X}' -f $_) -eq '80041319'}   { "The task XML is missing a required element or attribute." }
                    {$('{0:X}' -f $_) -eq '8004131A'}   { "The task XML is malformed." }
                    {$('{0:X}' -f $_) -eq '0004131B'}   { "The task is registered, but not all specified triggers will start the task." }
                    {$('{0:X}' -f $_) -eq '0004131C'}   { "The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal." }
                    {$('{0:X}' -f $_) -eq '8004131D'}   { "The task XML contains too many nodes of the same type." }
                    {$('{0:X}' -f $_) -eq '8004131E'}   { "The task cannot be started after the trigger end boundary." }
                    {$('{0:X}' -f $_) -eq '8004131F'}   { "An instance of this task is already running." }
                    {$('{0:X}' -f $_) -eq '80041320'}   { "The task will not run because the user is not logged on." }
                    {$('{0:X}' -f $_) -eq '80041321'}   { "The task image is corrupt or has been tampered with." }
                    {$('{0:X}' -f $_) -eq '80041322'}   { "The Task Scheduler service is not available." }
                    {$('{0:X}' -f $_) -eq '80041323'}   { "The Task Scheduler service is too busy to handle your request. Please try again later." }
                    {$('{0:X}' -f $_) -eq '80041324'}   { "The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition." }
                    {$('{0:X}' -f $_) -eq '00041325'}   { "The Task Scheduler service has asked the task to run." }
                    {$('{0:X}' -f $_) -eq '80041326'}   { "The task is disabled." }
                    {$('{0:X}' -f $_) -eq '80041327'}   { "The task has properties that are not compatible with earlier versions of Windows." }
                    {$('{0:X}' -f $_) -eq '80041328'}   { "The task settings do not allow the task to start on demand." }
                    {$('{0:X}' -f $_) -eq 'C000013A'}   { "The application terminated as a result of a CTRL+C." }
                    {$('{0:X}' -f $_) -eq 'C0000142'}   { "The application failed to initialize properly." }
                    Default                             { $null }
                }

                [pscustomobject]@{
                    Name            = $obj.ScheduledTask.TaskName
                    Status          = $obj.ScheduledTask.status
                    Triggers        = $Triggers
                    NextRunTime     = if ($obj.ScheduledTaskInfo.NextRunTime) {Get-Date $obj.ScheduledTaskInfo.NextRunTime -Format MM-dd-yy_hh:mm:sstt} else {$null}
                    LastRunTime     = if ($obj.ScheduledTaskInfo.LastRunTime) {Get-Date $obj.ScheduledTaskInfo.LastRunTime -Format MM-dd-yy_hh:mm:sstt} else {$null}
                    LastRunResult   = $LastRunResult
                    Author          = $obj.ScheduledTask.Author
                    Created         = if ($obj.ScheduledTask.Date) {Get-Date $obj.ScheduledTask.Date -Format MM-dd-yy_hh:mm:sstt} else {$null}
                }
            }
            $PUDRSSyncHT."$RemoteHost`Info".ScheduledTasks.AllScheduledTasks = $Session:AllScheduledTasksStatic
            
            $Session:AllScheduledTasksStatic | Out-UDGridData
        }

        # Live Data Element Example

        # Remove the Loading Indicator
        $null = $Session:ScheduledTasksPageLoadingTracker.Add("FinishedLoading")

        #endregion >> Controls
    }
}
$Page = New-UDPage -Url "/ScheduledTasks/:RemoteHost" -Endpoint $ScheduledTasksPageContent
$null = $Pages.Add($Page)