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 took place on the 16th and 17th of September 2024. If you missed the webinar or if you want to see it again you can use the links below to access the respective sessions:

English: events.sophos.com/.../35d7d211-5f6d-47b7-96f0-46ee9e7183e2
German: events.sophos.com/.../a8b5a60a-fd65-4c69-9a2a-851e8fd1e0c6


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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

#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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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}},
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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){
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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 ""
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Sample file for use with this snippet:
Fullscreen
1
2
3
sha256,comment
0f66c6e095a01f9709be73cde3ccbb43a2a30af57e15d6f099278a13d31b932e,Unknow
f5d39e20d406c846041343fe8fbd30069fd50886d7d3d0cce07c44008925d434,Megacortex
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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 ""
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Sample file for use with this snippet:
Fullscreen
1
2
3
4
5
6
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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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}}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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) {
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 {
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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"
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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")
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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"
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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")
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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")
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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")
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 "==============================================================================="
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
param ([switch] $Debug)
<#
Description: Migrate devices that start with a specific text 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 = ""
$SrcToken = ""
# Define OAUTH Access Data for the destination (receiving) account
$DstID = ""
$DstSecret = ""
$DstToken = ""
# Define which devices are te be migrated, for this we use the hostname. Full name means only one device is migrated,
# a blank string ("") all devices and "WKS" means that only devices starting with that string are migrated.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<#
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
(
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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 {
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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){
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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 "==============================================================================="
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 

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

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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 ""
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Sample file for use with the above snippet:
Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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}}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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" }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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) {
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX



Added the migration tag
[edited by: Marcel at 7:22 AM (GMT -7) on 8 Oct 2024]