Functions/Invoke-MCASRestMethod.ps1

function Invoke-MCASRestMethod {
    [CmdletBinding()]
    param (
        # Specifies the credential object containing tenant as username (e.g. 'contoso.us.portal.cloudappsecurity.com') and the 64-character hexadecimal Oauth token as the password.
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {
                ($_.GetNetworkCredential().username).EndsWith('.portal.cloudappsecurity.com')
            })]
        [ValidateScript( {
                $_.GetNetworkCredential().Password -match ($MCAS_TOKEN_VALIDATION_PATTERN)
            })]
        [System.Management.Automation.PSCredential]$Credential,

        # Specifies the relative path of the full uri being invoked (e.g. - '/api/v1/alerts/')
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {
                $_.StartsWith('/')
            })]
        [string]$Path,

        # Specifies the HTTP method to be used for the request
        [Parameter(Mandatory = $true)]
        [ValidateSet('Get', 'Post', 'Put', 'Delete')]
        [string]$Method,

        # Specifies the body of the request, not including MCAS query filters, which should be specified separately in the -FilterSet parameter
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        $Body,

        # Specifies the content type to be used for the request
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$ContentType = 'application/json',

        # Specifies the MCAS query filters to be used, which will be added to the body of the message
        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        $FilterSet,

        # Specifies the retry interval, in seconds, if a call to the MCAS web API is throttled. Default = 5 (seconds)
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [int]$RetryInterval = 5,

        # Specifies that a single item is to be fetched, skipping any processing for lists, such as checking result count totals
        #[switch]$Fetch,

        # Specifies use Invoke-WebRequest instead of Invoke-RestMethod, enabling the caller to get the raw response from the MCAS API without any JSON conversion
        [switch]$Raw
    )
    #Ensure TLS 1.2 is used.
    if([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12'){
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    }

    if ($Raw) {
        $cmd = 'Invoke-WebRequest'
        Write-Verbose "-Raw parameter was specified"
    }
    else {
        $cmd = 'Invoke-RestMethod'
        Write-Verbose "-Raw parameter was not specified"
    }
    Write-Verbose "$cmd will be used"

    $tenant = ($Credential.GetNetworkCredential().username)
    Write-Verbose "Tenant name is $tenant"

    Write-Verbose "Relative path is $Path"

    Write-Verbose "Method is $Method"

    $token = $Credential.GetNetworkCredential().Password
    #MK - Commenting out this line for security reasons. Not sure I like having the raw token in the verbose output.
    #Write-Verbose "OAuth token is $token"

    $headers = 'Authorization = "Token {0}"' -f $token | ForEach-Object {
        "@{$_}"
    }

    $verboseHeaders = $headers -replace 'Authorization = "Token .{9}', 'Authorization = "Token XXXXXXXXX'

    Write-Verbose "Request headers are $verboseHeaders"

    # Construct base MCAS call before processing -Body and -FilterSet
    $mcasCall = '{0} -Uri ''https://{1}{2}'' -Method {3} -Headers {4} -ContentType {5} -UseBasicParsing' -f $cmd, $tenant, $Path, $Method, $headers, $ContentType

    if ($Method -eq 'Get') {
        Write-Verbose "A request using the Get HTTP method cannot have a message body."
    }
    else {
        $jsonBody = $Body | ConvertTo-Json -Compress -Depth 4
        Write-Verbose "Base request body is $jsonBody"

        if ($FilterSet) {
            Write-Verbose "Request body before query filters is $jsonBody"
            $jsonBody = $jsonBody.TrimEnd('}') + ',' + '"filters":{' + ((ConvertTo-MCASJsonFilterString $FilterSet).TrimStart('{')) + '}'
            Write-Verbose "Request body after query filters is $jsonBody"
        }
        else {
            Write-Verbose "No filters were added to the request body"
        }
        Write-Verbose "Final request body is $jsonBody"

        # Add -Body to the constructed MCAS call, when the http method is not 'Get'
        $mcasCall = '{0} -Body ''{1}''' -f $mcasCall, $jsonBody
    }

    Write-Verbose "Constructed call to MCAS is to follow:"
    $mcasCall2 = '{0} -Uri ''https://{1}{2}'' -Method {3} -ContentType {5} -UseBasicParsing' -f $cmd, $tenant, $Path, $Method, $headers, $ContentType

    Write-Verbose $mcasCall2

    Write-Verbose "Retry interval if MCAS call is throttled is $RetryInterval seconds"

    # This loop is the actual call to MCAS. It includes automatic retry if the API call is throttled
    do {
        $retryCall = $false

        try {
            Write-Verbose "Attempting call to MCAS..."
            $response = Invoke-Expression -Command $mcasCall
        }
        catch {
            if ($_ -like 'The remote server returned an error: (429) TOO MANY REQUESTS.') {
                Write-Warning "429 - Too many requests. The MCAS API throttling limit has been hit, the call will be retried in $RetryInterval second(s)..."
                $retryCall = $true
                Write-Verbose "Sleeping for $RetryInterval seconds"
                Start-Sleep -Seconds $RetryInterval
            }
            ElseIf ($_ -match 'throttled') {
                Write-Warning "Too many requests. Usually the throttle time for this call is 1 minute. Next request will resume in 1 minute..."
                $retryCall = $true
                Write-Verbose "Sleeping for 60 seconds"
                Start-Sleep -Seconds 60
            }
            ElseIf ($_ -like '504' -or $_ -like '502') {
                Write-Warning "502 or 504 error encountered. The call will be retried in $RetryInterval second(s)..."
                $retryCall = $true
                Write-Verbose "Sleeping for $RetryInterval seconds"
                Start-Sleep -Seconds $RetryInterval
            }
            else {
                throw $_
            }
        }

        # Uncomment following two lines if you want to see raw responses in -Verbose output
        #Write-Verbose 'MCAS response to follow:'
        #Write-Verbose $response
    }
    while ($retryCall)

    # Provide the total record count in -Verbose output and as InformationVariable, if appropriate
    if (@('Get', 'Post') -contains $Method) {
        if ($response.total) {
            Write-Verbose 'Checking total matching record count via the response properties...'
            $recordTotal = $response.total
        }
        elseif ($response.Content) {
            try {
                Write-Verbose 'Checking total matching record count via raw JSON response...'
                $recordTotal = (($response.content).Replace('"Level":','"Level_2":') | ConvertFrom-Json).total
            }
            catch {
                Write-Verbose 'JSON conversion failed. Checking total matching record count via raw response string extraction...'
                #below linew as commented out as it breaks with the new activities_kusto endpoint.
                #$recordTotal = ($response.Content.Split(',', 3) | Where-Object {$_.StartsWith('"total"')} | Select-Object -First 1).Split(':')[1]
            }
        }
        else {
            Write-Verbose 'Could not check total matching record count, perhaps because zero or one records were returned. Zero will be returned as the matching record count.'
            $recordTotal = 0
        }

        Write-Verbose ('The total number of matching records was {0}' -f $recordTotal)
        #removing the below line because it is now breaking certain cmdlets such as Get-MCASFile when retriving a file by identity
        #Write-Information $recordTotal
    }
    $response
}