M365DefenderStuff.psm1

function Get-M365DefenderMachine {
    <#
    .SYNOPSIS
    Get list of just one/all machine/s.
 
    .DESCRIPTION
    Get list of just one/all machine/s.
 
    .PARAMETER machineId
    (optional) specific machine ID you want to retrieve.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $allMachines = Get-M365DefenderMachine -header $header
 
    Get all machines from defender portal.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $machine = Get-M365DefenderMachine -header $header -machineId 09a3a0af67c7bc1e5efc1a334114d00df3042cc8
 
    Get just one specific machine from defender portal.
 
    .NOTES
    Requires Machine.Read.All permission.
 
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/get-machine-by-id?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        [string] $machineId,

        $header,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    $url = "https://$apiUrl/api/machines"
    if ($machineId) {
        $url = $url + "/$machineId"
    }

    Invoke-RestMethod2 -uri $url -headers $header
}

function Get-M365DefenderMachineUser {
    <#
    .SYNOPSIS
    Retrieves a list of all users that logged in to the specified computer.
 
    .DESCRIPTION
    Retrieves a list of all users that logged in to the specified computer.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER machineId
    Machine ID.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    Get-M365DefenderMachineUser -header $header -machineId 23de7fcd303b5cee7b7aee032276bf2690448582
 
    Get all users for specified device.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    Get-M365DefenderMachineUser -header $header
 
    Get all computers and their users.
 
    .NOTES
    Requires User.Read.All.
 
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/get-machine-log-on-users?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        $header,

        [string[]] $machineId,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    if (!$machineId) {
        $machineId = Get-M365DefenderMachine -header $header | select -ExpandProperty Id
    }

    foreach ($id in $machineId) {
        $url = "https://$apiUrl/api/machines/$id/logonusers"

        Invoke-RestMethod2 -uri $url -headers $header | select *, @{n = 'MachineId'; e = { $id } }
    }
}

function Get-M365DefenderMachineVulnerability {
    <#
    .SYNOPSIS
    Retrieves a list of all the vulnerabilities affecting the organization per machine and software.
 
    .DESCRIPTION
    Retrieves a list of all the vulnerabilities affecting the organization per machine and software.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER severity
    Filter vulnerabilities by severity.
 
    Possible values: 'Low', 'Medium', 'High', 'Critical'
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $allMachineVulnerabilities = Get-M365DefenderMachineVulnerability -header $header
 
    .NOTES
    Requires Vulnerability.Read.All permission.
 
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/get-all-vulnerabilities-by-machines?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        $header,

        [ValidateSet('Low', 'Medium', 'High', 'Critical', ignorecase = $False)]
        [string[]] $severity,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    $url = "https://$apiUrl/api/vulnerabilities/machinesVulnerabilities"

    if ($severity) {
        $sevF = ""
        $severity | % {
            if ($sevF) { $sevF = $sevF + " or " }

            $sevF += "severity eq '$_'"
        }
        $url = $url + "?`$filter=($sevF)"
    }

    Invoke-RestMethod2 -Uri $url -Headers $header
}

function Get-M365DefenderRecommendation {
    <#
    .SYNOPSIS
    Get list of all/just selected (by name or machine) recommendation/s.
 
    .DESCRIPTION
    Get list of all/just selected (by name or machine) recommendation/s.
 
    .PARAMETER productName
    Name of the product to search recommendations for.
 
    .PARAMETER machineId
    Id of the machine you want recommendations for.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    Get-M365DefenderRecommendation
 
    Get all security recommendations.
 
    .EXAMPLE
    Get-M365DefenderRecommendation -productName putty
 
    Get security recommendations just for Putty software.
 
    .EXAMPLE
    Get-M365DefenderRecommendation -machineId 43a802402664e76a021c8dda2e2aa7db6a09a5a4
 
    Get all security recommendations for given machine.
 
    .NOTES
    Requires SecurityRecommendation.Read.All permission.
 
    https://learn.microsoft.com/en-us/defender-endpoint/api/get-all-recommendations?view=o365-worldwide
    #>


    [CmdletBinding(DefaultParameterSetName = 'productName')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = "productName")]
        [string] $productName,

        [Parameter(Mandatory = $true, ParameterSetName = "machineId")]
        [string] $machineId,

        $header,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    if ($machineId) {
        $url = "https://$apiUrl/api/machines/$machineId/recommendations"
    } else {
        $url = "https://$apiUrl/api/recommendations"
        if ($productName) {
            $url = $url + '?$filter=' + "productName eq '$productName'"
        }
    }

    Invoke-RestMethod2 -uri $url -headers $header
}

