Functions/Get-MCASAlert.ps1

<#
.Synopsis
   Gets alert information from your Cloud App Security tenant.
.DESCRIPTION
   Gets alert information from your Cloud App Security tenant and requires a credential be provided.
 
   Without parameters, Get-MCASAlert gets 100 alert records and associated properties. You can specify a particular alert GUID to fetch a single alert's information or you can pull a list of activities based on the provided filters.
 
   Get-MCASAlert returns a single custom PS Object or multiple PS Objects with all of the alert properties. Methods available are only those available to custom objects by default.
.EXAMPLE
    PS C:\> Get-MCASAlert -ResultSetSize 1
 
    This pulls back a single alert record and is part of the 'List' parameter set.
 
.EXAMPLE
    PS C:\> Get-MCASAlert -Identity 572caf4588011e452ec18ef0
 
    This pulls back a single alert record using the GUID and is part of the 'Fetch' parameter set.
 
.EXAMPLE
    PS C:\> (Get-MCASAlert -ResolutionStatus Open -Severity High | where{$_.title -match "system alert"}).descriptionTemplate.parameters.LOGRABBER_SYSTEM_ALERT_MESSAGE_BASE.functionObject.parameters.appName
 
    ServiceNow
    Box
 
    This command showcases the ability to expand nested tables of alerts. First, we pull back only Open alerts marked as High severity and filter down to only those with a title that matches "system alert". By wrapping the initial call in parentheses you can now extract the names of the affected services by drilling into the nested tables and referencing the appName property.
 
.FUNCTIONALITY
   Get-MCASAlert is intended to function as a query mechanism for obtaining alert information from Cloud App Security.
#>

