Sophos Central API Academy 2024 sample scripts

Disclaimer: This information is provided as-is for the benefit of the Community. Please contact Sophos Professional Services if you require assistance with your specific environment.

Below are the samples used in the Central API Academy webinar series which take place on the 16th and 17th of September 2024. You can use the link below to register for these webinars or to view the recordings after the event took place:

English: https://register.gotowebinar.com/register/9000387121772739927
German: https://attendee.gotowebinar.com/register/8012522103703881564


By using or accessing the Software below, you agree to be bound by the terms of the Sophos End User License Agreement


The first three code samples provide the general authentication against the Central platform

#01 covers Central Admin = customers
#02 covers Central Organization = customers using Enterprise Dashboard
#03 covers Central Partner = partners using Partner Dashboard

Put the code from the samples starting #5 (except the ones where it says “[Standalone]” these already include the authentication code) into e.g. Central Admin Authentication code where it says #### INSERT CODE HERE #####

The API credentials needed to run the samples are created in Central Admin, Enterprise Dashboard or Partner Dashboard respectively.

Sophos Central API Developer Reference https://developer.sophos.com/

XDR Schema docs https://docs.sophos.com/central/References/schemas/index.html?schema=xdr_schema_docs


#01 Central Admin Authentication - Basic sample showing how to authenticate using Central Admin API credentials and allowing to securely store these credentials locally.

param ([switch] $SaveCredentials)
<#
    Description: Authentication Script for Sophos Central
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC, this is needed when
                                    running the script unattended
#>

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Admin Authentication Example"
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos_Central_Admin_Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
    if ($ClientID -eq "") {Break}
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context
$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $ClientId , $ClientSecret

# SOPHOS OAuth URL
$AuthURI = "https://id.sophos.com/api/v2/oauth2/token"

# Body and Header for oAuth2 Authentication
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthBody.Add("client_id", $SecureCredentials.GetNetworkCredential().Username)
$AuthBody.Add("client_secret", $SecureCredentials.GetNetworkCredential().Password)
$AuthBody.Add("scope", "token")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $ClientSecret = $ClientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$Token = $Result.access_token

# SOPHOS Whoami URI:
$WhoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$WhoamiHead = @{}
$WhoamiHead.Add("Content-Type", "application/json")
$WhoamiHead.Add("Authorization", "Bearer $Token")

# Post Request to SOPHOS for Whoami Details:
$Result = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Check if we are using tenant (Central Admin) credentials
if ($Result.idType -ne "tenant") {
    Write-Output "Aborting script - idType does not match tenant!"
    Break
}

# Save Response details
$TenantID = $Result.id
$DataRegion = $Result.ApiHosts.dataRegion

################# INSERT CODE HERE ###############

#02 Central Organization Authentication - Basic sample showing how to authenticate using Central Enterprise API credentials and allowing to securely store these credentials locally. It then uses the Organization API to loop through all Tenants

param ([switch] $SaveCredentials)
<#
    Description: Authentication Script for Sophos Central using Organization Credentials
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC, this is needed when
                                    running the script unattended
#>

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Organization Authentication Example"
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos_Central_Organization_Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
    if ($ClientID -eq "") {Break}
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context
$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $ClientId , $ClientSecret

# SOPHOS OAuth URL
$AuthURI = "https://id.sophos.com/api/v2/oauth2/token"

# Body and Header for oAuth2 Authentication
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthBody.Add("client_id", $SecureCredentials.GetNetworkCredential().Username)
$AuthBody.Add("client_secret", $SecureCredentials.GetNetworkCredential().Password)
$AuthBody.Add("scope", "token")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $ClientSecret = $ClientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$Token = $Result.access_token

# SOPHOS Whoami URI:
$WhoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$WhoamiHead = @{}
$WhoamiHead.Add("Content-Type", "application/json")
$WhoamiHead.Add("Authorization", "Bearer $Token")

# Post Request to SOPHOS for Whoami Details:
$Result = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Check if we are using organization (Central Enterprise Dashboard) credentials
if ($Result.idType -ne "organization") {
    Write-Output "Aborting script - idType does not match organization!"
    Break
}

# Save Response details
$OrganizationID = $Result.id

# SOPHOS Organization API Headers:
$OrganizationHead = @{}
$OrganizationHead.Add("Authorization", "Bearer $Token")
$OrganizationHead.Add("X-Organization-ID", "$OrganizationID")

# Get all Tenants
$TenantPage = 1
do {

    $TenantList = (Invoke-RestMethod -Method Get -Uri "https://api.central.sophos.com/organization/v1/tenants?pageTotal=true&pageSize=100&page=$TenantPage" -Headers $OrganizationHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    foreach ($Tenant in $TenantList.items) {

        $TenantID = $Tenant.id
        $DataRegion = $Tenant.apiHost

        Write-Output ""
        Write-Output "-------------------------------------------------------------------------------"
        Write-Output $Tenant.showAs
        Write-Output "-------------------------------------------------------------------------------"

        ################# INSERT CODE HERE ###############
    }
    $TenantPage++

} while ($TenantPage -le $TenantList.pages.total)

#03 Central Partner Authentication - Basic sample showing how to authenticate using Central Partner API credentials and allowing to securely store these credentials locally. It then uses the Partner API to loop through all Tenants.

param ([switch] $SaveCredentials)
<#
    Description: Authentication Script for Sophos Central using Partner Credentials
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC, this is needed when
                                    running the script unattended
#>

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Partner Authentication Example"
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos_Central_Partner_Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
    if ($ClientID -eq "") {Break}
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context
$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $ClientId , $ClientSecret

# SOPHOS OAuth URL
$AuthURI = "https://id.sophos.com/api/v2/oauth2/token"

# Body and Header for oAuth2 Authentication
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthBody.Add("client_id", $SecureCredentials.GetNetworkCredential().Username)
$AuthBody.Add("client_secret", $SecureCredentials.GetNetworkCredential().Password)
$AuthBody.Add("scope", "token")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $ClientSecret = $ClientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$Token = $Result.access_token

# SOPHOS Whoami URI:
$WhoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$WhoamiHead = @{}
$WhoamiHead.Add("Content-Type", "application/json")
$WhoamiHead.Add("Authorization", "Bearer $Token")

# Post Request to SOPHOS for Whoami Details:
$Result = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Check if we are using partner (Central Partner Dashboard) credentials
if ($Result.idType -ne "partner") {
    Write-Output "Aborting script - idType does not match partner!"
    Break
}

# Save Response details
$PartnerID = $Result.id

# SOPHOS Partner API Headers:
$PartnerHead = @{}
$PartnerHead.Add("Authorization", "Bearer $Token")
$PartnerHead.Add("X-Partner-ID", "$PartnerID")

# Get all Tenants
$TenantPage = 1
do {

    $TenantList = (Invoke-RestMethod -Method Get -Uri "https://api.central.sophos.com/partner/v1/tenants?pageTotal=true&pageSize=100&page=$TenantPage" -Headers $PartnerHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    foreach ($Tenant in $TenantList.items) {

        $TenantID = $Tenant.id
        $DataRegion = $Tenant.apiHost

        Write-Output ""
        Write-Output "-------------------------------------------------------------------------------"
        Write-Output $Tenant.showAs
        Write-Output "-------------------------------------------------------------------------------"

        ################# INSERT CODE HERE ###############
    }
    $TenantPage++

} while ($TenantPage -le $TenantList.pages.total)

#04 Send Email [snippet] - Basic sample for sending emails through PowerShell with Gmail as relay. Can be used to send results via the body of the email instead of screen output.

# Send Email using Gmail and app password
# Please note that you have to prepare your Gmail account accordingly. 
# To be able to sent emails you namely have to activate MFA within your Gmail account and 
# once this has been done create an application password, for more details see: 
# https://support.google.com/accounts/answer/185833?hl=en.  

$SmtpSrvr = "smtp.gmail.com"
$SmtpPort = 587
$SmtpUser = "your@gmail.com"
$SmtpRcpt = "recipient@company.com"
$SmtpPass = "apppassword"

# Create MailMessage because it allows HTML content
$Message = New-Object System.Net.Mail.MailMessage
$Message.From = $SmtpUser
$Message.To.Add($SmtpRcpt)
$Message.Subject = "Automated Email"
$Message.IsBodyHtml = $false # set to true if you want to send an HTML based email
$Message.Body = "This is your content"

# Create the SmtpClient object and send the Email
$Smtp = New-Object Net.Mail.SmtpClient($SmtpSrvr, $SmtpPort)
$Smtp.EnableSsl = $true
if ($SmtpPass -ne "") {
    $Smtp.Credentials = New-Object System.Net.NetworkCredential( $SmtpUser, $SmtpPass );
}
$Smtp.Send($Message)

#05 Endpoint Status [snippet] - Basic sample of retrieving device health information. Uses the Endpoint  API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

$EndpointList = @()
$NextKey = $null

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

do {
    $GetEndpoints = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/endpoints?pageTotal=true&pageFromKey=$NextKey&fields=hostname,tamperProtectionEnabled,health,os&view=summary&sort=healthStatus" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    $NextKey = $GetEndpoints.pages.nextKey

    $EndpointList += $GetEndpoints.items
} while ($null -ne $NextKey)      

Write-Output $EndpointList | Format-Table -Property @{label='Name';e={$_.Hostname}}, 
                                                    @{label='TP Status';align='right';e={$_.tamperProtectionEnabled}},
                                                    @{label='Health Overall';align='right';e={$_.health.overall}}, 
                                                    @{label='Health Threats';align='right';e={$_.health.threats.status}}, 
                                                    @{label='Health Services';align='right';e={$_.health.services.status}}, 
                                                    @{label='OS';e={$_.os.name}}
                                                    

#06 SophosLabs Intelix [standalone] - Lookup SHA256 values, IP addresses or URLs to see their classification by SophosLabs. This can for example be used to check if a URL is already classified as malicious. Please note that you need SophosLabs Intelix credentials. For more information see “How to register” on https://api.labs.sophos.com/doc/.

param ([switch] $SaveCredentials, [switch] $IP, [switch] $IPfile, [Switch] $SHA, [Switch] $SHAfile, [switch] $URL, [switch] $URLfile, [string] $LookupValue)
<#
    Description: Perform an SophosLabs Intelix Lookup for a URL, IP, SHA256
    Parameters: -SaveCredentials -> will store the entered credentials locally on the PC
                -IP, -IPfile -SHA, -SHAfile, -URL, -URLfile -> define which lookup to perform 
                -LookupValue -> what to lookup
#>

Clear-Host
Write-Output "=============================================================================="
Write-Output "SophosLabs Intelix Lookup Request"
Write-Output "=============================================================================="

# Intelix Region to be used, must be "de", "us" or "au", please adjust this based on your location 
$Region = "de"  

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos-Intelix-Credentials.json'

# Check if Intelix Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
    if ($ClientID -eq "") {Break}
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context
$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $clientId , $clientSecret
$BasicAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($SecureCredentials.GetNetworkCredential().Username, $SecureCredentials.GetNetworkCredential().Password -join ":")))

# SophosLabs Intelix OAuth URL
$AuthURI = "https://api.labs.sophos.com/oauth2/token"

# Header and Body for oAuth2 authetication request
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")
$AuthHead.Add("authorization", "Basic $($BasicAuth)")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $clientSecret = $clientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SophosLabs Intelix Access Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$ReqHead=@{}
$ReqHead.Add("Authorization", "$($Result.access_token)")

if ((1 -ne $IP.IsPresent + $IPfile.IsPresent + $SHA.IsPresent + $SHAfile.IsPresent + $URL.IsPresent + $URLfile.IsPresent) -Or ($LookupValue -eq "")) {
    Write-Output "You have to call this script with one of the following parameters:"
    Write-Output "-IP <IP Address to check>"
    Write-Output "-IPfile <CSV-file containing IP-addresses>"
    Write-Output "-SHA <SHA-256 value to check>"
    Write-Output "-SHAfile <CSV-file containing SHA256 hashes>"
    Write-Output "-URL <URL to check>"
    Write-Output "-URLfile <CSV-file containing URL's>"
    Break
} 