function Get-M365DefenderSoftware {
    <#
    .SYNOPSIS
    Get list of just specific/all application/s.
 
    .DESCRIPTION
    Get list of just specific/all machine/s.
 
    .PARAMETER softwareId
    (optional) specific software ID you want to retrieve.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $allApplications = Get-M365DefenderSoftware -header $header
 
    Get all applications from defender portal.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $application = Get-M365DefenderSoftware -softwareId samsung-_-petservice -header $header
 
    Get just one specific application from defender portal.
 
    .NOTES
    Requires Software.Read.All permission.
 
    https://learn.microsoft.com/en-us/defender-endpoint/api/get-software?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        [string] $softwareId,

        $header,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    $url = "https://$apiUrl/api/software"
    if ($softwareId) {
        $url = $url + "/$softwareId"
    }

    Invoke-RestMethod2 -uri $url -headers $header
}

function Get-M365DefenderVulnerability {
    <#
    .SYNOPSIS
    Get list of all/just one vulnerabilities/y.
 
    .DESCRIPTION
    Get list of all/just one vulnerabilities/y.
 
    .PARAMETER vulnerabilityId
    (optional) specific vulnerability ID you want to retrieve.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $allVulnerabilities = Get-M365DefenderVulnerability -header $header
 
    Get all vulnerabilities.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
    Get-M365DefenderVulnerability -header $header -vulnerabilityId "CVE-2022-40674"
 
    Get vulnerability "CVE-2022-40674" details.
 
    .NOTES
    Requires Vulnerability.Read.All permission.
 
    It can can take several minutes (there is more than 200 000 vulnerabilities now)!
 
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/get-all-vulnerabilities?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        [string] $vulnerabilityId,

        $header,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    $url = "https://$apiUrl/api/vulnerabilities"
    if ($vulnerabilityId) {
        $url = $url + "/$vulnerabilityId"
    }

    # for some reason it doesn't provides '@odata.nextLink' so I need to loop through "manually"
    # returned output is limited to 8000 items per call
    $apiDefaultLimit = 8000
    $round = 0
    do {
        $urlFinal = $url

        if (!$vulnerabilityId) {
            # doesn't make sense deal with skip if only one vulnerability will be outputted
            $urlFinal = $urlFinal + '?$skip=' + ($apiDefaultLimit * $round)
        }

        Write-Verbose "Retrieval round: $round url: $urlFinal"

        $result = Invoke-RestMethod2 -uri $urlFinal -headers $header

        $result

        ++$round
    } while ($result -and !$vulnerabilityId -and !($result.count % $apiDefaultLimit))
}

