Private/Rules/Find-IslLongSleep.ps1

function Find-IslLongSleep {
    <#
    .SYNOPSIS
        Flags waits long enough to hit the agent's timeout or hold up other remediations.
 
    .DESCRIPTION
        Platform scripts are killed after 30 minutes, remediations and Win32 detection after
        60. Remediations also run strictly one after another, so a long wait in one delays
        every other remediation assigned to the device.
 
    .PARAMETER Context
        The IntuneScriptLab.ScriptContext from Get-IslScriptContext: AST, tokens, bytes and the
        effective ScriptType, Context and Architecture.
 
    .EXAMPLE
        Find-IslLongSleep -Context (Get-IslScriptContext -Path .\Detect.ps1)
 
        The findings this rule produces for one script, as IntuneScriptLab.Finding objects.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [pscustomobject]$Context
    )

    $rule = 'IslLongSleep'
    $limitSeconds = if ($Context.ScriptType -eq 'PlatformScript') { 1800 } else { 3600 }
    $evidence = ("AgentExecutor.log: process timeout $limitSeconds s (W32-DET-PROBE); the 17 remediations of " +
        'round 1 ran one after another at ~15 s each, ~6 minutes for the batch (REM-PROBE-SYS64 and the ' +
        'other REM-* of round 1)')

    foreach ($command in (Find-IslCommand -Ast $Context.Ast -Name 'Start-Sleep', 'sleep')) {
        $seconds = $null
        $elements = $command.CommandElements
        for ($i = 1; $i -lt $elements.Count; $i++) {
            $element = $elements[$i]
            $next = if ($i + 1 -lt $elements.Count) { $elements[$i + 1] } else { $null }
            $isParameter = $element.GetType().Name -eq 'CommandParameterAst'
            if ($isParameter -and $next -and $next.Extent.Text -match '^\d+$') {
                if ('Seconds'.StartsWith($element.ParameterName,
                    'OrdinalIgnoreCase')) { $seconds = [int]$next.Extent.Text }
                elseif ('Milliseconds'.StartsWith($element.ParameterName,
                    'OrdinalIgnoreCase')) { $seconds = [int]$next.Extent.Text / 1000 }
            }
            elseif ($i -eq 1 -and $element.Extent.Text -match '^\d+$') { $seconds = [int]$element.Extent.Text }
        }
        if ($null -eq $seconds) { continue }
        if ($seconds -ge $limitSeconds) {
            $findingSplat = @{
                RuleName = $rule
                Severity = 'Error'
                Context  = $Context
                Extent   = $command.Extent
                Message  = ("Sleeping $seconds s exceeds the $limitSeconds s timeout; the script is killed " +
                    'before it finishes')
                Evidence = $evidence
            }
            New-IslFinding @findingSplat
        }
        elseif ($seconds -ge 300) {
            $findingSplat = @{
                RuleName = $rule
                Severity = 'Warning'
                Context  = $Context
                Extent   = $command.Extent
                Message  = ("Sleeping $seconds s holds the agent: remediations run one at a time and the " +
                    "whole script must finish within $limitSeconds s")
                Evidence = $evidence
            }
            New-IslFinding @findingSplat
        }
    }
}