# Lookup an IP address to see if its known malicious
if($IP) {
    try {
        $Result = (Invoke-RestMethod -Method GET -Uri "https://$($Region).api.labs.sophos.com/lookup/ips/v1/$($LookupValue)" -Headers $ReqHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "Results for IP Address: $($LookupValue)..."
        Write-Output "------------------------------------------------------------------------------"
        Write-Output "Productivity Category: $($Result.category)"
    } catch {
        Write-Output "[ERROR] $($_.Exception.Message)"
        Write-Output "        --> verify that '$($LookupValue)' is a valid IP address`n"
    }
}


# Filter list of IP addresses from a file for know malicious entries
if($IPfile) {

    # Define table to hold 'known bad' entries
    $KnownBad = New-Object System.Data.Datatable
    [void]$KnownBad.Columns.Add("IPAddress")
    [void]$KnownBad.Columns.Add("Categories")

    $FileName = Get-ChildItem $LookupValue
    $ExportFile = "$($FileName.Directory)\$($FileName.BaseName) (Intelix Checked)$($FileName.Extension)"

    $ImportFile = Import-Csv $LookupValue
    if ((($importFile | Get-Member -MemberType NoteProperty).Name) -notcontains "IPaddress") {
        Write-Output "'IPaddress' column does not exist in the CSV file, aborting script!"
        break
    }

    foreach ($Item in $ImportFile) {
        try {
            $Result = (Invoke-RestMethod -Method GET -Uri "https://$($Region).api.labs.sophos.com/lookup/ips/v1/$($Item.ipAddress)" -Headers $ReqHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
            if (($Result.category -match "malware") -or ($Result.category -match "botnet") -or ($Result.category -match "spam_mta") -or ($Result.category -match "illegal")) {
                [void]$KnownBad.Rows.Add($Item.ipAddress, "$($Result.category -join ', ')" )
            } else {
                $Item | Export-Csv $ExportFile -Encoding UTF8 -NoTypeInformation -Append
            }
        } catch {
            Write-Output "[ERROR] $($_.Exception.Message)"
            Write-Output "        --> verify that '$($Item.ipAddress)' is a valid IP address`n"
        }
    }

    Write-Output "The following entries are classified as 'Known Bad':"
    Write-Output $KnownBad | Format-Table
    Write-Output "The remaining entries have been saved in:"
    Write-Output "$($Exportfile)"
}


# Lookup a SHA256 to see if its known malicious
if ($SHA) {
    try {
        $Result = (Invoke-RestMethod -Method GET -Uri "https://$($Region).api.labs.sophos.com/lookup/files/v1/$($LookupValue)" -Headers $ReqHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "Results for SHA256: $($LookupValue)..."
        Write-Output "------------------------------------------------------------------------------"
        Write-Output "Reputation Score: $($Result.reputationScore)"
        Write-Output "  Detection Name: $($Result.detectionName)"
    } catch {
        Write-Output "[ERROR] $($_.Exception.Message)"
        Write-Output "        --> verify that '$($LookupValue)' is a valid SHA256`n"
    }
}


# Filter a list of SHA256 values from an CSV file for know malicious entries
if ($SHAFile) {

    # Define table to hold 'known bad' entries
    $KnownBad = New-Object System.Data.Datatable
    [void]$KnownBad.Columns.Add("SHA256")
    [void]$KnownBad.Columns.Add("Reputation Score")
    [void]$KnownBad.Columns.Add("Detection Name")

    $FileName = Get-ChildItem $LookupValue
    $ExportFile = "$($FileName.Directory)\$($FileName.BaseName) (Intelix Checked)$($FileName.Extension)"

    $ImportFile = Import-Csv $LookupValue
    if ((($importFile | Get-Member -MemberType NoteProperty).Name) -notcontains "SHA256") {
        Write-Output "'SHA256' column does not exist in the CSV file, aborting script!"
        break
    }

    foreach ($Item in $ImportFile) { 
        try {
            $Result = (Invoke-RestMethod -Method GET -Uri "https://$($Region).api.labs.sophos.com/lookup/files/v1/$($Item.sha256)" -Headers $ReqHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

            if ($Result.reputationScore -lt 30) {
                [void]$KnownBad.Rows.Add($Item.SHA256, $Result.reputationScore, $Result.detectionName)
            } else {
                $Item | Export-Csv $ExportFile -Encoding UTF8 -NoTypeInformation -Append
            }
            Start-Sleep -m 100
        } catch {
            Write-Output "[ERROR] $($_.Exception.Message)"
            Write-Output "        --> verify that '$($Item.sha256)' is a valid SHA256`n"
        }
    }

    Write-Output "The following entries are classified as 'Known Bad':"
    Write-Output $KnownBad | Format-Table
    Write-Output "The remaining entries have been saved in:"
    Write-Output "$($Exportfile)"
}


# Lookup a URL to see if its known malicious
if ($URL) {
    try {
        $Result = (Invoke-RestMethod -Method GET -Uri "https://$($Region).api.labs.sophos.com/lookup/urls/v1/$($LookupValue)" -Headers $ReqHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "Results for URL: $($LookupValue)..."
        Write-Output "------------------------------------------------------------------------------"
        Write-Output "Productivity Category: $($Result.productivityCategory)"
        Write-Output "                Score: $($Result.productivityScore)"
        Write-Output "    Security Category: $($Result.securityCategory)"
        Write-Output "                Score: $($Result.securityScore)"
        Write-Output "           Risk level: $($Result.riskLevel)"
    } catch {
        Write-Output "[ERROR] $($_.Exception.Message)"
        Write-Output "        --> verify that '$($LookupValue)' is a valid URL`n"
    }
}


# Filter list of URLs from a file for known malicious entries
if ($URLfile) {

    # Define table to hold 'known bad' entries
    $KnownBad = New-Object System.Data.Datatable
    [void]$KnownBad.Columns.Add("URL")
    [void]$KnownBad.Columns.Add("Productivity Category")
    [void]$KnownBad.Columns.Add("Security Category")
    [void]$KnownBad.Columns.Add("Score")
    [void]$KnownBad.Columns.Add("Risk Level")

    $FileName = Get-ChildItem $LookupValue
    $ExportFile = "$($FileName.Directory)\$($FileName.BaseName) (Intelix Checked)$($FileName.Extension)"

    $ImportFile = Import-Csv $LookupValue
    if ((($importFile | Get-Member -MemberType NoteProperty).Name) -notcontains "URL") {
        Write-Output "'URL' column does not exist in the CSV file, aborting script!"
        break
    }

    foreach ($Item in $ImportFile) { 

        try {
            $Result = (Invoke-RestMethod -Method GET -Uri "https://$($Region).api.labs.sophos.com/lookup/urls/v1/$($Item.URL)" -Headers $ReqHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
            if ($Result.securityScore -lt 20) {
                [void]$KnownBad.Rows.Add($Item.URL, $Result.productivityCategory, $Result.securityCategory, $Result.securityScore, $Result.riskLevel)
            } else {
                $Item | Export-Csv $ExportFile -Encoding UTF8 -NoTypeInformation -Append
            }
            Start-Sleep -m 100
        } catch {
            Write-Output "[ERROR] $($_.Exception.Message)"
            Write-Output "        --> verify that '$($Item.URL)' is a valid URL`n"
        }
    }

    Write-Output "The following entries are classified as 'Known Bad':"
    Write-Output $KnownBad | Format-Table
    Write-Output "The remaining entries have been saved in:"
    Write-Output "$($Exportfile)"
}

Write-Output "=============================================================================="

#07 Import blocked items [snippet] – Basic sample which shows how to use 3rd Party Threat Intel for blocking PE-files based on their SHA256. Uses the blocked-items function from the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Define URI for updating the local site list
$Uri = $DataRegion+"/endpoint/v1/settings/blocked-items"

# Import data from CSV
Write-Output "Importing SHA256 values from CSV..."
Write-Output ""
$importFile = Import-Csv $PSScriptRoot\SHA256_hashes.csv

# Iterate through all sites from CSV
Write-Output "Creating blocked items in Sophos Central..."
Write-Output ""

foreach ($Item in $ImportFile){
    $Body = '{ "type": "sha256", "properties": { "sha256": "' + $Item.SHA256 + '" }, "comment": "' + $Item.Comment + '" }'

    # Invoke Request
    try {
        $Result = (Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Headers $TenantHead -Body $Body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "Created blocked item for '$($Result.comment)' with ID $($Result.id)"
    } catch {
        $WebError = ($_.ErrorDetails.Message | ConvertFrom-Json).message
        Write-Output "Blocked item '$($Item.comment)' --> $($WebError)"
    }
    
}
Write-Output ""
Write-Output "Successfully created local sites in Sophos Central..."

Sample file for use with this snippet:
sha256,comment
0f66c6e095a01f9709be73cde3ccbb43a2a30af57e15d6f099278a13d31b932e,Unknow
f5d39e20d406c846041343fe8fbd30069fd50886d7d3d0cce07c44008925d434,Megacortex

#08 Import website tags [snippet] – Basic sample which shows how 3rd Party Threat Intel for blocking access to URLs and IP-addresses or how to reclassify specific websites. Uses the local-sites function from the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Define URI for updating the local site list
$Uri = $DataRegion+"/endpoint/v1/settings/web-control/local-sites"


# Import data from CSV
Write-Output "Importing sites from CSV..."
Write-Output ""
$importFile = Import-Csv $PSScriptRoot\websites.csv

# Iterate through all sites from CSV
Write-Output "Creating local sites in Sophos Central..."
Write-Output ""

foreach ($Item in $ImportFile){

    # Split string in case of multiple tags
    $Tags = @($Item.tags.split("{;}"))

    # Change tags into an array
    $Item.PSobject.Properties.Remove('tags')
	$Item | Add-Member -NotePropertyName tags -NotePropertyValue $Tags
    
    # Create request body by converting to JSON
    $Body = $Item | ConvertTo-Json

    # Invoke Request
    $Result = (Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Headers $TenantHead -Body $Body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    Write-Output "Created Site: $($Result.url) with ID $($Result.id)"
    
}
Write-Output ""
Write-Output "Successfully created local sites in Sophos Central..."

Sample file for use with this snippet:
url,categoryId,tags,comment
www.abcd.de,,Block,
www.bcda.de,,Allow,
www.cdab.de,,Warn,
www.dabc.de,35,,
translate.google.com,,Allow,Blocked by default as Proxy/Translator

#09 Firewall Inventory [snippet] – Basic sample of listing the status of all Sophos Firewalls managed in Sophos Central. Uses the Firewall API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

Write-Output "Getting firewall information for Sophos Central..."
Write-Output ""
	
# SOPHOS API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")

if ($null -ne $DataRegion){
	# Post Request to Firewall API:
	$Result = (Invoke-RestMethod -Method Get -Uri $DataRegion"/firewall/v1/firewalls" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

#Output in table format
$Result.items | Format-Table -Property  @{label='Hostname';e={$_.hostname}},@{label='Firewall Name';e={$_.name}},  
                                        @{label='Serial Number';e={$_.serialNumber}}, @{label='Firmware Version';e={$_.firmwareVersion}}, 
                                        @{label='Firewall Model';e={$_.model}}, @{label='Cluster Mode';e={$_.cluster.mode}}, @{label='Cluster status';e={$_.cluster.status}}, 
                                        @{label='Central Connection';e={$_.status.connected}}
                                        

#10 Firewall Upgrade Check [snippet] - Basic sample of listing the update status of all Sophos Firewalls managed in Central Admin. Uses the Firewall API and its function firmware-upgrade-check.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# Create table for storing results
$FirewallTable = New-Object System.Data.Datatable
[void]$FirewallTable.Columns.Add("Serial")
[void]$FirewallTable.Columns.Add("Hostname")
[void]$FirewallTable.Columns.Add("Label")
[void]$FirewallTable.Columns.Add("Current Firmware")
[void]$FirewallTable.Columns.Add("Upgrade to")

# SOPHOS API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Post Request to Firewall API:
$FWList = (Invoke-RestMethod -Method Get -Uri $DataRegion"/firewall/v1/firewalls" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

foreach ($FW in $FWList.items) {

    # Only check firewall with Firewall Management active
    if ($null -ne $FW.status.managingStatus) {

        $Body = '{ "firewalls": ["'+$FW.id+'"]}'

        try {
            $FWCheck = (Invoke-RestMethod -Method POST -Uri $DataRegion"/firewall/v1/firewalls/actions/firmware-upgrade-check" -Headers $TenantHead -Body $Body)

            $UpgradeTo = ""
            Foreach ($FWVersion in $FWCheck.firmwareVersions ){	
                if ($UpgradeTo -ne "") {
                    $UpgradeTo = $UpgradeTo + "`n"    
                }
                $UpgradeTo = $UpgradeTo + $FWVersion.version + " (size: " + $FWVersion.size + ")" + " "+ $FWVersion.news[0]
            }

            [void]$FirewallTable.Rows.Add($FW.serialNumber,$FW.hostname, $FW.name, $FW.firmwareVersion, $UpgradeTo)

        } catch {
            # No result found --> Central Firewall Management not active!
        }
    }
}

# Display the results 
Write-Output $FirewallTable | Format-Table -wrap

#11 Firewall Upgrade Check Partner [standalone] - Basic sample of listing the update status of all customer firewalls – requires Partner Assistance to be active within the customer’s account. Works standalone and does not need the auth code in front. Uses the Firewall API and its function firmware-upgrade-check.

param ([switch] $SaveCredentials)
<#
    Description: Scan for managed Sophos Firewalls with pending Upgrades
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC, this is needed when
                                    running the script unattended
#>

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Scan tenants for firewalls with pending updates..."
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos_Central_Partner_Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context

$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $ClientId , $ClientSecret

# SOPHOS OAuth URL
$AuthURI = "https://id.sophos.com/api/v2/oauth2/token"

# Body and Header for oAuth2 Authentication
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthBody.Add("client_id", $SecureCredentials.GetNetworkCredential().Username)
$AuthBody.Add("client_secret", $SecureCredentials.GetNetworkCredential().Password)
$AuthBody.Add("scope", "token")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $ClientSecret = $ClientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$Token = $Result.access_token

# SOPHOS Whoami URI:
$WhoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$WhoamiHead = @{}
$WhoamiHead.Add("Content-Type", "application/json")
$WhoamiHead.Add("Authorization", "Bearer $Token")

# Post Request to SOPHOS for Whoami Details:
$Result = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Check if we are using partner (Central Partner Dashboard) credentials
if ($Result.idType -ne "partner") {
    Write-Output "Aborting script - idType does not match partner!"
    Break
}

# Save Response details
$PartnerID = $Result.id

# SOPHOS Partner API Headers:
$PartnerHead = @{}
$PartnerHead.Add("Authorization", "Bearer $Token")
$PartnerHead.Add("X-Partner-ID", "$PartnerID")

# Create table for storing results
$FirewallTable = New-Object System.Data.Datatable
[void]$FirewallTable.Columns.Add("Tenant Name")
[void]$FirewallTable.Columns.Add("Serial")
[void]$FirewallTable.Columns.Add("Hostname")
[void]$FirewallTable.Columns.Add("Label")
[void]$FirewallTable.Columns.Add("Current Firmware")
[void]$FirewallTable.Columns.Add("Upgrade to")

# Process all Tenants
Write-Output "Scanning tenants..."
$TenantPage = 1
$TenantCurr = 0

do {
    $TenantList = (Invoke-RestMethod -Method Get -Uri "https://api.central.sophos.com/partner/v1/tenants?pageTotal=true&pageSize=100&page=$TenantPage" -Headers $PartnerHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    foreach ($Tenant in $TenantList.items) {

        $TenantCurr = $TenantCurr + 1
        $TenantComp = ($TenantCurr/($TenantList.pages.items)*100)
        Write-Progress -Activity "Scanning tenants..." -Status "Progress:" -PercentComplete $TenantComp

        $TenantID = $Tenant.id
        $DataRegion = $Tenant.apiHost

        # SOPHOS API Headers:
        $TenantHead = @{}
        $TenantHead.Add("Authorization", "Bearer $Token")
        $TenantHead.Add("X-Tenant-ID", "$TenantID")
        $TenantHead.Add("Content-Type", "application/json")

        # If DataRegion is not set then the account was not activated properly (e.g. the Central datacenter was not yet selected)
        if ($null -ne $DataRegion) {
        
            # Post Request to Firewall API:
            try {
                $FWList = (Invoke-RestMethod -Method Get -Uri $DataRegion"/firewall/v1/firewalls" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
            } catch {
                # No Access to Tenant - If needed ask the customer to enable Partner Assistance
            }
        
            foreach ($FW in $FWList.items) {

                # Only check firewall with Firewall Management active
                if ($null -ne $FW.status.managingStatus) {

                    $Body = '{ "firewalls": ["'+$FW.id+'"]}'

                    try {
                        $FWCheck = (Invoke-RestMethod -Method POST -Uri $DataRegion"/firewall/v1/firewalls/actions/firmware-upgrade-check" -Headers $TenantHead -Body $Body)

                        $UpgradeTo = ""
                        Foreach ($FWVersion in $FWCheck.firmwareVersions ){	
                            if ($UpgradeTo -ne "") {
                                $UpgradeTo = $UpgradeTo + "`n"    
                            }
                            $UpgradeTo = $UpgradeTo + $FWVersion.version + " (size: " + $FWVersion.size + ")" + " "+ $FWVersion.news[0]
                        }

                        [void]$FirewallTable.Rows.Add($Tenant.showAs,$FW.serialNumber,$FW.hostname, $FW.name, $FWCheck.firewalls[0].firmwareVersion, $UpgradeTo)

                    } catch {
                        # No result found --> Central Firewall Management not active!
                    }
                }
            }
        }
    }
    $TenantPage++

} while ($TenantPage -le $TenantList.pages.total)

# Stop displaing the progress bar
Write-Progress -Activity "Scanning tenants..." -Completed

# Display the results 
Write-Output $FirewallTable | Format-Table -wrap

#12 Generate Emails from Events [standalone] – Basic sample showing how to generate emails for specific events/devices. Uses the SIEM Events API.

param ([switch] $SaveCredentials)
<#
    Description: Generate Emails for specific events
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC, this is needed when
                                    running the script unattended
#>

Function SendMail {
    param([parameter(Mandatory)]$Subject, [parameter(Mandatory)]$Body)

    # Please note that you have to prepare your Gmail account accordingly. 
    # To be able to sent emails you namely have to activate MFA within your Gmail account and 
    # once this has been done create an application password, for more details see: 
    # https://support.google.com/accounts/answer/185833?hl=en.  

    $SmtpSrvr = "smtp.gmail.com"
    $SmtpPort = 587
    $SmtpUser = "your@gmail.com"
    $SmtpRcpt = "recipient@company.com"
    $SmtpPass = "apppassword"
    
    # Create MailMessage because it allows HTML content
    $Message = New-Object System.Net.Mail.MailMessage
    $Message.From = $SmtpUser
    $Message.To.Add($SmtpRcpt)
    $Message.Subject = $Subject
    $Message.IsBodyHtml = $true
    $Message.Body = $Body

    # Create the SmtpClient object and send the Email
    $Smtp = New-Object Net.Mail.SmtpClient($SmtpSrvr, $SmtpPort)
    $Smtp.EnableSsl = $true
    if ($SmtpPass -ne "") {
        $Smtp.Credentials = New-Object System.Net.NetworkCredential( $SmtpUser, $SmtpPass );
    }
    $Smtp.Send($Message)
}

# Sophos Logo for Embedding in Emails
$SophosLogo = '<img alt="Sophos Central" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABRCAMAAAATpzE9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEh
ZcwAADsMAAA7DAcdvqGQAAAL0UExURQAAAAD//wB//wCq/wB//wCZzAB/1ACR2gB/3wCNxgB/zACL0AB/1ACJ1wB/yACIzAB/zwCH0gB/xgCGyQB/zACFzgB/0ACFxwB/yQB6ywB/zQB6xgB/yAB7ygB/zAB7zQ
B/xwB7yAB/ygB7ywB/xgB8xwB/yQB8ygB/zAB8xwB/yAB8yQB/ygB8xgB/xwB8yAB6yQB8ygB6xgB9yAB6yQB9ygB6xgB9xwB6yAB9yQB7ygB9xgB7xwB9yAB7yQB9xgB7xwB9yAB7yAB9yQB7xgB9xwB7yAB6y
QB7xgB6xwB6yAB8yQB6xgB6yAB8yAB6xgB8xwB6xwB8yAB7xgB8xgB7xwB8xwB7yAB8xgB7xgB8xwB7yAB6xgB7xgB6xwB7xwB6yAB7xgB6xgB7xwB6yAB7xgB6xgB7xwB6xwB7yAB6xgB7xgB6xwB8xwB6xgB8
xgB7xwB8xwB7yAB6xgB7xgB6xwB6xgB7xgB6xwB7xwB6xwB7xgB6xgB7xwB6xwB7xgB6xgB7xgB6xwB7xwB6xgB7xgB6xwB7xwB6xgB6xgB6xgB6xwB7xwB6xgB7xgB6xwB7xwB6xgB7xgB6xgB7xwB6xwB7xgB
6xgB7xwB6xwB7xgB6xgB7xgB6xwB7xwB6xgB6xgB6xwB6xwB6xgB6xgB6xgB6xwB7xgB6xgB7xgB6xgB7xwB6xgB7xgB6xgB7xwB6xgB7xgB6xgB7xgB6xwB7xgB6xgB6xgB6xwB6xgB6xgB6xgB6xgB6xwB6xg
B6xgB6xgB6xwB6xgB6xgB6xgB7xwB6xgB7xgB6xgB7xwB6xgB7xgB6xgB6xgB6xwB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xwB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xgB7xwB6xgB7xgB6xgB6xgB6xgB6x
gB6xgB6xgB6xwB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xwB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xgB6xgcWjccAAAD7dFJOUwABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSor
LC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJS0xNT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd5ent8fX5/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKW
mp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+dcTykgAACbtJREFUeNrtm3tcFMcdwIcDeSkPOZDwiK
igRA1qfcVXFNRIlNYopTYaFRsl1gclsREMVTGQ1MRHFKPERGhErZr4aDQxtpRqQoiIohBjFFFQvIhJID5BvJt/ers7v73Z3du5pR/89HKf+/0BOzO/+c3vezs785uZXYR46TB+5a6ik1XnL1YrJQ5JxWtESvrq9
7Zm/2n6OF9ZEXLrppCIsCB3mVYoKZLmhhF9uU0UEL8oc+22TSsW/HZ4B2RNohfnffZV5bkLVpzPFTRc065jdUmgjUVmn261FD04lj5A0lZP6yZuncmf7GLROkuypX5eEDLvSXNHvF9NG9qfEi7nG17McP4jXsX3
C4y1ET5dZFIUfxZDtRalbuabhP+BcM45hZ3W3CBJtSUPsS1C18+xNkKfXJO1cuM2vRZCbJzfVsLIf1vvERk6i84MtvM84SasjTD0WzWNyi5aCDGe3zbCMbfVDO1wBZ2RzbYJB2JthP4XGD0wWBNhy8C2EA65p25
ptxtRqsC2CQs0Eh6QdJTvzlynRhx8UqeFEFfptBP6X6Fr/lB57ib9jKwVlEZj24Rd4DZfXDgiKlTv19FbLkKPiLXUKnmJv2Vek3ZY2pwjHUvrCkXZefCU5beYqp1wpeUB3jPFm8sJeqHEMt5E80ofkeT9d+L7Ph
7k30nhvAdCyfDz+iGmFIHtxtnUQH0Vcg2dJPfwY0ndiH+JP45mQt9GqHN+uKjiktwCuZ9waXeSNMYzPM8lNaawAUeA5XuD6exe30P+bAYh6nQebkeAVsIMMHyhM600RZwbulNjyCGW6/uJUgCb8O9g+AVpfhLkf
8oiRPMl3VQLoQF+0Wip1rtgaak5kUCuX2G5TjqQSccmhHtV5SIrKIXwJoBF2B38elUjYW+osElmKegOKSg3J54n18ks1/dQN11doqHBRfKSl6AkkUXoDmPSGo2EKWC2n2pvMoc2z5LLLJbv7xCllUzCuWC2t7zk
MfB9FYvQD+rnaiTcQVS+V7giPhfjEOpPLqvdGb7Phl7GHGr+BgOpsugSKTrIIhwCbmVqJKwlKvsV7T0OptIQ8rxLrgu91H3vYhRH8uw5z8WPH8vLyP6RgdbmilNKA4dJ0WUWoRg9vqiN0OUh3asl4gIPYr45cRD
s1m9Z9LuJzwjOjxkUHe5G1TiiFg7cOLI8DJROk7x9SsJ1MAfTM76cUGxkGE24RSJNFKG/6nNv8eWfdJeVy/2yPHFdO9CkHvO0FpJJpJb63WSyHJS91O/hYtBp0NGEVuWeZOydqWwQloNfcze0nGHp7Fj5DGNNLj
3J69xWGbu59RmoBlP3sGiwKEPjXjwhmstH2ggHSUZoqRwiRd/ykUgLw9TDVLLzcITV4HXueewgC3hp+SOU9bQdeRtjNBI+A6lfq89v9XxquollbIZQxTuXpbXHrKEDhc3KBtNAMdo2YT7SSBgHqSRlgzC6GITkc
w0MY7dCSaVhBXdVlUxRVC/9UNlgJijqbRJeCUZt7aXJ6muAKohyllWrW3tdrOYxaE7WuvdhxbO3qMqy+/G2ubieXB9QNrgaeqDOFuFPvZFWQnFHK1XZIDzVxVRQODlj9eYPYbn26dc/i9YaXFXnycD10DPPUj5V
KhX3kqKbtlbA52Lk+zQxEqmlCPVQaYOywZukaK/6JO+aJK55BjPiGHjATOYZYxfsQyh/km/ooU2dsC7duy17bQZq0pOF3lh9TLBIj58skY+6wE7eUwilg9kBciWfB6RkF014cT0la7KWPt+HXrtoIIT9v5uKnzQ
eXElhRtJ/hqiCpfSGZaNmIlYL0KdJ4jG1qE0qGgj/CmZj5ZW3QEkUs41eWjxJJUq/R8jrFmwpyNeR/6DX3O1HOBLMvier63mDFNSyF7S+RO0oS+ltosQFeIWSW2Vld6MGtSuhSx0Ejk9ItcTdjW1swr5EbTtDx/
8qFaw8Kw6/PSTrPnHgX9G+hOhNMPyFJ63UT9wljmMTbiBqOeoqXnCacZ9vQtzIu9LLoqMXcxv92pkwsEk8GKFOuPpDH6VnQ2uygAoZAvQKCQrvE/+XGizxdpg47jfnkGMx/cIfsWRl256EaKlo2zDXn5xjrLXsv
D7N53RUOq8P6TFy3mFxdWSOtO7a2jZOlG478rv4+/Jyd5dShz71Pu1O6HGSWsV9tTs3b995yoNPBKWNtpw/jGwTXvQgh6jH1HWa+qF2J0RdDeoNVvhqJBxvm9A0Blr0Vl1lNceiR0CIompUQ/gQpI2QX8zYIFxN
RXuLG62qXJuAHgkh6rzR+ulnqTjZ2yD8rrNNQlO6NCb8QLmSbM3zQ4+I0Dw5HLfySKRZog42YYlwdMsiPDVR7lzUK8Wtkqc0IwShR0eI0PAcyVrLVDLbmyplETYuJzPpiUqFnD1zurRo59p5T1iPBBJfXbP9aPm
Xh7bnTA6VlXUlFtYxCT8mWrL9FyGzTKEekfzaht3Fp/5zID9d/vZHeqUV7yvKju/fnBHriZziFKc4xSlOcYpTnPILkuCCyy2OKpcLghEKqc/s5u6o0i2zPgQVLnPoHrqsENWHOjRhWD3CDj7MYCehJvH0dWhCn+
xqI76xtRufeKMOxItPXeHPfXV1dQvN/6rFwixQrT2xzfIxwziDAd7ONde0G8Kwa8K+zx3+LfU8cR/IW0iVcvtiOoy5Edvy6ksupWqcB6Z2YPwluczDRnshdDmG8Zaxo7JbcJ2O98w0VxA3ApFqIZyalJT0EH9u/
vsrQTUh4TepNbi5p2DKl3s3P9LuCHti/Bb3f4HwJqjUM47wdleRkJNW/K70NvURP8P4A65uIId09kQ4jbztEHr1aqIVwtt3uLMRFqHHA7xeyDiOX9+ML7nYG+HLGEdImExP8eIjpK6lYTyNSTgD4yV8ugfGT8bC
qZk9E9LHe2ZC1zJ8I0CF0JScnLLzZ/xA+BptBa5COgPe+ksjRANacb4KIXmBUHjB0qWGO2XdiJs87Z3Q6MeLKxByb4VNUCEsLzffQfKGzmiMp8bEzOU6tX0Rzjc/Pdz/wJKSSVZGGjOh9yVco/ocJoolH0g+3LA
jwqEYv0bG1KHWCdEEzm+1keYQvs8fBnrfEl97fcy+CDtcw61ZfcNn/Yib3IXhI14QT5EQbWcQRtwVXl2bbp5PuS+x4jB+mbYz5P8ftQ0iXwkaJ0mjtq4WwsAfGLPFEuFDo6P4pDDg1OEK2k6xHUTeUYVcvHl8NF
IlRDMZhG4VuEGPwo3wEuFbGPe3M0KEXLv36+hcATsJnYROQgZhs4dDA3o0o7JRDk04qgzNLNc7MKC+3LxyWWUoyHFUKTCs4jgjZ6U7qsyKROi/sVLE4CyiNRgAAAAASUVORK5CYII=" />'
$MailCount = 0

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos - Generate Emails for specific events"
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos_Central_Admin_Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context
$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $ClientId , $ClientSecret

# SOPHOS OAuth URL
$AuthURI = "https://id.sophos.com/api/v2/oauth2/token"

# Body and Header for oAuth2 Authentication
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthBody.Add("client_id", $SecureCredentials.GetNetworkCredential().Username)
$AuthBody.Add("client_secret", $SecureCredentials.GetNetworkCredential().Password)
$AuthBody.Add("scope", "token")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $ClientSecret = $ClientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$Token = $Result.access_token

# SOPHOS Whoami URI:
$WhoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$WhoamiHead = @{}
$WhoamiHead.Add("Content-Type", "application/json")
$WhoamiHead.Add("Authorization", "Bearer $Token")

# Post Request to SOPHOS for Whoami Details:
$Result = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Check if we are using tenant (Central Admin) credentials
if ($Result.idType -ne "tenant") {
    Write-Output "Aborting script - idType does not match tenant!"
    Break
}

# Save Response details
$TenantID = $Result.id
$DataRegion = $Result.ApiHosts.dataRegion

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Load previously saved cursor so that we only process the latest events
try {
    $Cursor = Get-Content -Path "$($PSScriptRoot)\SIEM_Events_cursor.txt" -ErrorAction Stop
} catch {
    $Cursor = ""
}

# Event filter, only process events listed below
# Use https://support.sophos.com/support/s/article/KBA-000006285 for more event types
$ProcessEvents = @()
$ProcessEvents += "Event::Firewall::FirewallGatewayDown"
$ProcessEvents += "Event::Firewall::FirewallGatewayUp"
$ProcessEvents += "Event::Firewall::FirewallHAStateDegraded"
$ProcessEvents += "Event::Firewall::FirewallHAStateRestored"
$ProcessEvents += "Event::Endpoint::CorePuaDetection"
$ProcessEvents += "Event::Endpoint::HmpaPuaDetected"
$ProcessEvents += "Event::Endpoint::Threat::PuaDetected"

# Device filter, only process events for the following devices, use .* to include all devices 
# or enter multiple entries separated by |
$EndpointFilter = ".*"
$FirewallFilter = "C010012334568|C010019468345"

do {
    $GetEvents = (Invoke-RestMethod -Method Get -Uri $DataRegion"/siem/v1/events"$Cursor -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    $Cursor = "?limit=200&cursor=" + $GetEvents.next_cursor

    foreach ($Event in $GetEvents.items) {

        if ( $ProcessEvents -contains $Event.type) {

            if (($Event.endpoint_type -match 'server|computer') -And ($Event.location -match $EndpointFilter)) {
                $Subject = "[$($Event.severity)] Event for Sophos Central of type: $($Event.type)"
                $Body = "<p style=`"color:#808080`">This email was generated programatically. Please do not reply to this email</p>" + 
                "$($SophosLogo)<BR><BR><B>Sophos Central Event Details</B><BR><BR>" +
                "<B>What happened: </B>$($Event.name)<BR>" +
                "<B>Where it happened: </B>$($Event.location)<BR>" +
                "<B>When did it happen: </B>$($Event.when)<BR>" +
                "<B>User associated with device: </B>$($Event.source)<BR>" +
                "<B>How severe is it: </B>$($Event.severity)<BR><BR>"

                SendMail -Subject $Subject -Body $Body
                $MailCount++ 

            } elseif (-Not ($Event.endpoint_type -match 'server|computer') -And ($Event.location -match $FirewallFilter)) {
                $Subject = "[$($Event.severity)] Event for Sophos Central of type: $($Event.type)"
                $Body = "<p style=`"color:#808080`">This email was generated programatically. Please do not reply to this email</p>" + 
                "$($SophosLogo)<BR><BR><B>Sophos Central Event Details</B><BR><BR>" +
                "<B>What happened: </B>$($Event.name)<BR>" +
                "<B>Where it happened: </B>$($Event.location)<BR>" +
                "<B>When did it happen: </B>$($Event.when)<BR>" +
                "<B>How severe is it: </B>$($Event.severity)<BR><BR>"

                SendMail -Subject $Subject -Body $Body
                $MailCount++ 
            } 
        } 
    }
    
} while ($null -ne $NextKey)     

# Save cursor to ensure that we do process the same event multiple times
Set-Content -Path "$($PSScriptRoot)\CentralAdmin-EmailEvents.txt" -Value $Cursor

Write-Output "Number of Emails sent: $($MailCount)" 

#13 Email Quarantine Search [snippet] - Basic sample showing how to search the email quarantine for messages with attachments. Uses the quarantine search function the Email API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# Create table for storing quarantine data
$Quarantine = New-Object System.Data.Datatable
[void]$Quarantine.Columns.Add("When")
[void]$Quarantine.Columns.Add("From")
[void]$Quarantine.Columns.Add("To")
[void]$Quarantine.Columns.Add("Reason")
[void]$Quarantine.Columns.Add("Attachments")
[void]$Quarantine.Columns.Add("Subject")

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Calc last 30 days for the query in UTC format
$currtime = Get-Date
$fromtime = $currtime.AddDays(-30).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")
$tilltime = $currtime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")

try {
    $TenantBody = '{ "beginDate": "' + $fromtime + '", "endDate": "' + $tilltime + '" }'

    do {
        $Request = (Invoke-RestMethod -Method Post -Uri $DataRegion"/email/v1/quarantine/messages/search" -Headers $TenantHead -Body $TenantBody -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
        foreach ($Item in $Request.Items) {
            if ($Item.attachments.total -ne 0) {
                $QuarantinedAt = $Item.quarantinedAt
                $From = $Item.from.domainAddress
                $To = $Item.to.localaddress + '@' + $Item.to.domainAddress
                $Subject = $Item.subject
                $Reason = $Item.reason
                $Attachments = ""

                foreach ($AttItem in $Item.attachments.items) {
                    if ($Attachments -ne "") {
                        $Attachments += "`n"    
                    }
                    if ($AttItem.stripped) {
                        $Attachments += $AttItem.name + " (size: " + $AttItem.sizeInBytes + ") STRIPPED"
                    } else {
                        $Attachments += $AttItem.name + " (size: " + $AttItem.sizeInBytes + ")"
                    }
                }
                [void]$Quarantine.Rows.Add($QuarantinedAt, $From, $to, $Reason, $Attachments, $Subject)
            }
        }
        $TenantBody = '{ "beginDate": "' + $fromtime + '", "endDate": "' + $tilltime + '", "pageFromKey": "' + $Request.Pages.nextKey + '" }'
    } while ($null -ne $Request.Pages.nextKey)

} catch {
    # Something went wrong, get error details...
    Write-Host "   --> $($_)"
}

Write-Output "Attachments in the quarantine:"
Write-Output $Quarantine | Format-Table -wrap

#14 Central Partner MSP Billing Report [standalone] - Basic sample of retrieving monthly usage for Sophos MSP Flex Partners. Uses the billing usage function of the Partner API.

param ([switch] $SaveCredentials, $Month, $Year)
<#
    Description: MSP - Get Billing Report 
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC, this is needed when
                                    running the script unattended
                -Month -> will select the month for reporting
                -Year -> will select the reporing year
#>

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - MSP - Get Billing Report"
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$CredentialFile = $PSScriptRoot + '\Sophos-Central-Partner-Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $CredentialFile) -eq $false) -or $SaveCredentials){
	# Prompt for Credentials
	$ClientId = Read-Host "Please Enter your Client ID"
	$ClientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $Credentials = Get-Content $CredentialFile | ConvertFrom-Json
    $ClientId = $Credentials[0]
    $ClientSecret = $Credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context
$SecureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $ClientId , $ClientSecret

# SOPHOS OAuth URL
$AuthURI = "https://id.sophos.com/api/v2/oauth2/token"

# Body and Header for oAuth2 Authentication
$AuthBody = @{}
$AuthBody.Add("grant_type", "client_credentials")
$AuthBody.Add("client_id", $SecureCredentials.GetNetworkCredential().Username)
$AuthBody.Add("client_secret", $SecureCredentials.GetNetworkCredential().Password)
$AuthBody.Add("scope", "token")
$AuthHead = @{}
$AuthHead.Add("content-type", "application/x-www-form-urlencoded")

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $Result = (Invoke-RestMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $AuthHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($SaveCredentials) {
	    $ClientSecret = $ClientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $ClientID, $ClientSecret | Out-File $CredentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$Token = $Result.access_token

# SOPHOS Whoami URI:
$WhoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$WhoamiHead = @{}
$WhoamiHead.Add("Content-Type", "application/json")
$WhoamiHead.Add("Authorization", "Bearer $Token")

# Post Request to SOPHOS for Whoami Details:
$Result = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Check if we are using partner (Central Partner Dashboard) credentials
if ($Result.idType -ne "partner") {
    Write-Output "Aborting script - idType does not match partner!"
    Break
}

# Save Response details
$PartnerID = $Result.id

# SOPHOS Partner API Headers:
$PartnerHead = @{}
$PartnerHead.Add("Authorization", "Bearer $Token")
$PartnerHead.Add("X-Partner-ID", "$PartnerID")

if (($null -eq $Month) -or ($null -eq $Year)) {
    $Month = Get-Date -Format "MM";
    $Year = Get-Date -Format "yyyy";
}

# Post Request to Partner API:
try {
    $Uri = "https://api.central.sophos.com/partner/v1/billing/usage/" + $Year + "/" + "$Month"
    $Response = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $PartnerHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    # Store result in CSV File
    $Response.items | Export-Csv -Path $PSScriptRoot"\monthlyusage.csv"
} catch {
    Write-Output $_.Exception.Message
}

For non-MSP based accounts, you will find a good example of the licensing API in the Sophos Community, see:  Sophos Central Licensing API 

#15 Account Health Check [snippet] - Basic sample of performing the Central Admin platform health check. Uses the Account Health Check API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# Tenant Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

if ($null -ne $DataRegion){
	# Post Request to Firewall API:
	$Result = (Invoke-RestMethod -Method Get -Uri $DataRegion"/account-health-check/v1/health-check" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

Write-Host "Account Health Check"

### Output Protected Endpoints
Write-Host ("`nProtection Status")
Write-Host ("-------------------------------")
Write-Host ("Unprotected Computers: " + $Result.endpoint.protection.computer.notFullyProtected + " out of " + $Result.endpoint.protection.computer.total)
Write-Host ("Unprotected Servers: " + $Result.endpoint.protection.server.notFullyProtected + " out of " + $Result.endpoint.protection.server.total)

### Output Policy Status
Write-Host ("`nPolicy Status")
Write-Host ("-------------------------------")
Write-Host ("Computer policies not on recommended settings: " + $Result.endpoint.policy.computer.'threat-protection'.notOnRecommended + " out of " + $Result.endpoint.policy.computer.'threat-protection'.total)
Write-Host ("Server policies not on recommended settings : " + $Result.endpoint.policy.server.'server-threat-protection'.notOnRecommended + " out of " + $Result.endpoint.policy.server.'server-threat-protection'.total)

### Output Exclusions
Write-Host ("`nExclusion Status")
Write-Host ("-------------------------------")
Write-Host ("Risky exclusions for computers: " + $Result.endpoint.exclusions.policy.computer.numberOfSecurityRisks)
Write-Host ("Risky exclusions for servers: " + $Result.endpoint.exclusions.policy.server.numberOfSecurityRisks)
Write-Host ("Risky global exclusions: " + $Result.endpoint.exclusions.global.numberOfSecurityRisks)

### Output Tamper Protection
Write-Host ("`nTamper Protection")
Write-Host ("-------------------------------")
Write-Host ("Tamper Protection enabled for account: " + $Result.endpoint.tamperProtection.global)
Write-Host ("Computers with disabled Tamper Protection: " + $Result.endpoint.tamperProtection.computer.disabled + " out of " + $Result.endpoint.tamperProtection.computer.total )
Write-Host ("Servers  with disabled Tamper Protection: " + $Result.endpoint.tamperProtection.server.disabled + " out of " + $Result.endpoint.tamperProtection.server.total)

Partners or organizations using the Enterprise Dashboard will find a multi-tenant dashboard in the following community post:  Building Multi-Tenant Dashboards with Sophos Central API’s - Part 2: Health Check

#16 XDR Detections [snippet] - Basic sample showing how to pull XDR Detection data from Sophos Central. Uses the Detections API.

# add this code snippet to the auth code samples for Central (snippets 1)
# you will find a line that says INSERT CODE HERE

$Detections_Total = 0
$Detections_Critical = 0
$Detections_High = 0
$Detections_Medium = 0
$Detections_Low = 0
$Detections_Info = 0

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")
$TenantHead.Add("Content-Type", "application/json")

#Calc last 30 days for the query in UTC format
$currtime = Get-Date
$fromtime = $currtime.AddDays(-7).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")
$tilltime = $currtime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")

$TenantBody = '{ "from": "' + $fromtime + '", "to": "' + $tilltime + '"}'

try {
    Write-Output("[Detections] Send Request to retreive the detections from the last 7 days...")
    $Request = (Invoke-RestMethod -Method Post -Uri $DataRegion"/detections/v1/queries/detections" -Headers $TenantHead -Body $TenantBody -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
    Write-Output "[Detections] Request sent, wait for results..."

    do {
        Start-Sleep -Milliseconds 500
        $Request = (Invoke-RestMethod -Method Get -Uri $DataRegion"/detections/v1/queries/detections/$($Request.id)" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
    } while ($Request.Result -eq "notAvailable")
    Write-Output("[Detections] Request ready, retreiving results")

    $uri = "$($DataRegion)/detections/v1/queries/detections/$($Request.id)/results?pageSize=2000"
    $Detections =  (Invoke-RestMethod -Method Get -Uri $uri -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    foreach ($Detection in $Detections.items) {
        $Detections_Total += 1
        
        switch ($Detection.severity) {
            0 {$Detections_info += 0}
            1 {$Detections_Low += 1}
            2 {$Detections_Low += 1}
            3 {$Detections_Low += 1}
            4 {$Detections_Medium += 1}
            5 {$Detections_Medium += 1}
            6 {$Detections_Medium += 1}
            7 {$Detections_High += 1}
            8 {$Detections_High += 1}
            9 {$Detections_Critical += 1}
            10 {$Detections_Critical += 1}
        }
    }

    Write-Output("")
    Write-Output("[Detections] Details:")
    $Detections.items | Format-Table -Property `
        @{label='Severity';e={ if ($_.severity -eq 0) { "Info" } elseif ($_.severity -le 3) { "Low" } elseif ($_.severity -le 6) { "Medium" } elseif ($_.severity -le 8) { "High" } elseif ($_.severity -le 10) { "Critical" }}},
        @{label='Detection';e={$_.detectionRule}}, 
        @{label='time';e={$_.sensorGeneratedAt}}, 
        @{label='Host';e={$_.device.entity}}, 
        @{label='Sensor Type';e={$_.sensor.type}}, 
        @{label='MITRE Attack';e={$_.mitreAttacks.tactic.name}}

    Write-Output("[Detections] Summary:")
    Write-Output("$($Detections_Critical) (Critical) + $($Detections_High) (High) + $($Detections_Medium) (Medium) + $($Detections_Low) (Low) + $($Detections_Info) (Info)")
    Write-Output("$($Detections_Total) detections total")

} catch {
    # Something went wrong, get error details...
    Write-Host "   --> $($_)"
}

Partners or organizations using the Enterprise Dashboard will find a multi-tenant dashboard in the following community post:  Building Multi-Tenant Dashboards with Sophos Central API’s - Part 1: Detections 

#17 XDR Cases [snippet] - Basic sample showing how to pull XDR Cases data from Sophos Central. Uses the Cases API.

# add this code snippet to the auth code samples for Central (snippets 1)
# you will find a line that says INSERT CODE HERE

$Cases_Total = 0
$Cases_Critical = 0
$Cases_High = 0
$Cases_Medium = 0
$Cases_Low = 0
$Cases_Info = 0
$Cases_Unknown = 0

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")
$TenantHead.Add("Content-Type", "application/json")

try {
    Write-Output("[Cases] Retreive cases created in the last 30 days...")
    $Cases = (Invoke-RestMethod -Method get -Uri $DataRegion"/cases/v1/cases?createdAfter=-P30D&pageSize=5000" -Headers $TenantHead  -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   

    foreach ($Case in $Cases.items) {
        $Cases_Total += 1

        switch ($Case.severity) {
            "Informational" {$Cases_Info += 1}
            "low"           {$Cases_Low += 1}
            "medium"        {$Cases_Medium += 1}
            "high"          {$Cases_Higg += 1}
            "critical"      {$Cases_Critical += 1}
            Default         {$Cases_Unknown += 1}
        }
    }

    Write-Output("")
    Write-Output("[Cases] Details:")
    $Cases.items | Format-Table -Property `
        @{label='Severity';e={$_.severity}},
        @{label='Case ID';e={$_.id}}, 
        @{label='Status';e={$_.status}}, 
        @{label='Assignee';e={$_.assignee.name}}, 
        @{label='Name';e={$_.name}}, 
        @{label='Managed By';e={$_.managedBy}},
        @{label='Case type';e={$_.type}}

    Write-Output("[Cases] Summary:")
    Write-Output("$($Cases_Critical) (Critical) + $($Cases_High) (High) + $($Cases_Medium) (Medium) + $($Cases_Low) (Low) + $($Cases_Info) (Info) + $($Cases_Unknown) (Not classified yet)")
    Write-Output("$($Cases_Total) Cases total")

} catch {
    # Something went wrong, get error details...
    Write-Host "   --> $($_)"
}

Partners or organizations using the Enterprise Dashboard will find a multi-tenant dashboard in the following community post:  Building Multi-Tenant Dashboards with Sophos Central API’s - Part 3: Cases 

#18 XDR SHA256 Lookup [snippet] - Basic sample showing how to search the Data Lake for SHA256 values (PE and productivity documents). Uses the XDR API.
NOTE: You must replace the two instances of UN#I#ON with UNION before you use this snippet.

# add this code snippet to the auth code samples for Central (snippets 1)
# you will find a line that says INSERT CODE HERE

Write-Output "[XDR] Search for SHA256 ocurrances within the Data Lake."
Write-Output "      You can search multiple SHA-values by separting them with a |."
Write-Output ""

do {
    $SHA256 = [string](Read-Host -Prompt 'Enter the SHA256 value to look for')
} while ($SHA256.Length -lt 1)

# Calculate last 30 days for the query in UTC format
$currtime = Get-Date
$fromtime = $currtime.AddDays(-30).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")
$tilltime = $currtime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")

# SOPHOS XDR API Headers:
$XDRHead = @{}
$XDRHead.Add("Authorization", "Bearer $Token")
$XDRHead.Add("X-Tenant-ID", "$TenantID")
$XDRHead.Add("Content-Type", "application/json")


$template = @"
    WITH Connection_Info AS (

        SELECT DISTINCT meta_hostname, path, query_name, count(*)
        FROM xdr_data  
        WHERE query_name IN ('open_sockets') 
        GROUP BY meta_hostname, path, query_name
        
        UN#I#ON ALL
    
        SELECT DISTINCT meta_hostname, path, query_name, count(address)
        FROM xdr_data  
        WHERE query_name IN ('listening_ports') 
            AND address NOT IN ('::1', '0.0.0.0', '::')
            AND path <> ''
        GROUP BY meta_hostname, path, query_name
        
        UN#I#ON ALL
        
        SELECT DISTINCT x1.meta_hostname, x2.new_pid, x1.query_name, count(*)
        FROM xdr_data AS x1
        CROSS JOIN 
        UNNEST(SPLIT(x1.sophos_pids, ',')) AS x2(new_pid)
        WHERE x1.query_name IN ('sophos_ips_windows', 'sophos_urls_windows')
        GROUP BY meta_hostname, new_pid, query_name
    )
    
    SELECT DISTINCT sha256, meta_hostname AS hostname, query_name, path,
    CASE query_name
        WHEN 'access_productivity_documents' THEN 'N/A'
        ELSE IF((select count(*) from Connection_Info AS ci WHERE (ci.path = xd.path AND ci.meta_hostname = xd.meta_hostname) OR ci.path = xd.sophos_pid) > 0,'Yes','No')
    END AS used_network
    FROM xdr_data AS xd
    WHERE regexp_like(sha256, '($SHA256)')
    ORDER BY sha256
"@ -replace "(?m)^\s+"  -replace "`r","" -replace "`n","\n"

$body= '{ "adHocQuery": { "template": "' + $template + '" }, "from": "' + $fromtime + '" , "to": "' + $tilltime + '" }'


# Query Data Lake 
Write-Output "[XDR] Send Request to run Ad-Hoc Query for finding occurrances of specified SHA256 values..."
if ($null -ne $DataRegion) {
    $XDR_Response = (Invoke-RestMethod -Method POST -Uri $DataRegion"/xdr-query/v1/queries/runs" -Headers $XDRHead -Body $Body)
    Write-Output "[XDR] Request sent wait for results..."

    While ($XDR_Response.status -ne "finished") {
        start-sleep -s 5
        $XDR_Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)" -Headers $XDRHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    }

    if ($XDR_Response.result -eq "succeeded") {
        Write-Output("[XDR] Query finished, retreiving results")
        $XDR_Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)/results?pageSize=2000" -Headers $XDRHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

        Write-Output("[XDR] Query results:")
        $XDR_Response.items | Format-Table -Property sha256, hostname, path, query_name, used_network
        Write-Output "[XDR] Request completed, $($XDR_Response.Items.Count) entries were found!"
    } else {
        Write-Output ("[XDR] Request failed!")
        Write-Output ($XDR_Response)
    }
}

#19 XDR IP Lookup [snippet] - Basic sample showing how to search the Data Lake for IP addresses. Uses the XDR API.
NOTE: You must replace the two instances of UN#I#ON with UNION before you use this snippet.

# add this code snippet to the auth code samples for Central (snippets 1)
# you will find a line that says INSERT CODE HERE

Write-Output "[XDR] Search for devices that connected to specific IP addresses."
Write-Output "      You can search multiple addresses by separting them with a |."
Write-Output "      You can search partial addresses."
Write-Output ""

do {
    $IPAddress = [string](Read-Host -Prompt 'Enter you search text')
} while ($IPAddress.Length -lt 1)

# Calculate last 30 days for the query in UTC format
$currtime = Get-Date
$fromtime = $currtime.AddDays(-30).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")
$tilltime = $currtime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")

# SOPHOS XDR API Headers:
$XDRHead = @{}
$XDRHead.Add("Authorization", "Bearer $Token")
$XDRHead.Add("X-Tenant-ID", "$TenantID")
$XDRHead.Add("Content-Type", "application/json")


$template = @"
    WITH addresses AS (

        SELECT meta_hostname AS Hostname, query_name AS QueryName, address AS Address , 0 AS Port, '' AS Name, '' AS CmdLine
        FROM xdr_data
        WHERE query_name = 'arp_cache'

        UN#I#ON ALL
        
        SELECT meta_hostname, query_name, destination_ip, destination_port, '', ''
        FROM xdr_data
        WHERE query_name = 'sophos_ips_windows'
    
        UN#I#ON ALL    

        SELECT meta_hostname, query_name, remote_address, remote_port, name, cmdline
        FROM xdr_data
        WHERE query_name = 'open_sockets' AND name <> 'Idle'
    )

    SELECT distinct Hostname, Address
    FROM addresses
    WHERE regexp_like(address, '($IPAddress)')
    ORDER BY Hostname
"@ -replace "(?m)^\s+"  -replace "`r","" -replace "`n","\n"

$body= '{ "adHocQuery": { "template": "' + $template + '" }, "from": "' + $fromtime + '" , "to": "' + $tilltime + '" }'

# Query Data Lake 
Write-Output ""
Write-Output "[XDR] Send Request to find occurrances of specified IP searchmask..."
if ($null -ne $DataRegion) {
    $XDR_Response = (Invoke-RestMethod -Method POST -Uri $DataRegion"/xdr-query/v1/queries/runs" -Headers $XDRHead -Body $Body)
    Write-Output "[XDR] Request sent wait for results..."

    While ($XDR_Response.status -ne "finished") {
        start-sleep -s 5
        $XDR_Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)" -Headers $XDRHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    }

    if ($XDR_Response.result -eq "succeeded") {
        Write-Output("[XDR] Query finished, retreiving results")
        $XDR_Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)/results?pageSize=2000" -Headers $XDRHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output ""

        if($XDR_Response.count -eq 0) {
            Write-Output "[XDR] Request completed, no entries were found!"
        } else {
            Write-Output("[XDR] Query details for IP search mask:")
            $XDR_Response.items | Format-Table -Property hostname, address
            Write-Output "[XDR] Request completed, $($XDR_Response.Items.Count) entries were found!"

            Write-Output("[XDR] Query summary:")
            $Hostnames = $XDR_Response.items | Select-Object 'hostname' -unique | Sort-Object 'hostname' 
            $Hostnames.hostname -join ', ' 
        }
    } else {
        Write-Output ("[XDR] Request failed!")
        Write-Output ($XDR_Response)
    }
}

#20 Admin isolate device [snippet] - Basic sample showing how to isolate devices using the API. Uses the isolation function of the Endpoint API.

# add this code snippet to the auth code samples for Central (snippets 1)
# you will find a line that says INSERT CODE HERE

##### Device Isolation Start #####

$EndpointList = @()
$NextKey = $null

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Retrieve list of endpoints that match our search string
do {
    $GetEndpoints = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/endpoints?pageTotal=true&isolationStatus=notIsolated&fields=hostname,type,os,isolation&pageFromKey=$NextKey" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    $NextKey = $GetEndpoints.pages.nextKey

    $EndpointList += $GetEndpoints.items
} while ($null -ne $NextKey)      

# Display results
Write-Output "Total number of devices found: $($EndpointList.Count)"
Write-Output ""
Write-Output "Which devices do you want to isolate?"
do {
    $Hostname = [string](Read-Host -Prompt 'Enter the hostname search string (wildcard allowed)')
} while ($Hostname.Length -lt 1)

$SelectList = @()
$SelectList += $EndpointList | Where-Object -Property "Hostname" -Like "*$($Hostname)*"

if ($SelectList.Count -gt 50) {
    Write-Output "This script support isolating up to 50 devices at once."
    Write-Output "We however found $($SelectList.Count) devices that match."
    Write-Output "Script aborted"
} elseif ($SelectList.Count -gt 0) {
    Write-Output "Number of Endpoints to isolate: $($SelectList.Count)"
    write-output ""
    write-output "Hostname(s) of devices to be isolated:"
    Write-Output ($SelectList.hostname -join ', ')
    write-output ""

    # Get confirmation that these devices should be isolated
    do {
        $Isolate = Read-Host 'Isolate selected devices (Y/N)'
    } while (-not (($Isolate -eq "y") -or ($Isolate -eq "n")))

    if ($Isolate -eq "y") {
        # EndpointList must be an array even when it only contains one object
        if ($SelectList.Count -eq 1) {
            $Body = '{ "enabled": true, "comment": "Isolating endpoints using API","ids": [' + ($SelectList.id | ConvertTo-Json) + '] }' 
        } else {
            $Body = '{ "enabled": true, "comment": "Isolating endpoints using API","ids": ' + ($SelectList.id | ConvertTo-Json) + ' }' 
        }
    
        try {
            $Result = (Invoke-RestMethod -Method Post -Uri $DataRegion"/endpoint/v1/endpoints/isolation" -Body $Body -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
            Write-Output  "Device isolation triggered!"
        } catch {
            Write-Output  "Something went wrong, status code: $($_.Exception.Response.StatusCode.value__)"
            break
        }
    } else {
        Write-Output "Process aborted..."
    }
} else {
    Write-Output "No devices found that match the search string"
}

#21 Remove devices from isolation [snippet] - Basic sample showing how to isolate devices using the API. Uses the isolation function of the Endpoint API.

# add this code snippet to the auth code samples for Central (snippets 1)
# you will find a line that says INSERT CODE HERE

##### Device Isolation Stop #####

$EndpointList = @()
$NextKey = $null

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Retrieve list of endpoints that are isolated
do {
    $GetEndpoints = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/endpoints?pageTotal=true&isolationStatus=isolated&fields=hostname,type,os,isolation&pageFromKey=$NextKey" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    $NextKey = $GetEndpoints.pages.nextKey

    $EndpointList += $GetEndpoints.items
} while ($null -ne $NextKey)      

# Display results
Write-Output "Number of isolated devices found: $($EndpointList.Count)"

if ($EndpointList.Count -eq 0) {
    Write-Output "No isolated devices found, aborting script..."
    Exit
}

write-output "Hostname(s) of isolated devices:"
Write-Output ($EndpointList.hostname -join ', ')
write-output ""

# Define pattern to search for
Write-Output "Select devices do you want to remove from isolation?"
do {
    $Hostname = [string](Read-Host -Prompt 'Enter the search string (wildcard allowed)')
} while (($Hostname.Length -lt 1) -or ($Hostname.Length -gt 10))

$SelectList = @()
$SelectList += $EndpointList | Where-Object -Property "Hostname" -Like "*$($Hostname)*"
write-output ""

if ($SelectList.Count -gt 50) {
    Write-Output "This script supports removing up to 50 devices from isolation at once."
    Write-Output "We however found $($SelectList.Count) devices that match."
    Write-Output "Script aborted"
} elseif ($SelectList.Count -gt 0) {
    Write-Output "Number of devices selected: $($SelectList.Count)"
    write-output "Hostname(s) of devices to be removed from isolation:"
    Write-Output ($SelectList.hostname -join ', ')
    write-output ""

    # Get confirmation that these devices should be removed from isolation
    do {
        $Isolate = Read-Host 'Remove selected devices from isolation (y/n)'
    } while (-not (($Isolate -eq "y") -or ($Isolate -eq "n")))

    if ($Isolate -eq "y") {
        # EndpointList must be an array even when it only contains one object
        if ($EndpointList.Count -eq 1) {
            $Body = '{ "enabled": false, "comment": "Isolating endpoints using API","ids": [' + ($EndpointList.id | ConvertTo-Json) + '] }' 
        } else {
            $Body = '{ "enabled": false, "comment": "Isolating endpoints using API","ids": ' + ($EndpointList.id | ConvertTo-Json) + ' }' 
        }

        # Remove devices from isolation
        try {
            $Result = (Invoke-RestMethod -Method Post -Uri $DataRegion"/endpoint/v1/endpoints/isolation" -Body $Body -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
            Write-Output  "Device removal from isolation triggered!"
        } catch {
            Write-Output  "Something went wrong, status code: $($_.Exception.Response.StatusCode.value__)"
            break
        }
    } else {
        Write-Output "Process aborted..."
    }
} else {
    Write-Output "No devices found that match the search string"
}

#22 Switch & AP6 Threat Response [standalone] - Block devices based on their MAC-address on Sophos Switch and AP6 access points. Uses the mac-filtering function of the Switch and WiFi APIs.

param ([string[]] $macAddresses, [switch] $add, [switch] $del, [switch] $wifi, [switch] $switch, [switch] $saveCredentials)
<#
    Description: Sophos ATR MAC Filter
    Parameters: -SaveCredentials -> will store then entered credentials locally on the PC.
                -macAddresses -> comma separated list of MAC addresses to be added / deletec
                -add or -del -> initiate add or delete action
                -switch and/or -wifi -> targets for the action
#>

function IsMACAddressValid {
    param ([string]$macAddress)
    $RegEx = "^([0-9A-Fa-f]{2}[-:]){5}([0-9A-Fa-f]{2})$"
    if ($macAddress -match $RegEx) {
        return $true
    } else {
        return $false
    }
}

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Wi-Fi / Switch ATR MAC Filter --- START"
Write-Output "==============================================================================="

# Define the filename and path for the credential file
$credentialFile = $PSScriptRoot + '\Sophos_Central_Admin_Credentials.json'

# Check if Central API Credentials have been stored, if not then prompt the user to enter the credentials
if (((Test-Path $credentialFile) -eq $false) -or $saveCredentials){
	# Prompt for Credentials
	$clientId = Read-Host "Please Enter your Client ID"
	$clientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString 
} else { 
    # Read Credentials from JSON File
    $credentials = Get-Content $credentialFile | ConvertFrom-Json
    $clientId = $credentials[0]
    $clientSecret = $credentials[1] | ConvertTo-SecureString
}

# We are making use of the PSCredentials object to store the API credentials
# The Client Secret will be encrypted for the user excuting the script
# When scheduling execution of the script remember to use the same user context

$secureCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $clientId , $clientSecret

# SOPHOS OAuth URL
$tokenURI = "https://id.sophos.com/api/v2/oauth2/token"

# TokenRequestBody for oAuth2
$tokenRequestBody = @{
	"grant_type" = "client_credentials";
	"client_id" = $secureCredentials.GetNetworkCredential().Username;
	"client_secret" = $secureCredentials.GetNetworkCredential().Password;
	"scope" = "token";
}
$tokenRequestHeaders = @{
	"content-type" = "application/x-www-form-urlencoded";
}

# Set TLS Version
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Post Request to SOPHOS for OAuth2 token
try {
    $APIAuthResult = (Invoke-RestMethod -Method Post -Uri $tokenURI -Body $tokenRequestBody -Headers $tokenRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    if ($saveCredentials) {
	    $clientSecret = $clientSecret | ConvertFrom-SecureString
	    ConvertTo-Json $clientID, $clientSecret | Out-File $credentialFile -Force
    }
} catch {
    # If there's an error requesting the token, say so, display the error, and break:
    Write-Output "" 
	Write-Output "AUTHENTICATION FAILED - Unable to retreive SOPHOS API Authentication Token"
    Write-Output "Please verify the credentials used!" 
    Write-Output "" 
    Write-Output "If you are working with saved credentials then you can reset them by calling"
    Write-Output "this script with the -SaveCredentials parameter"
    Write-Output "" 
    Read-Host -Prompt "Press ENTER to continue..."
    Break
}

# Set the Token for use later on:
$token = $APIAuthResult.access_token

# SOPHOS Whoami URI:
$whoamiURI = "https://api.central.sophos.com/whoami/v1"

# SOPHOS Whoami Headers:
$whoamiRequestHeaders = @{
	"Content-Type" = "application/json";
	"Authorization" = "Bearer $token";
}

# Post Request to SOPHOS for Whoami Details:
$APIWhoamiResult = (Invoke-RestMethod -Method Get -Uri $whoamiURI -Headers $whoamiRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Save Response details
$APIidTenant = $APIWhoamiResult.id
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion
if ($APIWhoamiResult.idType -ne "tenant") {
    Write-Output "This script can only be used with tenant credentials. Aborting..."
    exit
}	

# SOPHOS Endpoint API Headers:
$tentantAPIHeaders = @{
	"Authorization" = "Bearer $token";
	"X-Tenant-ID" = "$APIidTenant";
}

# Ensure that the MAC Address is valid for add/del actions and normalize it
if ($add -or $del -or $switch -or $wifi) {
    if (-not $PSBoundParameters.ContainsKey('macAddresses')) {
        Write-Output "MAC Address(es) not specified, aborting..." 
        exit
    }

    if (-not ($del -or $add)) {
        Write-Output "The ATR targets -wifi and/or -switch need to be combined with an action."
        Write-Output "You can use -add or -del as action. Aborting..."
        exit
    }
    
    if (-not ($switch -or $wifi)) {
        Write-Output "The actions -add or -del need to be combined with a target ATR list. "
        Write-Output "You can use -wifi and/or -switch as targets. Aborting..."
        exit
    }
    
    if ($del -and $add) {
        Write-Output "The actions -add and -del cannot be combined. Aborting..."
        exit
    }
}

# Create object to hold MAC-Address List
$macList = New-Object System.Data.Datatable
[void]$macList.Columns.Add("macAddress")
[void]$macList.Columns.Add("WiFi")
[void]$macList.Columns.Add("Switch")
$macView = New-Object System.Data.DataView($macList)
$macView.Sort = "macAddress ASC"

# Get current ATR MAC-Filter Lists
$getATRSwitch = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/switch/v1/settings/mac-filtering" -Headers $tentantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
foreach ($address in $getATRSwitch.macAddresses) {
    $address = ($address.ToUpper()) -replace '-',':'
    $macView.RowFilter = "macAddress = '$($address)'"
    if ($macView.Count -ne 0) {
        $macView  | ForEach-Object { $_.Switch = $true }
    } else {
        [void]$macList.Rows.Add($address, $false, $true)
    }
}

$getATRWiFi = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/wifi/v1/settings/mac-filtering" -Headers $tentantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
foreach ($address in $GetATRWiFi.macAddresses) {
    $address = ($address.ToUpper()) -replace '-',':'
    $macView.RowFilter = "macAddress = '$($address)'"
    if ($macView.Count -ne 0) {
        $macView  | ForEach-Object { $_.WiFi = $true }
    } else {
        [void]$macList.Rows.Add($address, $true, $false)
    }
}

Write-Output "" 
Write-Output "Current ATR MAC Filter List:" 
$macView.RowFilter = "WiFi = $true or Switch = $true"
$macView | Format-Table

# Modify table based on parameters specified...
foreach ($macAddress in $macAddresses) {
    if (IsMACAddressValid $macAddress) {
        $macAddress = ($macAddress.ToUpper()) -replace '-',':'
    } else {
        Write-Output "MAC Address invalid $($macAddress), aborting..."
        exit
    }

    if ($add) {
        if ($switch) {
            $macView.RowFilter = "macAddress = '$($macAddress)'"
            if ($macView.Count -ne 0) {
                $macView  | ForEach-Object { $_.Switch = $true }
            } else {
                [void]$macList.Rows.Add($macAddress, $false, $true)
            }
        }
        if ($wifi) {
            $macView.RowFilter = "macAddress = '$($macAddress)'"
            if ($macView.Count -ne 0) {
                $macView  | ForEach-Object { $_.WiFi = $true }
            } else {
                [void]$macList.Rows.Add($macAddress, $true, $false)
            }
        }
    }

    if ($del) {
        if ($switch) {
            $macView.RowFilter = "macAddress = '$($macAddress)'"
            if ($macView.Count -ne 0) {
                $macView  | ForEach-Object { $_.Switch = $false }
            }
        }
        if ($wifi) {
            $macView.RowFilter = "macAddress = '$($macAddress)'"
            if ($macView.Count -ne 0) {
                $macView  | ForEach-Object { $_.WiFi = $false }
            }
        }
    }
}

# time to update the ATR lists in Sophos Central
if ($add -or $del) {

    if ($switch) {
        $macView.RowFilter = "Switch = $true"

        if ($macView.Count -eq 0) {
            $Body = '{ "macAddresses": [ ] }'
        } elseif ($macView.Count -eq 1) {
            $Body = '{ "macAddresses": [ ' + ($macView.macAddress | ConvertTo-Json) + '] }'
        } else { 
            $Body = '{ "macAddresses": ' + ($macView.macAddress | ConvertTo-Json) + ' }'
        }

        try {
            $response = Invoke-WebRequest -Uri $APIdataRegion"/switch/v1/settings/mac-filtering" -Method PUT -Headers $tentantAPIHeaders -ContentType 'application/json' -Body $Body
        } catch {
            Write-Output "Oops, something went wrong. Aborting..."

            $resultStream = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
            $resultBody = $resultStream.readToEnd()
            $resultBody
            exit
        }
    }

    if ($wifi) {
        $macView.RowFilter = "WiFi = $true"

        if ($macView.Count -eq 0) {
            $Body = '{ "macAddresses": [ ] }'
        } elseif ($macView.Count -eq 1) {
            $Body = '{ "macAddresses": [ ' + ($macView.macAddress | ConvertTo-Json) + '] }'
        } else { 
            $Body = '{ "macAddresses": ' + ($macView.macAddress | ConvertTo-Json) + ' }'
        }

        try {
            $response = Invoke-WebRequest -Uri $APIdataRegion"/wifi/v1/settings/mac-filtering" -Method PUT -Headers $tentantAPIHeaders -ContentType 'application/json' -Body $Body
        } catch {
            Write-Output "Oops, something went wrong. Aborting..."
            $resultStream = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
            $resultBody = $resultStream.readToEnd()
            $resultBody
            exit
        }
    }
 
    Write-Output "New ATR MAC Filter List:" 
    $macView.RowFilter = "WiFi = $true or Switch = $true"
    $macView  | Format-Table
}

Write-Output "==============================================================================="
Write-Output "Sophos API - Wi-Fi / Switch ATR MAC Filter --- END"
Write-Output "==============================================================================="


Extra scripts that were not covered during the API Academy but are useful as well:

X01 Device Migration Start [standalone] - Basic sample of migrating installed Endpoints from one Central Admin account to another. Uses the migrations function of the Endpoint API.

<#
    Description: Migrate all devices from one tenant to another tenant
#>

###################################################################################################################
# Define OAUTH Access Data before running the script!
###################################################################################################################

# Define OAUTH Access Data for the source (sending) account 
$SrcID = ""
$SrcSecret = ""

# Define OAUTH Access Data for the destination (receiving) account 
$DstID = ""
$DstSecret = ""

###################################################################################################################

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Device Migration"
Write-Output "==============================================================================="
$SrcToken = ""
$DstToken = ""


function API_Authenticate {
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $ID,
         [Parameter(Mandatory=$true, Position=1)]
         [string] $Secret
    )

    # Define SOPHOS OAuth URL and Request Header
    $TokenURI = "https://id.sophos.com/api/v2/oauth2/token"
    $TokenRequestHeaders = @{
	    "content-type" = "application/x-www-form-urlencoded";
    }

    # Set TLS Version
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    # Define TokenRequestBody for OAuth and the source Account
    $TokenRequestBody = @{
    	"grant_type" = "client_credentials";
    	"client_id" = $ID;
    	"client_secret" = $Secret;
    	"scope" = "token";
    }

    # Post Request to SOPHOS for OAuth2 token
    try {
        $APIAuthResult = (Invoke-RestMethod -Method Post -Uri $TokenURI -Body $TokenRequestBody -Headers $TokenRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    } catch {
        # If there's an error requesting the token, say so, display the error, and break:
        Write-Output "" 
    	Write-Output "AUTHENTICATION FAILED"
        Write-Output "Please verify the credentials used for the source account!" 
        Read-Host -Prompt "Press ENTER to continue..."
        Break
    }

    # Set the Token for use later on:
    return $APIAuthResult.access_token

}

function API_Whoami {
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $Token
    )

    # SOPHOS Whoami URI:
    $WhoamiURI = "https://api.central.sophos.com/whoami/v1"

    # SOPHOS Whoami Headers:
    $WhoamiRequestHeaders = @{
    	"Content-Type" = "application/json";
    	"Authorization" = "Bearer $Token";
    }

    # Post Request to SOPHOS for Whoami Details:
    $APIWhoamiResult = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

    # Set the Token for use later on:
    return $APIWhoamiResult
}

if ( ($SrcID -eq "") -or ($SrcSecret -eq "") -or ($DstID -eq "") -or ($DstSecret -eq "")) {
    Write-Output "You must enter the OAUTH2 Access Data in the script to be able to use the script!"
}

# Authenticate against the source (sending) account and get whoami details
Write-Output "[SRC] Authenticate using credentials of the sending account"
$SrcToken = API_Authenticate -id $SrcID -Secret $SrcSecret
$SrcWhoami = API_Whoami -Token $SrcToken

# Define refresh time when we want to renew our token during the monitor phase
$SrcRefresh = Get-Date 
$SrcRefresh = $SrcRefresh.AddMinutes(50)

# Authenticate against the destination (receiving) account and get whoami details
Write-Output "[DST] Authenticate using credentials of the receiving account"
$DstToken =  API_Authenticate -id $DstID -Secret $DstSecret
$DstWhoami = API_Whoami -Token $DstToken

# Retrieve List of Endpoints from the source (sending) account 
Write-Output "[SRC] Create a list of all devices to be migrated"
$EndpointList = @()
$NextKey = $null

# SOPHOS Endpoint API Headers
$APIHeader = @{ "Authorization" = "Bearer $SrcToken"; "X-Tenant-ID" = "$($SrcWhoami.id)";}

do {
    $Uri = $SrcWhoami.apiHosts.dataRegion + "/endpoint/v1/endpoints?pageTotal=true&pageFromKey=$NextKey&fields=hostname,type&sort=id"
    $GetEndpoints = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $APIHeader -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    $EndpointList += $GetEndpoints.items
    $NextKey = $GetEndpoints.pages.nextKey
} while ($null -ne $NextKey) 

# Show some statistics
write-output ("      +-> Total amount of computers to be migrated: " + $EndpointList.where{$_.type -match 'computer'}.count)
write-output ("      +-> Total amount of servers to be migrated..: " + $EndpointList.where{$_.type -match 'server'}.count)

# Abort migration when no devices
if ($EndpointList.Length -eq 0) {
    write-output ""
    write-output "Aborting migration, no devices found!"
    break
}

# Abort migration when too many devices, you can address this by splitting the list in separate jobs with max. 1000 devices per job
if ($EndpointList.Length -gt 1000) {
    write-output ""
    write-output "Aborting migration, too many devices found, this script supports up to max. 1000 devices!"
    break
}

# Start receiver for the migration task on destination (receiving) account
Write-Output "[DST] Start a migration job in the receiving tenant"
$Header = @{ "Authorization" = "Bearer $DstToken"; "X-Tenant-ID" = "$($DstWhoami.id)"; "Content-Type" = "application/json";}
$Body = '{ "fromTenant": "' + $($SrcWhoami.id) + '", "endpoints": ' + ($EndpointList.id | ConvertTo-Json) + ' }' 
$Uri = $DstWhoami.apiHosts.dataRegion + "/endpoint/v1/migrations"

try {
    $Result = (Invoke-RestMethod -Method Post -Uri $Uri -Body $Body -Headers $Header -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    Write-Host "      +-> Migration receiver created!"
} catch {
    Write-Host "      +-> Something went wrong, status code: $($_.Exception.Response.StatusCode.value__)"
    break
}

# Start migration task on source (sending) account
Write-Output "[SRC] Start a migration job in the sending tenant"
$Header = @{ "Authorization" = "Bearer $SrcToken"; "X-Tenant-ID" = "$($SrcWhoami.id)"; "Content-Type" = "application/json";}
$Body = '{ "id": "' + $($Result.id) + '", "token": "' + $($Result.token) + '", "endpoints": ' + ($EndpointList.id | ConvertTo-Json) + ' }' 
$Uri = $DstWhoami.apiHosts.dataRegion + "/endpoint/v1/migrations/$($Result.id)"

try {
    $Result = (Invoke-RestMethod -Method Put -Uri $Uri -Body $Body -Headers $Header -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    Write-Host "      +-> Migration started in sending tenant!"
} catch {
    Write-Host "      +-> Something went wrong, status code: $($_.Exception.Response.StatusCode.value__)"
    Write-Host "          403? --> Make sure that Migrations are active in the sending account!"
    break
}

# Retrieve List of Migration Tasks
do { $yn = Read-Host "[SRC] Start Device Migration Monitoring Mode? [Y/N]"; if ($yn -eq 'n') {exit}; } while($yn -ne "y")

do {
    Clear-Host
    Write-Output "==============================================================================="
    Write-Output "Sophos API - Device Migration Progress Monitor"
    Write-Output "==============================================================================="
    Write-Output "[SRC] Task created at $($Result.createdAt)"
    Write-Output "[SRC] Task expires at $($Result.expiresAt) "

    # Check if we neet to refresh out access token and do so if needed
    $CurrTime = Get-Date
    if ($SrcRefresh -lt $Currtime) {
        $SrcToken = API_Authenticate -id $SrcID -Secret $SrcSecret
        $SrcRefresh = $Currtime.AddMinutes(50)
    }
    
    $Header = @{ "Authorization" = "Bearer $SrcToken"; "X-Tenant-ID" = "$($SrcWhoami.id)";}
    $Uri = $SrcWhoami.apiHosts.dataRegion + "/endpoint/v1/migrations/" + $Result.id + "/endpoints?pageSize=1000"
    $Devices = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $Header -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
     
    # Calculate statistics
    $CountPending = $Devices.items.where{$_.status -match 'pending'}.count
    $CountSucceeded = $Devices.items.where{$_.status -match 'succeeded'}.count
    $CountFailed = $Devices.items.where{$_.status -match 'failed'}.count

    if ( $CountPending -gt 0 ) {
        Write-Output " "
        Write-Output "[SRC] Pending devices:"

        foreach ($Device in $Devices.items) {
            if($Device.status -eq "pending") {
                $Hostname = $EndpointList.where{$_.id -match $Device.id}.hostname      
                Write-Output "      +-> $($Device.id) with Hostname $Hostname"
            }  
        }
    }
    
    Write-Output ""
    Write-Output "[SRC] Summary: migrated $CountSucceeded - failed $CountFailed - pending $CountPending devices"
    Write-Output ""

    if ( $CountPending -gt 0 ) {
        Write-Output "[SRC] Reload in one minute, make sure that pending devices are turned on"
        Start-Sleep -Seconds 60
    }

} while ( $CountPending -gt 0 )

Write-Output "[SRC] No more pending migrations, process complete!"

X02 Device Migration Check [standalone] - Part two of the device migration. You can use this script to check the progress of a previously started migration. Uses the migrations function of the Endpoint API.

<#
    Description: Monitor the progress of Device Migration Tasks
#>

###################################################################################################################
# Define OAUTH Access Data for the account you want to monitor
###################################################################################################################

$SrcID = ""
$SrcSecret = ""

###################################################################################################################

Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - Device Migration Monitor"
Write-Output "==============================================================================="

function API_Authenticate {
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $ID,
         [Parameter(Mandatory=$true, Position=1)]
         [string] $Secret
    )


    # Define SOPHOS OAuth URL and Request Header
    $TokenURI = "https://id.sophos.com/api/v2/oauth2/token"
    $TokenRequestHeaders = @{
	    "content-type" = "application/x-www-form-urlencoded";
    }

    # Set TLS Version
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    # Define TokenRequestBody for OAuth and the source Account
    $TokenRequestBody = @{
    	"grant_type" = "client_credentials";
    	"client_id" = $ID;
    	"client_secret" = $Secret;
    	"scope" = "token";
    }

    # Post Request to SOPHOS for OAuth2 token
    try {
        $APIAuthResult = (Invoke-RestMethod -Method Post -Uri $TokenURI -Body $TokenRequestBody -Headers $TokenRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    } catch {
        # If there's an error requesting the token, say so, display the error, and break:
        Write-Output "" 
    	Write-Output "AUTHENTICATION FAILED"
        Write-Output "Please verify the credentials used for the source account!" 
        Read-Host -Prompt "Press ENTER to continue..."
        Break
    }

    # Set the Token for use later on:
    return $APIAuthResult.access_token

}

function API_Whoami {
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $Token
    )

    # SOPHOS Whoami URI:
    $WhoamiURI = "https://api.central.sophos.com/whoami/v1"

    # SOPHOS Whoami Headers:
    $WhoamiRequestHeaders = @{
    	"Content-Type" = "application/json";
    	"Authorization" = "Bearer $Token";
    }

    # Post Request to SOPHOS for Whoami Details:
    $APIWhoamiResult = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

    # Set the Token for use later on:
    return $APIWhoamiResult
}

if ( ($SrcID -eq "") -or ($SrcSecret -eq "") ) {
    Write-Output "You must enter the OAUTH Access Data in the script to be able to use the script!"
}

# Authenticate against the source (sending) account and get whoami details
Write-Output "[SRC] Authenticate using credentials of the sending account"
$SrcToken = API_Authenticate -id $SrcID -Secret $SrcSecret
$SrcWhoami = API_Whoami -Token $SrcToken

# Retrieve List of Migration Tasks
Write-Output "[SRC] Create a list of all sending Migration Tasks in this account"
    
$APIHeader = @{ "Authorization" = "Bearer $SrcToken"; "X-Tenant-ID" = "$($SrcWhoami.id)";}
$Uri = $SrcWhoami.apiHosts.dataRegion + "/endpoint/v1/migrations?mode=sending"
$Migrations = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $APIHeader -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

foreach ($Migration in $Migrations.items) {
    Write-Output ""
    Write-Output "[SRC] Migration Task found"
    Write-Output "      +-> Task created at $($Migration.createdAt)"
    Write-Output "      +-> Task expires at $($Migration.expiresAt)"

    $Uri = $SrcWhoami.apiHosts.dataRegion + "/endpoint/v1/migrations/" + $Migration.id + "/endpoints?pageSize=1000"
    $Devices = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $APIHeader -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        
    $CountPending = $Devices.items.where{$_.status -match 'pending'}.count
    $CountSucceeded = $Devices.items.where{$_.status -match 'succeeded'}.count
    $CountFailed = $Devices.items.where{$_.status -match 'failed'}.count
        
    foreach ($Device in $Devices.items) {
        if($Device.status -eq "pending") {
            Write-Output "      +-> Device with ID $($Device.id) is still pending"
        }  
        if($Device.status -eq "failed") {
            Write-Output "      +-> Device with ID $($Device.id) failed to migrate"
        }  
    }
    Write-Output "[SRC] Summary: migrated $CountSucceeded - failed $CountFailed - pending $CountPending devices"
}

Write-Output ""
Write-Output "[SRC] Scan complete!"

X03 Clear Sophos Firewall connection alerts [standalone] - Basic sample of acknowledging Central alerts - in this case Firewall alerts – using the API. Uses the alerts function of the Common API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

if ($null -ne $DataRegion){
	# Post Request to SOPHOS for Endpoint API:
	$AllAlertResult = (Invoke-RestMethod -Method Get -Uri $DataRegion"/common/v1/alerts?sort=raisedAt&product=firewall&category=connectivity&pageSize=1000" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

# Get the last date/time where the Firewall restored its connection:
$alertLastResolved = @{}
foreach ($alert in $AllAlertResult.items) {
	if (($alert.type -eq "Event::Firewall::FirewallGatewayUp") -or ($alert.type -eq "Event::Firewall::Reconnected")) {

        if (-not $alertLastResolved.Contains($alert.managedAgent.id)) {
            $alertLastResolved.Add(($alert.managedAgent.id),($alert.raisedAt))
        } else {
            $alertLastResolved[$alert.managedAgent.id] = $alert.raisedAt
        }
	}
}

# Clear all connection alerts for the firewall that are older then the last connection restored entry:
foreach ($alert in $AllAlertResult.items) {
	if ($alert.raisedAt -le $alertLastResolved[$alert.managedAgent.id]) {

        $Body = "{""action"":""acknowledge""}"
		$uri = $DataRegion+"/common/v1/alerts/"+$alert.id+"/actions"

		# Post Request to SOPHOS Details:
		$Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $TenantHead -Body $Body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
	}
}

X04 Import Users from CSV file [snippet] - Basic sample of importing users into Central Admin with fixed group assignments (group imported users). Uses the directory function of the Common API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Creating group for imported users
Write-Output "Creating new group for imported users"
Write-Output ""

# Define Object 
$Body = @{}
$Body.Add("name", "Imported Users")
$Body.Add("description", "Group for imported users")

# Convert Object to JSON Format
$Body = $Body | ConvertTo-Json

 # Set correct URI
$uri = $DataRegion+"/common/v1/directory/user-groups"

# Invoke Request
$Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $TenantHead -Body $Body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Store group result for next requests
$GroupID = @($Result.id)

Write-Output "Importing users from CSV..."
Write-Output ""

# Import data from CSV
$ImportFile = Import-Csv $PSScriptRoot\importUser.csv

Write-Output "Creating users in Sophos Central..."
Write-Output ""

# Iterate through all users
foreach ($Item in $ImportFile){
	
	# Add the groupID add the users to the imported users group
	$Item | Add-Member -NotePropertyName groupIds -NotePropertyValue $GroupID

	# Create request body by converting to JSON
	$Body = $Item | ConvertTo-Json

	 # Set correct URI
	$uri = $DataRegion+"/common/v1/directory/users"

	# Invoke Request
	$Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $TenantHead -Body $Body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

	Write-Output "Created User: $($Result.name) with ID $($Result.id)"

}
Write-Output ""
Write-Output "Successfully created users in Sophos Central..."

Sample file for use with this snippet:
name,firstName,lastName,email,exchangeLogin
User1,User,One,user1@one.com,user1
User2,User,Two,user2@two.com,user2

X05 Assign Admin Role [snippet] - Basic sample of assigning roles to admin accounts. Uses the admins, roles and directory function of the Common API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Common API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Initialize Variables
$AdminId = $null
$AdminRole = $null
$UserId = $null

# Specify Emailaddress of the user which should become Super Admin
$AdminEmail='<your admin email address>'

Write-Output "Getting details of already existing Administrators"
# Search all Admin Accounts in Tennant
$GetAdmins = (Invoke-RestMethod -Method Get -Uri $DataRegion"/common/v1/admins?pageTotal=true" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
foreach($Admin in $GetAdmins.items){
    if ($Admin.profile.email -eq $AdminEmail) {
        $AdminId=$Admin.id
        $AdminRole=$Admin.roleAssignments.RoleId
    }
}

Write-Output "Getting details of users defined in Sophos Central"
# Search all User Accounts in Tennant
$GetUsers = (Invoke-RestMethod -Method Get -Uri $DataRegion"/common/v1/directory/users?pageTotal=true" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
foreach($User in $GetUsers.items){
    if ($User.email -eq $AdminEmail) {
        $UserId=$User.id  
    }
}

Write-Output "Getting the ID of the Super Admin Role"
# Search ID of SuperAdmin Role
$GetRoles = (Invoke-RestMethod -Method Get -Uri $DataRegion"/common/v1/roles?pageTotal=true" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
$RoleId=$GetRoles.items.Where{$_.Name -eq 'SuperAdmin'}.id


if ($null -eq $UserId){
    Write-Output 'ATTENTION: Email address not found. Please create an user first!' 
    exit
}

if ($AdminRole -eq $RoleId) {
    Write-Output 'ATTENTION: User is SuperAdmin already.'
    exit
}

# Body and URI are different for new Admins vs. Admins that are assigned a new role
if ($null -eq $AdminId){
    $Body = '{"userId": "' + $UserId + '", "roleAssignments": [{ "roleId": "' + $RoleId + '"}]}'
    $Uri = $DataRegion + "/common/v1/admins"
} else {
    $Body = '{ "roleId": "' + $RoleId + '"}'
    $Uri = $DataRegion + "/common/v1/admins/" + $AdminId + "/role-assignments"
}

try {
    $Result = (Invoke-RestMethod -Uri $Uri -Method Post -Headers $TenantHead -Body $Body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
} catch {
    Write-Output 'ERROR: Could not make the user a Super Admin.'
}

Write-Output  'DONE! The user was promoted to Super Admin.'

X06 Exclusion Report [snippet] - Basic sample to run a report for global exclusions and exclusions set in policies. Uses the policies and exclusions function of the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS  API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

Write-Output "==============================================================================="
Write-Output "Global Exclusions"
Write-Output "==============================================================================="

if ($null -ne $DataRegion){
	# Post Request to Central API:
	$Result = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/settings/exclusions/scanning" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

$Result.items | Format-Table -Property @{label='Type';e={$_.type}},@{label='Scan Mode';e={$_.scanMode}}, @{label='Description';e={$_.description}}, @{label='Value';e={$_.value}}

Write-Output "==============================================================================="
Write-Output "Endpoint Policy Exclusions"
Write-Output "==============================================================================="

if ($null -ne $DataRegion){
	# Post Request to Central API:
	$Result = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/policies?policyType=threat-protection&pageSize=100" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

foreach ($Item in $Result.items) {
    if($Item.settings.'endpoint.threat-protection.exclusions.scanning'.value.Count -gt 0){
        Write-Output "`n---------------------------------"
        Write-Output $Item.name
        Write-Output "---------------------------------"
        $Item.settings.'endpoint.threat-protection.exclusions.scanning'.value | Format-Table -Property @{label='Type';e={$_.type}},@{label='Scan Mode';e={$_.scanMode}}, @{label='Value';e={$_.value}}
    }  
}

Write-Output "==============================================================================="
Write-Output "Server Policy Exclusions"
Write-Output "==============================================================================="

if ($null -ne $DataRegion){
	# Post Request to Central API:
	$Result = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/policies?policyType=server-threat-protection&pageSize=100" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

foreach ($Item in $Result.items) {
    if($Item.settings.'endpoint.threat-protection.exclusions.scanning'.value.Count -gt 0){
        Write-Output "`n---------------------------------"
        Write-Output $Item.name
        Write-Output "---------------------------------"
        $Item.settings.'endpoint.threat-protection.exclusions.scanning'.value | Format-Table -Property @{label='Type';e={$_.type}},@{label='Scan Mode';e={$_.scanMode}}, @{label='Value';e={$_.value}}
    }
}
 

X07 Import exclusions [snippet] - Basic sample to create global exclusions from a CSV-file. Uses the exclusions function of the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Define URI for setting Global exclusions
$Uri = $DataRegion+"/endpoint/v1/settings/exclusions/scanning"

# Import data from CSV
Write-Output "Importing global exclusions from CSV..."
Write-Output ""
$ImportFile = Import-Csv $PSScriptRoot\exclusions.csv

# Iterate through all exclusions from CSV
Write-Output "Creating global exclusions in Sophos Central..."
Write-Output ""

foreach ($Item in $ImportFile){
    # Create request body by converting to JSON
    $Body = $Item | ConvertTo-Json

    # Invoke Request
    $Result = (Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Headers $TenantHead -Body $body -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    Write-Output "Created global exclusion: $($Result.value) with ID $($Result.id)"
}

Write-Output ""
Write-Output "Successfully created global exclusions in Sophos Central..."

Sample file for use with the above snippet:
value,type,scanMode,comment
*.vhd,path,onDemandAndOnAccess,Virtual Hard Disk file
*.vhdx,path,onDemandAndOnAccess,Virtual Hard Disk v2 file
*.avhd,path,onDemandAndOnAccess,Virtual Hard Disk snapshot file
*.avhdx,path,onDemandAndOnAccess,Virtual Hard Disk v2 snapshot file
*.vhds,path,onDemandAndOnAccess,VHD Set file
%ProgramData%\Microsoft\Windows\Hyper-V\,path,onDemandAndOnAccess,The default virtual machine configuration directory
%userprofile%\Public\Documents\Hyper-V\Virtual Hard Disks\,path,onDemandAndOnAccess,The default virtual machine virtual hard disk files directory
%programdata%\Microsoft\Windows\Hyper-V\Snapshots\,path,onDemandAndOnAccess,The default snapshot files directory
C:\ClusterStorage\,path,onDemandAndOnAccess,The default Cluster Shared Volumes path
%windir%\systen32\Vmms.exe,process,onAccess,Hyper-V VMMS Process
%windir%\systen32\Vmwp.exe,process,onAccess,Hyper-V VMWP Process
%windir%\systen32\Vmsp.exe,process,onAccess,Hyper-V VMSP Process 
%windir%\systen32\Vmcompute.exe,process,onAccess,Hyper-V VMComputer Process
www.sophos.com,web,onAccess,Example Website

X08 Download Installers [snippet] - Basic sample of retrieving the URL to the installer from a given Central Admin account. Uses the downloads function of the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# List all Installers sorted by PLatform and Platform Type
$Getinstallers = (Invoke-RestMethod -Method Get -Uri $DataRegion"/endpoint/v1/downloads" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
Write-Output $GetInstallers.Installers | Format-Table @{label='Product Name';e={$_.productName}},
                                                      @{label='Platform';e={$_.platform}},
                                                      @{label='Type';e={$_.type}},
                                                      @{label='Link';e={$_.downloadUrl}}

X09 Update Web Control base policy [snippet] - Basic sample showing how to change the Web Control base policy to include several tags that are used in script #08. Uses the policies function of the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Define URI for updating the local site list
$Uri = $DataRegion+"/endpoint/v1/policies?pageTotal=true&policyType=web-control"


# Get all Web Control Policies
$Policies = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Define what happens when the "Warnen", "Erlauben" und "Blockieren" custom tags are used
$Body = '{ "settings": { "endpoint.web-control.tags.settings": { "value": [  
                { "tag": "Warn", "action": "warn" }, 
                { "tag": "Allow", "action": "allow" }, 
                { "tag": "Block", "action": "block" }
            ]}}}'

# Find the base policy which has oriority 0
Write-Output "Scanning policies for the Web Control Base Policy..."
foreach ($Policy in $Policies.items) {
    if ($Policy.priority -eq 0) {

        $PolicyID = $Policy.id
        $Uri = $DataRegion + "/endpoint/v1/policies/" + $PolicyID

        Write-Output "Adding tags to the base policy for Web Control..."
        $Result = (Invoke-RestMethod -Method Patch -Uri $Uri -Body $Body -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "Base Policy Updated"
    }
}

X10 Update Threat Protection Policy [snippet] – Basic sample showing how to enable SSL/TLS decryption and QUIC blocking in all Endpoint Threat Protection policies. Uses the policies function of the Endpoint API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS Endpoint API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization" ,"Bearer $Token")
$TenantHead.Add("X-Tenant-ID" ,"$TenantID")
$TenantHead.Add("Content-Type", "application/json")

# Define URI for updating the local site list
$Uri = $DataRegion+"/endpoint/v1/policies?pageTotal=true&pageSize=200&policyType=threat-protection"

# Get all Threat Endpoint Protection Policies
$Policies = (Invoke-RestMethod -Method Get -Uri $Uri -Headers $tenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Define action to enable Deep Learning
$Body = '{ "settings": { "endpoint.threat-protection.web-control.tls-decryption.enabled": { "value": true },"endpoint.threat-protection.web-control.tls-decryption.quic.enabled": {	"value": true }}}'

Write-Output "Scanning for Threat Protection policies with SSL/TLS Inspection & QUIC Blocking deactivated..."
foreach ($Policy in $Policies.items) {

    $ModifyPolicy = $false

    if ($false -eq $Policy.settings.'endpoint.threat-protection.web-control.tls-decryption.enabled'.value) {
        Write-Output "+ SSL/TLS Inspection is not active in the $($PolicyType) '$($Policy.name)' policy --> activating..."
        $ModifyPolicy = $True
    }
    
    if ($false -eq $Policy.settings.'endpoint.threat-protection.web-control.tls-decryption.quic.enabled'.value) {
        Write-Output "+ QUIC Blocking is not active in the $($PolicyType) '$($Policy.name)' policy --> activating..."
        $ModifyPolicy = $True
    }

    if ($ModifyPolicy) {
        $PolicyID = $Policy.id
        $Uri = $DataRegion + "/endpoint/v1/policies/" + $PolicyID
        $Result = (Invoke-RestMethod -Method Patch -Uri $Uri -Body $Body -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "+-> Policy updated!"
    } else {
        Write-Output "+ Policy '$($Policy.name)' matches required settings --> no action needed..."
    }
}

Write-Output "" "Policy Scan completed"

X11 XDR ad hoc query template [snippet] - Basic sample of a framework to use any SQL statement against the XDR Data Lake. Pre canned Data Lake SQL Statements can be found in the Threat Analysis Center. Uses the XDR-Query API.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")
$TenantHead.Add("Content-Type", "application/json")

if ($null -ne $DataRegion){
        
    ###################################################################################################################################
    # Runs an adHoc query against the Sophos Data Lake via the XDR API
    ###################################################################################################################################
    Write-Host("`n===============================================================================")
    Write-Host("[Data Lake] Running adHoc query...")
    Write-Host("===============================================================================")
    
    # Define Query in easily readable form and then format it for XDR
    $Template = @" 
        # INSERT YOUR QUERY HERE 
"@ -replace "(?m)^\s+"  -replace "`r","" -replace "`n","\n"

    # If your query uses variables then define them here, for example:
    # $variables = '[{"name": "device_name","dataType": "text","value": "%"}]'
    $Variables = ''

    # Specify desired time range for the query
    $Currtime = Get-Date
    $Fromtime = $Currtime.AddDays(-7).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")
    $Tilltime = $Currtime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")

    # Construct body
    if ($Variables -eq '') {
        $Body = '{"adHocQuery": { "template": "' + $Template + '" },"from": "'+$Fromtime+'","to": "'+$Tilltime+'}'
    } else {
        $Body = '{"adHocQuery": { "template": "' + $Template + '" },"from": "'+$Fromtime+'","to": "'+$Tilltime+'","variables": '+$Variables+'}'
    }

    $Response = (Invoke-RestMethod -Method POST -Uri $DataRegion"/xdr-query/v1/queries/runs" -Headers $TenantHead -Body $Body)              

    While ($Response.status -ne "finished") {
        start-sleep -s 1
        $Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($Response.id)" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
    }

    if ($Response.result -eq "succeeded") {
        $Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($Response.id)/results?maxSize=1000" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        
        # plain output of the response, if you need the results formatted then use | format-table
        $Response.items 
    } else {
        Write-Output ("Request failed!")
        Write-Output ($Response)
    }
}

X11a XDR Ad hoc Query Example [snippet] – In this example is purely for showing how to use the X11 XDR ad hoc query template. We have inserted a sample query that uses one variable.

# add this code snippet to one of the auth code samples for Central Admin, Central Enterprise Dashboard or Central Partner (snippets 1 2 or 3)
# you will find a line that says INSERT CODE HERE

# SOPHOS XDR API Headers:
$TenantHead = @{}
$TenantHead.Add("Authorization", "Bearer $Token")
$TenantHead.Add("X-Tenant-ID", "$TenantID")
$TenantHead.Add("Content-Type", "application/json")

if ($null -ne $DataRegion){
        
    ###################################################################################################################################
    # Runs an adHoc query against the Sophos Data Lake via the XDR API
    ###################################################################################################################################
    Write-Host("`n===============================================================================")
    Write-Host("[Data Lake] Running adHoc query...")
    Write-Host("===============================================================================")
    
    # Define Query in easily readable form and then format it for XDR
    $Template = @" 
    -- running_processes_windows_sophos
    SELECT meta_hostname, meta_ip_address, cmdline, global_rep, local_rep, ml_score, pua_score, sha256, sophos_pid, calendar_time
    FROM xdr_data
    WHERE query_name = 'running_processes_windows_sophos' AND parent_name = '`$`$Parent_Name`$`$'
"@ -replace "(?m)^\s+"  -replace "`r","" -replace "`n","\n"

    # If your query uses variables then define them here, for example:
    # $variables = '[{"name": "device_name","dataType": "text","value": "%"}]'
    $variables = '[{ "name": "Parent_Name", "dataType": "text", "value": "svchost.exe" }]'

    # Specify desired time range for the query
    $Currtime = Get-Date
    $Fromtime = $Currtime.AddDays(-7).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")
    $Tilltime = $Currtime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")

    # Construct body
    if ($Variables -eq '') {
        $Body = '{"adHocQuery": { "template": "' + $Template + '" },"from": "'+$Fromtime+'","to": "'+$Tilltime+'}'
    } else {
        $Body = '{"adHocQuery": { "template": "' + $Template + '" },"from": "'+$Fromtime+'","to": "'+$Tilltime+'","variables": '+$Variables+'}'
    }

    $Response = (Invoke-RestMethod -Method POST -Uri $DataRegion"/xdr-query/v1/queries/runs" -Headers $TenantHead -Body $Body)              

    While ($Response.status -ne "finished") {
        start-sleep -s 1
        $Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($Response.id)" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
    }

    if ($Response.result -eq "succeeded") {
        $Response = (Invoke-RestMethod -Method Get -Uri $DataRegion"/xdr-query/v1/queries/runs/$($Response.id)/results?maxSize=1000" -Headers $TenantHead -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        
        # plain output of the response, if you need the results formatted then use | format-table
        $Response.items 
    } else {
        Write-Output ("Request failed!")
        Write-Output ($Response)
    }
}



Updated Tags
[edited by: Marcel at 1:48 PM (GMT -7) on 13 Sep 2024]