function Get-M365DefenderVulnerabilityReport {
    <#
    .SYNOPSIS
    Function process vulnerabilities returned by Get-M365DefenderMachineVulnerability, process them and returns as custom PSObject.
 
    .DESCRIPTION
    Function process vulnerabilities returned by Get-M365DefenderMachineVulnerability, process them and returns as custom PSObject.
 
    .PARAMETER groupBy
    Possible values: machine, productName
 
    .PARAMETER severity
    Possible values: Low, Medium, High, Critical
 
    By default Critical.
 
    .PARAMETER skipOSVuln
    Switch for skipping Operating System vulnerabilities.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    $vulnerabilityPerMachine = Get-M365DefenderVulnerabilityReport -groupBy machine -header $header -skipOSVuln -severity Critical
    #>


    [CmdletBinding()]
    param (
        [ValidateSet('machine', 'productName')]
        [string] $groupBy,

        [ValidateSet('Low', 'Medium', 'High', 'Critical', ignorecase = $False)]
        [string[]] $severity = "Critical",

        [switch] $skipOSVuln,

        $header
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    $machineVulnerability = Get-M365DefenderMachineVulnerability -header $header -severity $severity

    if ($skipOSVuln) {
        $machineVulnerability = $machineVulnerability | ? productName -NotIn "windows_10", "windows_11", "mac_os"
    }

    # get all machines to be able to translate id to dnsname
    $allMachines = Get-M365DefenderMachine -header $header

    if ($groupBy -eq "machine") {
        $vulnGroupedByMachineId = $machineVulnerability | group MachineId

        foreach ($groupedData in $vulnGroupedByMachineId) {
            $machineId = $groupedData.Name

            $groupSw = $groupedData.group | group productName, productVersion
            $VulnSW = $groupSw | % {
                [PSCustomObject]@{
                    VulnSW         = (@($_.Group.productName)[0] + ", " + @($_.Group.productVersion)[0])
                    productName    = @($_.Group.productName)[0]
                    productVersion = @($_.Group.productVersion)[0]
                    productVendor  = @($_.Group.productVendor)[0]
                    cveId          = $_.Group.cveId
                    fixingKbId     = $_.Group.fixingKbId
                }
            }

            [PSCustomObject]@{
                ComputerName = ($allMachines.where({ $_.Id -eq $machineId })).computerDnsName
                MachineId    = $machineId
                VulnSW       = $groupSw.Name
                VulnSWData   = $VulnSW
            }
        }
    } elseif ($groupBy -eq "productName") {
        $vulnGroupedByProdName = $machineVulnerability | group productName

        foreach ($groupedData in $vulnGroupedByProdName) {
            $productName = $groupedData.Name

            $groupSw = $groupedData.group | group productName, productVersion
            $VulnSW = $groupSw | % {
                $machineId = $_.group.machineid | select -Unique
                [PSCustomObject]@{
                    VulnSW         = (@($_.Group.productName)[0] + ", " + @($_.Group.productVersion)[0])
                    productName    = @($_.Group.productName)[0]
                    productVersion = @($_.Group.productVersion)[0]
                    productVendor  = @($_.Group.productVendor)[0]
                    cveId          = $_.Group.cveId | select -Unique
                    fixingKbId     = $_.Group.fixingKbId
                    machineid      = $machineId
                    ComputerName   = ($allMachines.where({ $_.Id -in $machineId })).computerDnsName
                }
            }

            [PSCustomObject]@{
                ProductName  = $productName
                ComputerName = (($allMachines.where({ $_.Id -in $groupedData.group.machineid })).computerDnsName | sort)
                MachineId    = $groupedData.group.machineid | select -Unique
                VulnSW       = $groupSw.Name
                VulnSWData   = $VulnSW
            }
        }
    } else {
        # don't group the results
        $machineVulnerability | select *, @{n = 'ComputerName'; e = { $machineId = $_.MachineId; ($allMachines.where({ $_.Id -eq $machineId })).computerDnsName } }
    }
}

function Invoke-M365DefenderAdvancedQuery {
    <#
    .SYNOPSIS
    Returns result of the specified KQL.
 
    .DESCRIPTION
    Returns result of the specified KQL.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    Invoke-M365DefenderAdvancedQuery -header $header -query "DeviceInfo | join kind = fullouter DeviceTvmSoftwareEvidenceBeta on DeviceId"
 
    Returns result of the selected KQL query.
 
    .NOTES
    Requires AdvancedQuery.Read.All permission.
 
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/run-advanced-query-api?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        [string] $query,

        $header,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    $url = "https://$apiUrl/api/advancedqueries/run"

    $queryBody = ConvertTo-Json -InputObject @{ 'Query' = $query }

    Write-Verbose "Query: $query"

    Invoke-RestMethod2 -uri $url -headers $header -method POST -body $queryBody -ErrorAction Stop | select -ExpandProperty Results
}

function Invoke-M365DefenderSoftwareEvidenceQuery {
    <#
    .SYNOPSIS
    Get Software Evidence query results.
 
    .DESCRIPTION
    Get Software Evidence query results from DeviceTvmSoftwareEvidenceBeta table.
 
    .PARAMETER appName
    (optional) name of the app you want to get data for.
 
    .PARAMETER appVersion
    (optional) version of the app you want to get data for.
 
    .PARAMETER deviceId
    (optional) ID of the device you want to get data for.
 
    .PARAMETER header
    Header created using New-M365DefenderAuthHeader.
 
    .PARAMETER apiUrl
    API url.
 
    By default "api-eu.securitycenter.microsoft.com" for best performance in EU region.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    Invoke-M365DefenderSoftwareEvidenceQuery -header $header
 
    Get all (100 000 at most) results of Software Evidence table query.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    Invoke-M365DefenderSoftwareEvidenceQuery -header $header -appName JRE
 
    Get all (100 000 at most) results of Software Evidence table query related to JRE software.
 
    .NOTES
    Requires AdvancedQuery.Read.All permission.
 
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/run-advanced-query-api?view=o365-worldwide
    #>


    [CmdletBinding()]
    param (
        [string] $appName,

        [string] $appVersion,

        [string] $deviceId,

        $header,

        [ValidateSet("api.securitycenter.microsoft.com", "api-eu.securitycenter.microsoft.com", "api-us.securitycenter.microsoft.com", "api-uk.securitycenter.microsoft.com", "api-au.securitycenter.microsoft.com")]
        [string] $apiUrl = "api-eu.securitycenter.microsoft.com"
    )

    if (!$header) {
        $header = New-M365DefenderAuthHeader -ErrorAction Stop
    }

    #region create query
    $query = "DeviceTvmSoftwareEvidenceBeta`n| sort by SoftwareName, SoftwareVersion"

    if ($appName) {
        $query += "`n| where SoftwareName has '$appName'"
    }
    if ($appVersion) {
        $query += "`n| where SoftwareVersion has '$appVersion'"
    }
    if ($deviceId) {
        $query += "`n| where DeviceId has '$deviceId'"
    }
    #endregion create query

    Write-Verbose "Running query:`n$query"

    Invoke-M365DefenderAdvancedQuery -header $header -query $query
}

function New-M365DefenderAuthHeader {
    <#
    .SYNOPSIS
    Function creates authentication header for accessing Microsoft 365 Defender API.
 
    .DESCRIPTION
    Function creates authentication header for accessing Microsoft 365 Defender API.
 
    Support authentication using Managed identity, current user, app secret.
 
    .PARAMETER credential
    Application ID (as username), application secret (as password).
 
    .PARAMETER identity
    Use managed identity to authenticate.
    https://learn.microsoft.com/en-us/answers/questions/1394819/authenticate-to-microsoft-defender-for-endpoint-ap
 
    .PARAMETER tenantId
    ID of your tenant.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader
 
    # Send the webrequest and get the results.
    $url = "https://api.securitycenter.microsoft.com/api/alerts?`$filter=alertCreationTime ge $dateTime"
    $response = Invoke-WebRequest -Method Get -Uri $url -Headers $header -ErrorAction Stop
 
    # Extract the alerts from the results.
    $alerts = ($response | ConvertFrom-Json).value | ConvertTo-Json
 
    Interactive authentication using provided credentials.
 
    .EXAMPLE
    Connect-AzAccount
 
    $header = New-M365DefenderAuthHeader
 
    Silent authentication using currently authenticated user.
 
    .EXAMPLE
    $header = New-M365DefenderAuthHeader -credential $credential
 
    Silent authentication using provided credentials.
 
    .EXAMPLE
    Connect-AzAccount -identity
 
    $header = New-M365DefenderAuthHeader -identity
 
    Silent authentication using managed identity.
 
    .NOTES
    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/exposed-apis-create-app-webapp?view=o365-worldwide#use-powershell
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = "Credential")]
        [System.Management.Automation.PSCredential] $credential,

        [Parameter(Mandatory = $true, ParameterSetName = "ManagedIdentity")]
        [switch] $identity,

        [Parameter(Mandatory = $false, ParameterSetName = "Credential")]
        [ValidateNotNullOrEmpty()]
        $tenantId = $_tenantDomain
    )

    if ($credential -and !$tenantId) {
        throw "TenantId parameter cannot be empty!"
    }

    if ($identity) {
        # connecting using managed identity

        if (!(Get-Command "Get-AzAccessToken" -ea SilentlyContinue)) {
            throw "'Get-AzAccessToken' command is missing (module Az.Accounts). Unable to continue"
        }

        $sourceAppIdUri = 'https://api.securitycenter.microsoft.com/.default'
        $response = Get-AzAccessToken -ResourceUri $sourceAppIdUri
        $token = $response.token

        if (!$token) {
            throw "Unable to obtain an auth. token. Are you authenticated using managed identity via 'Connect-AzAccount -Identity'?"
        }
    } else {
        # connecting using credentials

        if ($credential) {
            # connecting using provided credentials
            $oAuthUri = "https://login.microsoftonline.com/$tenantId/oauth2/token"
            $authBody = [Ordered]@{
                scope         = 'https://api.securitycenter.microsoft.com/.default'
                client_id     = $credential.username
                client_secret = $credential.GetNetworkCredential().password
                grant_type    = 'client_credentials'
            }

            $authResponse = Invoke-RestMethod -Method Post -Uri $oAuthUri -Body $authBody -ErrorAction Stop
            $token = $authResponse.access_token
        } else {
            # connecting using existing Azure session
            $AccessToken = Get-AzAccessToken -ResourceUri 'https://api.securitycenter.microsoft.com' -ErrorAction Stop
            $token = $AccessToken.token
        }

        if (!$token) {
            throw "Unable to obtain an auth. token"
        }
    }

    $headers = @{
        'Content-Type' = 'application/json'
        Accept         = 'application/json'
        Authorization  = "Bearer $token"
    }

    return $headers
}

Export-ModuleMember -function Get-M365DefenderMachine, Get-M365DefenderMachineUser, Get-M365DefenderMachineVulnerability, Get-M365DefenderRecommendation, Get-M365DefenderSoftware, Get-M365DefenderVulnerability, Get-M365DefenderVulnerabilityReport, Invoke-M365DefenderAdvancedQuery, Invoke-M365DefenderSoftwareEvidenceQuery, New-M365DefenderAuthHeader