Functions/Get-MCASActivity.ps1

<#
.Synopsis
   Gets user activity information from your Cloud App Security tenant.
.DESCRIPTION
   Gets user activity information from your Cloud App Security tenant and requires a credential be provided.
 
   Without parameters, Get-MCASActivity gets 100 activity records and associated properties. You can specify a particular activity GUID to fetch a single activity's information or you can pull a list of activities based on the provided filters.
 
   Get-MCASActivity returns a single custom PS Object or multiple PS Objects with all of the activity properties. Methods available are only those available to custom objects by default.
.EXAMPLE
    PS C:\> Get-MCASActivity -ResultSetSize 1
 
    This pulls back a single activity record and is part of the 'List' parameter set.
 
.EXAMPLE
    PS C:\> Get-MCASActivity -Identity 572caf4588011e452ec18ef0
 
    This pulls back a single activity record using the GUID and is part of the 'Fetch' parameter set.
 
.EXAMPLE
    PS C:\> (Get-MCASActivity -AppName Box).rawJson | ?{$_.event_type -match "upload"} | select ip_address -Unique
 
    ip_address
    ----------
    69.4.151.176
    98.29.2.44
 
    This grabs the last 100 Box activities, searches for an event type called "upload" in the rawJson table, and returns a list of unique IP addresses.
 
.FUNCTIONALITY
   Get-MCASActivity is intended to function as a query mechanism for obtaining activity information from Cloud App Security.
#>

