private/ConvertFrom-VSAScExport.ps1

function ConvertFrom-VSAScExport {
    <#
    .SYNOPSIS
        Parses a Kaseya VSA 9 ScExport XML body (the agent-procedure export format) into objects.
    .DESCRIPTION
        VSA 9 Agent Procedures are stored as XML, so the agent-procedure list endpoint
        (api/v1.0/automation/agentprocs/proclist) returns an ScExport document rather than JSON -- by
        design, not error. This helper is the one place that understands that format, keeping XML
        handling local to the cmdlet that needs it (Get-VSAAPList) instead of teaching the generic
        JSON transport to sniff content types.

        The document shape (confirmed live) is:

            <ScExport xmlns="http://www.kaseya.com/vsa/2008/12/Scripting">
              <Records name="TotalRecords" totalRecords="786" startingRecordIndex="0" currentNumRecords="100" />
              <Procedure name="..." treePres="3" id="659091963" folderId="546051318732114"
                         treeFullPath="Core\..." shared="true"> <Body>...</Body> </Procedure>
              ...
            </ScExport>

        The `<Records>` element carries the same paging totals the JSON envelope exposes as
        TotalRecords, so callers page it identically. The heavy `<Body>` child (the full procedure
        definition) is intentionally not projected -- a list cmdlet returns the summary, not the body.
    .PARAMETER Body
        The raw ScExport XML string (e.g. the body returned by Invoke-VSAHttp for the proclist endpoint).
    .OUTPUTS
        A PSCustomObject with the paging totals (TotalRecords, StartingRecordIndex, CurrentNumRecords)
        and a Procedures array, one PSCustomObject per <Procedure> node
        (Id, Name, Path, FolderId, Shared [bool], TreePres). All ids are strings: folderId overflows
        Int32.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Body
    )

    [xml] $doc = $Body
    $root = $doc.DocumentElement   # <ScExport>; a default namespace makes XPath awkward, so walk by LocalName.

    # --- paging totals from <Records> ---
    [int] $total = 0; [int] $start = 0; [int] $current = 0
    $recordsNode = $root.ChildNodes | Where-Object { $_.LocalName -eq 'Records' } | Select-Object -First 1
    if ($recordsNode) {
        [void][int]::TryParse($recordsNode.GetAttribute('totalRecords'),        [ref]$total)
        [void][int]::TryParse($recordsNode.GetAttribute('startingRecordIndex'), [ref]$start)
        [void][int]::TryParse($recordsNode.GetAttribute('currentNumRecords'),   [ref]$current)
    }

    # --- one object per <Procedure> node; <Body> is deliberately omitted ---
    $procedures = foreach ($node in ($root.ChildNodes | Where-Object { $_.LocalName -eq 'Procedure' })) {
        [pscustomobject]@{
            Id       = $node.GetAttribute('id')            # string: big numeric id
            Name     = $node.GetAttribute('name')
            Path     = $node.GetAttribute('treeFullPath')
            FolderId = $node.GetAttribute('folderId')      # string: overflows Int32
            Shared   = ($node.GetAttribute('shared') -eq 'true')
            TreePres = $node.GetAttribute('treePres')
        }
    }

    return [pscustomobject]@{
        TotalRecords        = $total
        StartingRecordIndex = $start
        CurrentNumRecords   = $current
        Procedures          = @($procedures)
    }
}