function Get-MCASAlert {
    [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=$false)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]$Credential = $CASCredential,
        
        # Fetches an alert object by its unique identifier.
        [Parameter(ParameterSetName='Fetch', Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern({^[A-Fa-f0-9]{24}$})]
        [Alias("_id")]
        [string]$Identity,
        
        # Specifies the property by which to sort the results. Possible Values: 'Date','Severity', 'ResolutionStatus'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        #[ValidateSet('Date','Severity','ResolutionStatus')] # Additional sort fields removed by PG
        [ValidateSet('Date')]
        [string]$SortBy,

        # Specifies the direction in which to sort the results. Possible Values: 'Ascending','Descending'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateSet('Ascending','Descending')]
        [string]$SortDirection,

        # Specifies the maximum number of results to retrieve when listing items matching the specified filter criteria.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateRange(1,1000000000)]
        [int]$ResultSetSize = 100,

        # Specifies the number of records, from the beginning of the result set, to skip.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateScript({$_ -gt -1})]
        [int]$Skip = 0,

        # Periodically writes the activities returned in JSON format to a specified file. Useful for large queries. (Example: -PeriodicWriteToFile "C:\path\to\file.txt")
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [string]$PeriodicWriteToFile,

        ##### FILTER PARAMS #####

        # Limits the results by severity. Possible Values: 'High','Medium','Low'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [severity_level[]]$Severity,

        # Limits the results to items with a specific resolution status. Possible Values: 'Open','Dismissed','Resolved'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [resolution_status[]]$ResolutionStatus,

        # Limits the results to items related to the specified user/users, such as 'alice@contoso.com','bob@contoso.com'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Alias("User")]
        [string[]]$UserName,

        # Limits the results to items related to the specified service IDs, such as 11161,11770 (for Office 365 and Google Apps, respectively).
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Alias("Service","Services")]
        [int[]]$AppId,

        # Limits the results to items related to the specified service names, such as 'Office_365' and 'Google_Apps'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Alias("ServiceName","ServiceNames")]
        [mcas_app[]]$AppName,

        # Limits the results to items not related to the specified service ids, such as 11161,11770 (for Office 365 and Google Apps, respectively).
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Alias("ServiceNot","ServicesNot")]
        [int[]]$AppIdNot,

        # Limits the results to items not related to the specified service names, such as 'Office_365' and 'Google_Apps'.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Alias("ServiceNameNot","ServiceNamesNot")]
        [mcas_app[]]$AppNameNot,

        # Limits the results to items related to the specified policy ID, such as 57595d0ba6b5d8cd76d6be8c.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Policy,

        # Limits the results to items with a specific risk score. The valid range is 1-10.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateRange(0,10)]
        [int[]]$Risk,

        # Limits the results to items from a specific source.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Source,

        # Limits the results to read items.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [switch]$Read,

        # Limits the results to unread items.
        [Parameter(ParameterSetName='List', Mandatory=$false)]
        [switch]$Unread
    )
    begin {
        #below lines are used to find the max amount of records that can be returned from a single API call.

        $CallLimit = (Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/alerts/" -Method Post).max
        Write-Verbose "Call Limit: $CallLimit"

        #calculating if we need to do a final call for the remaining records.
        if ($ResultSetSize -and $ResultSetSize -gt $CallLimit){
            $ResultSetSizeSecondaryChunks = $ResultSetSize % $CallLimit
        }
    }
    process {
        # Fetch mode should happen once for each item from the pipeline, so it goes in the 'Process' block
        if ($PSCmdlet.ParameterSetName -eq 'Fetch')
        {
            try {
                # Fetch the item by its id
                $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/alerts/$Identity/" -Method Get
            }
            catch {
                throw "Error calling MCAS API. The exception was: $_"
            }
            
            try {
                Write-Verbose "Adding alias property to results, if appropriate"
                $response = $response | Add-Member -MemberType AliasProperty -Name Identity -Value '_id' -PassThru
            }
            catch {}
            
            $response
        }
    }
    end {
        if ($PSCmdlet.ParameterSetName -eq  'List') # Only run remainder of this end block if not in fetch mode
        {
            # List mode logic only needs to happen once, so it goes in the 'End' block for efficiency

            $body = @{'skip'=$Skip;'limit'=$ResultSetSize} # Base request body

            #region ----------------------------SORTING----------------------------

            if ($SortBy -xor $SortDirection) {throw 'Error: When specifying either the -SortBy or the -SortDirection parameters, you must specify both parameters.'}

            # Add sort direction to request body, if specified
            if ($SortDirection) {$body.Add('sortDirection',$SortDirection.TrimEnd('ending').ToLower())}

            # Add sort field to request body, if specified
            if ($SortBy)
            {
                if ($SortBy -eq 'ResolutionStatus')
                {
                    $body.Add('sortField','status') # Patch to convert 'resolutionStatus' to 'status', because the API is not using them consistently, but we are
                }
                else
                {
                    $body.Add('sortField',$SortBy.ToLower())
                }
            }
            #endregion ----------------------------SORTING----------------------------

            #region ----------------------------FILTERING----------------------------
            $filterSet = @() # Filter set array

            # Additional parameter validations and mutexes
            if ($AppName    -and ($AppId   -or $AppNameNot -or $AppIdNot)) {throw 'Cannot reconcile app parameters. Only use one of them at a time.'}
            if ($AppId      -and ($AppName -or $AppNameNot -or $AppIdNot)) {throw 'Cannot reconcile app parameters. Only use one of them at a time.'}
            if ($AppNameNot -and ($AppId   -or $AppName    -or $AppIdNot)) {throw 'Cannot reconcile app parameters. Only use one of them at a time.'}
            if ($AppIdNot   -and ($AppId   -or $AppNameNot -or $AppName))  {throw 'Cannot reconcile app parameters. Only use one of them at a time.'}
            if ($Read -and $Unread) {throw 'Cannot reconcile -Read and -Unread parameters. Only use one of them at a time.'}

            # Value-mapped filters
            if ($AppName)          {$filterSet += @{'entity.service'=   @{'eq'=([int[]]($AppName | ForEach-Object {$_ -as [int]}))}}}
            if ($AppNameNot)       {$filterSet += @{'entity.service'=   @{'neq'=([int[]]($AppNameNot | ForEach-Object {$_ -as [int]}))}}}
            if ($Severity)         {$filterSet += @{'severity'=         @{'eq'=([int[]]($Severity | ForEach-Object {$_ -as [int]}))}}}
            if ($ResolutionStatus -in ('Dismissed','Open','Resolved')) {$filterSet += @{'resolutionStatus'= @{'eq'=([int[]]($ResolutionStatus | ForEach-Object {$_ -as [int]}))}}}

            # Simple filters
            if ($UserName)   {$filterSet += @{'entity.user'=    @{'eq'=$UserName}}}
            if ($AppId)      {$filterSet += @{'entity.service'= @{'eq'=$AppId}}}
            if ($AppIdNot)   {$filterSet += @{'entity.service'= @{'neq'=$AppIdNot}}}
            if ($Policy)     {$filterSet += @{'entity.policy'=  @{'eq'=$Policy}}}
            if ($Risk)       {$filterSet += @{'risk'=           @{'eq'=$Risk}}}
            if ($AlertType)  {$filterSet += @{'id'=             @{'eq'=$AlertType}}}
            if ($Source)     {$filterSet += @{'source'=         @{'eq'=$Source}}}
            if ($Read)       {$filterSet += @{'read'=           @{'eq'=$true}}}
            if ($Unread)     {$filterSet += @{'read'=           @{'eq'=$false}}}

            #endregion ----------------------------FILTERING----------------------------
            $hasNext = 'True'
            $collection = @()
            $i = $Skip
            if ($ResultSetSize -gt $CallLimit)
                {
                    $limit = $CallLimit
                }
            else
                {
                    $limit = $ResultSetSize
                }
           
            if ($ResultSetSize){
            
                do{
                    Write-Verbose "Running loop B. Resultsetsize set."
                $body = @{'skip'=$i;'limit'=$limit} # Base request body
                $bodyjson = $body | ConvertTo-Json
                Write-Verbose "Request body is $bodyjson"
                # Get the matching items and handle errors
                try {
                    $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/alerts/" -Body $body -Method Post -FilterSet $filterSet
                
                }
                catch {
                    throw $_  #Exception handling is in Invoke-MCASRestMethod, so here we just want to throw it back up the call stack, with no additional logic
                }
                
               
<#
                $response = $response.data
 
                 
               
                try {
                    Write-Verbose "Adding alias property to results, if appropriate"
                    $response = $response | Add-Member -MemberType AliasProperty -Name Identity -Value '_id' -PassThru
                }
                catch {}
                #>

                
     

                $collection += $response.data

             
              
                if ($PeriodicWriteToFile){
                    Write-Verbose "Writing response output to $PeriodicWriteToFile"
                    $collection | ConvertTo-Json -depth 10 | Out-File $PeriodicWriteToFile
                }
               
                $i+= $CallLimit
    
                    
               $body = @{'skip'=$i;'limit'=$limit}
               
               
            }
            while($i -lt $ResultSetSize + $skip - $ResultSetSizeSecondaryChunks)

            }
 
            $collection
        }
    }
}