function Get-MCASActivity {
    [CmdletBinding()]
    param
    (
        # Fetches an activity object by its unique identifier.
        [Parameter(ParameterSetName = 'Fetch', Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        #[ValidatePattern("[A-Fa-f0-9_\-]{51}|[A-Za-z0-9_\-]{20}")]
        [alias("_id")]
        [string]$Identity,

        # 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,

        # Specifies the property by which to sort the results. Possible Values: 'Date','Created'.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateSet('Date', 'Created')]
        [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, 100000)]
        [int]$ResultSetSize,

        # 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 #####

        # -User limits the results to items related to the specified user/users, for example '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 from a particular data source. (Possible Values: 0 = Access control, 1 = Session control, 2 = App connector, 3 = App connector analysis, 5 = Discovery, 6 = MDATP)
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("\b[0-6]\b")]
        [int]$Source,

        # Limits the results to items of specified event type name, such as EVENT_CATEGORY_LOGIN,EVENT_CATEGORY_DOWNLOAD_FILE.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$EventTypeName,

        # Limits the results to items not of specified event type name, such as EVENT_CATEGORY_LOGIN,EVENT_CATEGORY_DOWNLOAD_FILE.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$EventTypeNameNot,

        # Limits the results to items of specified action type name, such as '11161:EVENT_AAD_LOGIN:OAuth2:Authorize'
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ActionTypeName,

        # Limits the results to items not of specified action type name, such as '11161:EVENT_AAD_LOGIN:OAuth2:Authorize'
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ActionTypeNameNot,

        # Limits the results by ip category. Possible Values: 'None','Internal','Administrative','Risky','VPN','Cloud_Provider'.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ip_category[]]$IpCategory,

        # Limits the results to items with the specified IP leading digits, such as 10.0.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateLength(1, 45)]
        [string[]]$IpStartsWith,

        # Limits the results to items without the specified IP leading digits, such as 10.0.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateLength(1, 45)]
        [string[]]$IpDoesNotStartWith,

        # Limits the results to items found before specified date. Use Get-Date.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [datetime]$DateBefore = (Get-Date),

        # Limits the results to items found after specified date. Use Get-Date.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [datetime]$DateAfter,

        # Limits the results by device type. Possible Values: 'Desktop','Mobile','Tablet','Other'.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateSet('Desktop', 'Mobile', 'Tablet', 'Other')]
        [string[]]$DeviceTypeNot,

        # Limits the results by device type. Possible Values: 'Desktop','Mobile','Tablet','Other'.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateSet('Desktop', 'Mobile', 'Tablet', 'Other')]
        [string[]]$DeviceType,

        # Limits the results by performing a free text search
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_.Length -ge 5 })]
        [string]$Text,

        # Limits the results to events listed for the specified File ID.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        #[ValidatePattern("\b[A-Za-z0-9]{24}\b")]
        [string]$FileID,

        # Limits the results to events listed for the specified AIP classification labels. Use ^ when denoting (external) labels. Example: @("^Private")
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [array]$FileLabel,

        # Limits the results to events excluded by the specified AIP classification labels. Use ^ when denoting (external) labels. Example: @("^Private")
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [array]$FileLabelNot,

        # Limits the results to events listed for the specified IP Tags.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [validateset('Akamai_Technologies', 'Amazon_Web_Services', 'Anonymous_proxy', 'Ascenty_Data_Centers', 'Botnet', 'Brute_force_attacker', 'Cisco_CWS', 'Cloud_App_Security_network', 'Darknet_scanning_IP', 'Exchange_Online', 'Exchange_Online_Protection', 'Google_Cloud_Platform', 'Internal_Network_IP', 'Malware_CnC_server', 'Masergy_Communications', 'McAfee_Web_Gateway', 'Microsoft_Azure', 'Microsoft_Cloud', 'Microsoft_Hosting', 'Microsoft_authentication_and_identity', 'Office_365', 'Office_365_Planner', 'Office_365_ProPlus', 'Office_Online', 'Office_Sway', 'Office_Web_Access_Companion', 'OneNote', 'Remote_Connectivity_Analyzer', 'Salesforce_Cloud', 'Satellite_provider', 'ScanSafe', 'SharePoint_Online', 'Skype_for_Business_Online', 'Symantec_Cloud', 'Tor', 'Yammer', 'Zscaler')]
        [string[]]$IPTag,

        # Limits the results to events listed for the specified IP Tags.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [validateset('Akamai_Technologies', 'Amazon_Web_Services', 'Anonymous_proxy', 'Ascenty_Data_Centers', 'Botnet', 'Brute_force_attacker', 'Cisco_CWS', 'Cloud_App_Security_network', 'Darknet_scanning_IP', 'Exchange_Online', 'Exchange_Online_Protection', 'Google_Cloud_Platform', 'Internal_Network_IP', 'Malware_CnC_server', 'Masergy_Communications', 'McAfee_Web_Gateway', 'Microsoft_Azure', 'Microsoft_Cloud', 'Microsoft_Hosting', 'Microsoft_authentication_and_identity', 'Office_365', 'Office_365_Planner', 'Office_365_ProPlus', 'Office_Online', 'Office_Sway', 'Office_Web_Access_Companion', 'OneNote', 'Remote_Connectivity_Analyzer', 'Salesforce_Cloud', 'Satellite_provider', 'ScanSafe', 'SharePoint_Online', 'Skype_for_Business_Online', 'Symantec_Cloud', 'Tor', 'Yammer', 'Zscaler')]
        [string[]]$IPTagNot,

        # Limits the results to events that include a country code value.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [switch]$CountryCodePresent,

        # Limits the results to events that include a country code value.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [switch]$CountryCodeNotPresent,

        # Limits the results to events that include a country code value.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(2, 2)]
        [string[]]$CountryCode,

        # Limits the results to events that include a country code value.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(2, 2)]
        [string[]]$CountryCodeNot,

        # Limits the results to events listed for the specified IP Tags.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        #[ValidatePattern("[A-Fa-f0-9]{24}")]
        [string]$PolicyId,

        # Limits the results to items occuring in the last x number of days.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateRange(1, 180)]
        [int]$DaysAgo,

        # Limits the results to admin events if specified.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [switch]$AdminEvents,

        # Limits the results to non-admin events if specified.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [switch]$NonAdminEvents,

        # Limits the results to impersonated events if specified.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [switch]$Impersonated,

        # Limits the results to non-impersonated events if specified.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [switch]$ImpersonatedNot,

        # Limits the results to those with user agent strings containing the specified substring.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$UserAgentContains,

        # Limits the results to those with user agent strings not containing the specified substring.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$UserAgentNotContains,

        # Limits the results to those with user agent tags equal to the specified value(s).
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [validateset('Native_client', 'Outdated_browser', 'Outdated_operating_system', 'Robot')]
        [string[]]$UserAgentTag,

        # Limits the results to those with user agent tags not equal to the specified value(s).
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [validateset('Native_client', 'Outdated_browser', 'Outdated_operating_system', 'Robot')]
        [string[]]$UserAgentTagNot,

        # Limits the results to activities performed by users part of the specified groups.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$UserGroup,    

        # Limits the results to activities performed by users not part of the specified groups.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]$UserGroupNot,

        # Limits the results to activities performed by users who are part of any imported group.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [switch]$UserGroupPresent, 

        # Limits the results to activities performed by users who are not part of any imported group.
        [Parameter(ParameterSetName = 'List', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [switch]$UserGroupNotPresent
    )
    begin {
        if ($ResultSetSize -gt 100) {
            $ResultSetSizeSecondaryChunks = $ResultSetSize % 100
        }

        if ($PeriodicWriteToFile -and $ResultSetSize -le 100) { throw 'Error: You cannot use periodic file writing with a resultsetsize <= 100. Either remove periodicwritetofile or set your resultsetsize greater than 100.' }
        #if ($Skip -and $ResultSetSize -gt 100){throw 'Error: You cannot use the skip parameter when specifying more than 100 records. Large pull requests will skip for you automatically. Either remove the skip parameter or reduce your resultsetsize to 100 or less.'}
        if (($Skip + $ResultSetSize -gt 5000) -and $Skip -gt 0) { throw 'Error: You cannot pull more than 5000 records when using the -Skip parameter. Either remove -Skip or reduce your -ResultSetSize such that -Skip + -ResultSetSize is less than 5000.' }
        if ($ResultSetSize -gt 5000 -and $ResultSetSize % 100 -ne 0) { throw 'Error: When pulling more than 5000 records, you must keep ResultSetSize as a multiple of 100' }
    }
    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/activities/$Identity/" -Method Get
            }
            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
            }

            try {
                Write-Verbose "Adding alias property to results, if appropriate"
                $Response = $Response | Add-Member -MemberType AliasProperty -Name Identity -Value '_id' -PassThru
                #Fix collisions
                $response = $response.Replace('"Level":', '"Level_2":')
                $response = $response.Replace('"EventName":', '"EventName_2":')
            }
            catch { }


            # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
            try {
                Write-Verbose "Checking for property collision. Converting response to PSObject"
                #$response = $response | ConvertFrom-Json #this may not work anymore.
                #$response = $response.Replace('"Level":', '"Level_2":')
                #$response = $response.Replace('"EventName":', '"EventName_2":')
            }
            catch {
                Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                $response = $response.Replace('"Level":', '"Level_2":')
                $response = $response.Replace('"EventName":', '"EventName_2":')
                try {
                    $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                }
                catch {
                    throw $_
                }
                Write-Verbose "Any property name collisions appear to have been resolved."
            }



            $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) {
                $body.Add('sortField', $SortBy.ToLower())
            }
            #endregion ----------------------------SORTING----------------------------

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

            # Additional function for date conversion to unix format.
            if ($DateBefore) { $DateBefore2 = ([int64]((Get-Date -Date $DateBefore) - (get-date "1/1/1970")).TotalMilliseconds) }
            if ($DateAfter) { $DateAfter2 = ([int64]((Get-Date -Date $DateAfter) - (get-date "1/1/1970")).TotalMilliseconds) }
            
            #Set blank
            #$filterSet += @{'date' = @{}}

            # Additional parameter validations and mutual exclusions
            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 (($DateBefore -and $DaysAgo) -or ($DateAfter -and $DaysAgo)){throw 'Cannot reconcile app parameters. Only use one date parameter.'}
            if ($Impersonated -and $ImpersonatedNot) { throw 'Cannot reconcile app parameters. Do not combine Impersonated and ImpersonatedNot parameters.' }

            # Value-mapped filters
            if ($IpCategory) { $filterSet += @{'ip.category' = @{'eq' = ([int[]]($IpCategory | ForEach-Object { $_ -as [int] })) } }
            }
            if ($AppName) { $filterSet += @{'service' = @{'eq' = ([int[]]($AppName | ForEach-Object { $_ -as [int] })) } }
            }
            if ($AppNameNot) { $filterSet += @{'service' = @{'neq' = ([int[]]($AppNameNot | ForEach-Object { $_ -as [int] })) } }
            }
            if ($IPTag) { $filterSet += @{'ip.tags' = @{'eq' = ($IPTag.GetEnumerator() | ForEach-Object { $IPTagsList.$_ -join ',' }) } }
            }
            if ($IPTagNot) { $filterSet += @{'ip.tags' = @{'neq' = ($IPTagNot.GetEnumerator() | ForEach-Object { $IPTagsList.$_ -join ',' }) } }
            }
            if ($UserAgentTag) { $filterSet += @{'userAgent.tags' = @{'eq' = ($UserAgentTag.GetEnumerator() | ForEach-Object { $UserAgentTagsList.$_ -join ',' }) } }
            }
            if ($UserAgentTagNot) { $filterSet += @{'userAgent.tags' = @{'neq' = ($UserAgentTagNot.GetEnumerator() | ForEach-Object { $UserAgentTagsList.$_ -join ',' }) } }
            }

            # Simple filters
            if ($UserName) { $filterSet += @{'user.username' = @{'eq' = $UserName } }
            }

    # AppId
            if ($AppId -or $AppIdNot){ 
                $filterSet += @{'service' = @{} 
                }
                $FilterName = "service"
            }
            # AppId
            if ($AppId) { $filterSet.($FilterName).add('eq', $AppId ) }
            # AppIdNot
            if ($AppIdNot) { $filterSet.($FilterName).add('neq', $AppIdNot ) }

    
    # Source
            if ($Source){ 
                $filterSet += @{'source' = @{} 
                }
                $FilterName = "source"
            }
            # EventTypeName
            if ($Source) { $filterSet.($FilterName).add('eq', $Source ) }


    # EventTypeName / EventTypeNameNot
            if ($EventTypeName -or $EventTypeNameNot){ 
                $filterSet += @{'activity.eventType' = @{} 
                }
                $FilterName = "activity.eventType"
            }
            # EventTypeName
            if ($EventTypeName) { $filterSet.($FilterName).add('eq', $EventTypeName ) }
            # EventTypeNameNot
            if ($EventTypeNameNot) { $filterSet.($FilterName).add('neq', $EventTypeNameNot ) }


    # ActionTypeName / ActionTypeNameNot
            if ($ActionTypeName -or $ActionTypeNameNot){ 
                $filterSet += @{'activity.actionType' = @{} 
                }
                $FilterName = "activity.actionType"
            }
            # ActionTypeName
            if ($ActionTypeName) { $filterSet.($FilterName).add('eq', $ActionTypeName ) }
            # ActionTypeNameNot
            if ($ActionTypeNameNot) { $filterSet.($FilterName).add('neq', $ActionTypeNameNot ) }

    
    # UserGroup / UserGroupNot / UserGroupPresent / UserGroupNotPresent
            if ($UserGroup -or $UserGroupNot -or $UserGroupNotPresent -or $UserGroupPresent){ 
                $filterSet += @{'user.tags' = @{} 
                }
                $FilterName = "user.tags"
            }
            # UserGroup
            if ($UserGroup) { 

                $UserGroupArray = @(
                    @{
                        role = 1
                        adv = $true
                    }
                    $UserGroup
                )

                $filterSet.($FilterName).add('eq', $UserGroupArray) 
            } 
            # UserGroupNot
            if ($UserGroupNot) { 

                $UserGroupNotArray = @(
                    @{
                        role = 1
                        adv = $true
                    }
                    $UserGroupNot
                )

                $filterSet.($FilterName).add('neq', $UserGroupNotArray) 
            } 
            # UserGroupPresent
            if ($UserGroupPresent) { $filterSet.($FilterName).add('isset', $true)}
            # UserGroupNotPresent
            if ($UserGroupNotPresent) { $filterSet.($FilterName).add('isnotset', $true)}
            


    # CountryCode / CountryCodeNot / CountryCodePresent / CountryCodeNotPresent
            if ($CountryCode -or $CountryCodeNot){ 
                $filterSet += @{'location.country' = @{} 
                }
                $FilterName = "location.country"
            }
            # CountryCode
            if ($CountryCode) { $filterSet.($FilterName).add('eq', $CountryCode ) }
            # CountryCodeNot
            if ($CountryCodeNot) { $filterSet.($FilterName).add('neq' , $CountryCodeNot ) }
            # CountryCodePresent
            if ($CountryCodePresent) { $filterSet.($FilterName).add('isset' , $true ) }
            # CountryCodeNotPresent
            if ($CountryCodeNotPresent) { $filterSet.($FilterName).add('isnotset' , $true ) }


    # DeviceType / DeviceTypeNot
            if ($DeviceType -or $DeviceTypeNot){ 
                $filterSet += @{'device.type' = @{} 
                }
                $FilterName = "device.type"
            }
            #DeviceType
            if ($DeviceType) { $filterSet.($FilterName).add('eq', $DeviceType.ToUpper() ) }
            #DeviceTypeNot
            if ($DeviceTypeNot) { $filterSet.($FilterName).add('neq', $DeviceTypeNot.ToUpper() ) }
            

    # UserAgentContains / UserAgentNotContains
            if ($UserAgentContains -or $UserAgentNotContains){ 
                $filterSet += @{'userAgent.userAgent' = @{} 
                }
                $FilterName = "userAgent.userAgent"
            }
            # UserAgentContains
            if ($UserAgentContains) { $filterSet.($FilterName).add('contains', $UserAgentContains ) }
            # UserAgentNotContains
            if ($UserAgentNotContains) { $filterSet.($FilterName).add('ncontains', $UserAgentNotContains) }


    # IpStartsWith / $IpDoesNotStartWith
            if ($IpStartsWith -or $IpDoesNotStartWith){ 
                $filterSet += @{'ip.address' = @{} 
                }
                $FilterName = "ip.address"
            }
            #IpStartsWith
            if ($IpStartsWith) { $filterSet.($FilterName).add('startswith', $IpStartsWith ) }
            #IpDoesNotStartWith
            if ($IpDoesNotStartWith) { $filterSet.($FilterName).add('doesnotstartwith', $IpDoesNotStartWith) }


    # Text
            if ($Text){ 
                $filterSet += @{'text' = @{} 
                }
                $FilterName = "text"
            }
            # Text
            if ($Text) { $filterSet.($FilterName).add('text', $Text ) }


    # Impersonated / ImpersonatedNot
            if ($Impersonated -or $ImpersonatedNot){ 
                $filterSet += @{'activity.impersonated' = @{} 
                }
                $FilterName = "activity.impersonated"
            }
            #DeviceType
            if ($Impersonated) { $filterSet.($FilterName).add('eq', $true ) }
            #DeviceTypeNot
            if ($ImpersonatedNot) { $filterSet.($FilterName).add('eq', $false ) }

    
    # FileId
            if ($FileID){ 
                $filterSet += @{'fileSelector' = @{} 
                }
                $FilterName = "fileSelector"
            }
            # FileId
            if ($FileID) { $filterSet.($FilterName).add('eq', $FileID ) }
      

    # FileLabel
            if ($FileLabel){ 
                $filterSet += @{'fileLabels' = @{} 
                }
                $FilterName = "fileLabels"
            }
            # FileLabel
            if ($FileLabel) { $filterSet.($FilterName).add('eq', $FileLabel ) }


    # PolicyId
            if ($PolicyId){ 
                $filterSet += @{'policy' = @{} 
                }
                $FilterName = "policy"
            }
            # PolicyId
            if ($PolicyId) { $filterSet.($FilterName).add('eq', $PolicyId ) }


    # AdminEvents / NonAdminEvents
            if ($AdminEvents -or $NonAdminEvents){ 
                $filterSet += @{'activity.type' = @{} 
                }
                $FilterName = "activity.type"
            }
            # AdminEvents
            if ($AdminEvents) { $filterSet.($FilterName).add('eq', $true ) }
            # NonAdminEvents
            if ($NonAdminEvents) { $filterSet.($FilterName).add('eq', $false ) }            


    # DaysAgo / DateBefore / DateAfter
            if ($DaysAgo) { $filterSet += @{'date' = @{'gte_ndays' = $DaysAgo } }
            }
            if ($DateBefore -and (-not $DateAfter)) { $filterSet += @{'date' = @{'lte' = $DateBefore2 } }
            }
            if ($DateAfter -and (-not $DateBefore)) { $filterSet += @{'date' = @{'gte' = $DateAfter2 } }
            }
            if ($DateAfter -and $DateBefore) {
                $filterSet += @{'date' = @{'range' = @(
                            @{
                                'start' = $DateAfter2
                                'end'   = $DateBefore2
                            }
                        )
                    }
                }
            }

            if ($DaysAgo -and $DateBefore) {
                $filterSet += @{'date' = 
                    @{
                        'gte_ndays' = $DaysAgo
                        'lte'       = $DateBefore2
                    }
                }
            }

            #endregion ----------------------------FILTERING----------------------------
        }

        $collection = @()
        $i = $Skip
        $latestTimestamp = $DateBefore2

        #if no resultsetsize is specified - get all matching records up to 5000 using skip/limit method and stopping when all matches are returned. This uses hasnext

        if (!$ResultSetSize -and $PSCmdlet.ParameterSetName -ne 'Fetch') {
            Write-Verbose "Running loop A."

            #This first call gets us the total number of matches.
            $response = (Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw) 

            try {
                $response = $response | ConvertFrom-Json
            }
            catch {
                Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                $response = $response.Replace('"Level":', '"Level_2":')
                $response = $response.Replace('"EventName":', '"EventName_2":')
                try {
                    $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                }
                catch {
                    throw $_
                }
                Write-Verbose "Any property name collisions appear to have been resolved."
            }
            $responsetotal = $response.total
            Write-Verbose "Total Matching Records: $responsetotal"

            $ResultSetSizeSecondaryChunks = $responsetotal % 100
            $ResponseTotalMultiple100 = $responsetotal - $ResultSetSizeSecondaryChunks

            $hasnext = $response.hasnext
            Write-Verbose "HasNext: $hasnext"

            $collection = @()
            $i = $Skip
            $latestTimestamp = $DateBefore2
            $returnedrecords = 100 #we set this to a default of 100 but it can be overwritten during subsequent calls to match what the tenant is set for.

            do {
                $body = @{'skip' = $i; 'limit' = $returnedrecords } # Base request body

                try {
                    $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw
                }
                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.Content

                # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
                try {
                    $response = $response | ConvertFrom-Json
                }
                catch {
                    Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                    $response = $response.Replace('"Level":', '"Level_2":')
                    $response = $response.Replace('"EventName":', '"EventName_2":')
                    try {
                        $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                    }
                    catch {
                        throw $_
                    }
                    Write-Verbose "Any property name collisions appear to have been resolved."
                }
                $hasnext = $response.hasnext
                $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
                if ($PeriodicWriteToFile) {
                    Write-Verbose "Writing response output to $PeriodicWriteToFile"
                    $collection | ConvertTo-Json -depth 10 | Out-File $PeriodicWriteToFile
                }
                $returnedrecords = ($response | Measure-Object).count
                $i += $returnedrecords
                

            }
            while ($hasnext -eq $true)


            $collection
        }

    



        #if a resultsetsize > 5000 is specified, use the timestamp method

        if ($ResultSetSize -gt 5000 -and $skip -eq 0){
            Write-Verbose "Running loop B. This uses timestamps."
            do{
                $body = @{'skip'=0;'limit'=100} # Base request body
                $filterSet = @{'date'= @{'lte'=$latestTimestamp}}


                try {
                    $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw
                }
                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.Content

                # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
                try {
                    $response = $response | ConvertFrom-Json
                }
                catch {
                    Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                    $response = $response.Replace('"Level":','"Level_2":')
                    $response = $response.Replace('"EventName":','"EventName_2":')
                    try {
                        $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                    }
                    catch {
                        throw $_
                    }
                    Write-Verbose "Any property name collisions appear to have been resolved."
                }

                $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
                if ($PeriodicWriteToFile){
                    Write-Verbose "Writing response output to $PeriodicWriteToFile"
                    $collection | ConvertTo-Json -depth 10 | Out-File $PeriodicWriteToFile
                }
                $i+= 100
                $latestTimestamp = ($response | Select-Object -Last 1).timestamp
                Write-Verbose "$i records pulled so far."
                }
            while($i -lt $ResultSetSize - $ResultSetSizeSecondaryChunks)

            if ($ResultSetSizeSecondaryChunks -gt 0){
                Write-Verbose "Running loop D."
                $body = @{'skip'=($ResultSetSize - $ResultSetSizeSecondaryChunks);'limit'=$ResultSetSizeSecondaryChunks}

               try {
                    $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw
                }
                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.Content

                # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
                try {
                    $response = $response | ConvertFrom-Json
                }
                catch {
                    Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                    $response = $response.Replace('"Level":','"Level_2":')
                    $response = $response.Replace('"EventName":','"EventName_2":')
                    try {
                        $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                    }
                    catch {
                        throw $_
                    }
                    Write-Verbose "Any property name collisions appear to have been resolved."
                }

                $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
                if ($PeriodicWriteToFile){
                    Write-Verbose "Writing response output to $PeriodicWriteToFile"
                    $collection | ConvertTo-Json -depth 10 | Out-File $PeriodicWriteToFile
                }

                }


                $collection
            }







        #if a resultsetsize <= 5000 and > 100 is specified, perform a simple call using skip/limit

        if ($ResultSetSize -le 5000 -and $ResultSetSize -gt 100) {
            Write-Verbose "Running loop C. This uses skip/limit."


            do {
                $body = @{'skip' = $i; 'limit' = 100 } # Base request body

                try {
                    $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw
                }
                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.Content

                # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
                try {
                    $response = $response | ConvertFrom-Json
                }
                catch {
                    Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                    $response = $response.Replace('"Level":', '"Level_2":')
                    $response = $response.Replace('"EventName":', '"EventName_2":')
                    try {
                        $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                    }
                    catch {
                        throw $_
                    }
                    Write-Verbose "Any property name collisions appear to have been resolved."
                }

                $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
                if ($PeriodicWriteToFile) {
                    Write-Verbose "Writing response output to $PeriodicWriteToFile"
                    $collection | ConvertTo-Json -depth 10 | Out-File $PeriodicWriteToFile
                }
                $i += 100

            }
            while ($i -lt $ResultSetSize + $skip - $ResultSetSizeSecondaryChunks)

            if ($ResultSetSizeSecondaryChunks -gt 0) {
                Write-Verbose "Running loop D. This uses skip/limit"
                $body = @{'skip' = ($ResultSetSize - $ResultSetSizeSecondaryChunks); 'limit' = $ResultSetSizeSecondaryChunks }

                try {
                    $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw
                }
                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.Content

                # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
                try {
                    $response = $response | ConvertFrom-Json
                }
                catch {
                    Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                    $response = $response.Replace('"Level":', '"Level_2":')
                    $response = $response.Replace('"EventName":', '"EventName_2":')
                    try {
                        $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                    }
                    catch {
                        throw $_
                    }
                    Write-Verbose "Any property name collisions appear to have been resolved."
                }

                $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
                if ($PeriodicWriteToFile) {
                    Write-Verbose "Writing response output to $PeriodicWriteToFile"
                    $collection | ConvertTo-Json -depth 10 | Out-File $PeriodicWriteToFile
                }

            }

            $collection
        }

       





        # if resultsetsize <= 100 specified, perform single call using skip/limit method
        if ($ResultSetSize -le 100 -and $ResultSetSize -ne 0) {
            Write-Verbose "Running loop E. This will make a single call."
            # Get the matching items and handle errors
            try {
                $response = Invoke-MCASRestMethod -Credential $Credential -Path "/api/v1/activities/" -Body $body -Method Post -FilterSet $filterSet -Raw
            }
            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.Content
            

            # Attempt the JSON conversion. If it fails due to property name collisions to to case insensitivity on Windows, attempt to resolve it by renaming the properties.
            try {
                $response = $response | ConvertFrom-Json
            }
            catch {
                Write-Verbose "One or more property name collisions were detected in the response. An attempt will be made to resolve this by renaming any offending properties."
                $response = $response.Replace('"Level":', '"Level_2":')
                $response = $response.Replace('"EventName":', '"EventName_2":')
                try {
                    $response = $response | ConvertFrom-Json # Try the JSON conversion again, now that we hopefully fixed the property collisions
                }
                catch {
                    throw $_
                }
                Write-Verbose "Any property name collisions appear to have been resolved."
            }
            $recordtotal = $response.total
            
            $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 { }
            $response
        }
    }
}