Sophos Central API Academy 2022 sample scripts

Below are the samples used in the September 2022 Central API Academy webinar series. You can find the recording later in September here https://events.sophos.com/recordings 

---------------------

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

for Central Admin = customer 

for Central Organization = customers using Enterprise Dashboard

for Central Partner = partners using Partner Dashboard

Put the code from the samples starting #5 (except the ones where it says "Works standalone and does not need the auth code in front") 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 for API Central Admin authentication with securely saving the 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 - 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"
	$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
$APIidType = $APIWhoamiResult.idType	
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion

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

#*02 Central Oranization Authentication - Basic sample for API Central Enterprise Dashbaord authentication with securely saving the 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 - 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"
	$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
$APIidOrganization = $APIWhoamiResult.id
$APIidType = $APIWhoamiResult.idType	
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion

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

# SOPHOS Organization API Headers:
$APIorganizationHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Organization-ID" = "$APIidOrganization";
}

# 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 $APIorganizationHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    foreach ($Tenant in $TenantList.items) {

        $APIidTenant = $Tenant.id
        $APIdataRegion = $Tenant.apiHost

        ################# INSERT CODE HERE ###############
        Write-Output $Tenant.showAs
        ################# INSERT CODE HERE ###############
    }
    $TenantPage++

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

#*03 Central Partner Authentication - Basic sample for API Central Partner Dashbaord authentication with securely saving the 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 - 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"
	$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
$APIidPartner = $APIWhoamiResult.id
$APIidType = $APIWhoamiResult.idType	
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion

# Check if we are using partner cerdentials
if ($APIidType -ne "partner") {
    Write-Output "Aborting script - idType does not match partner!"
    Break
}

# SOPHOS Partner API Headers:
$APIpartnerHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Partner-ID" = "$APIidPartner";
}

# 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 $APIpartnerHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    foreach ($Tenant in $TenantList.items) {

        $APIidTenant = $Tenant.id
        $APIdataRegion = $Tenant.apiHost

        ################# INSERT CODE HERE ###############
        Write-Output $Tenant.showAs
        ################# INSERT CODE HERE ###############

    }
    $TenantPage++

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

#*04 Send Email - Basic standalone sample of sending emails through PowerShell with Gmail as relay. Can be used to send data in body of the email instead of screen output.

# Send Email using Gmail and app password
$SMTP = "smtp.gmail.com"
$From = "your@gmail.com"
$To = "recipient@company.com"
$Subject = "Automated Email"
$Body = "This is your content"
$Email = New-Object Net.Mail.SmtpClient($SMTP, 587)
$Email.EnableSsl = $true
$Email.Credentials = New-Object System.Net.NetworkCredential("your@gmail.com", "apppasswordhere");
$Email.Send($From, $To, $Subject, $Body)

#*05 Clear Firewall Connectivity Alerts - Basic sample of acknowledging Central alerts - in this case Firewall alert.

# 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

$APIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
}
if ($null -ne $APIdataRegion){
	# Post Request to SOPHOS for Endpoint API:
	$AllAlertResult = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/common/v1/alerts?sort=raisedAt&product=firewall&category=connectivity&pageSize=1000" -Headers $APIHeaders -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]) {

		# Request Headers
		$Headers = [ordered]@{
			"Authorization" = "Bearer $script:token";
			"X-Tenant-ID" = "$script:ApiTenantId";
			"Content-Type" = "application/json";
			"cache-control" = "no-cache";
		}
		$Body = "{""action"":""acknowledge""}"
		$uri = $script:ApiHost+"/common/v1/alerts/"+$alert.id+"/actions"

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



#*06 Health Check - Basic sample of performing the Central Admin platform health 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

# SOPHOS  API Headers:
$APIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
}

if ($null -ne $APIdataRegion){
	# Post Request to Firewall API:
	$Result = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/account-health-check/v1/health-check" -Headers $APIHeaders -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)

#*07 Import User - Basic sample of importing users into Central Admin with fixed group assignments (group imported users)

# 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

# Creating group for imported users

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

$APIHeaders = [ordered]@{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
	"Content-Type" = "application/json";
	"cache-control" = "no-cache";
}

# Replace original groups 
$body = [PSCustomObject]@{
    name     = 'Imported Users'
    description = 'Group for imported users'
   
}

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

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

# Invoke Request
$Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $APIHeaders -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){
	
	# Request headers
	$APIHeaders = [ordered]@{
		"Authorization" = "Bearer $Token";
		"X-Tenant-ID" = "$APIidTenant";
		"Content-Type" = "application/json";
		"cache-control" = "no-cache";
	}
		
	# 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 = $APIdataRegion+"/common/v1/directory/users"

	# Invoke Request
	$Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $APIHeaders -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..."

#*07a csv template file required for user import. Name it importUser.csv

name,firstName,lastName,email,exchangeLogin
User1,User,One,user1@one.com,user1
User2,User,Two,user2@two.com,user2

#*08 Import User with groups - Little more complex sample of importing users in indvidual groups. Great for already existing groups in Central Admin.

# 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 "Importing users from CSV..."
Write-Output ""

# Import data from CSV
$importFile = Import-Csv $PSScriptRoot\importUserWithGroups.csv

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

# Iterate through all users
foreach ($item in $importFile){
	
	# Request headers
	$APIHeaders = [ordered]@{
		"Authorization" = "Bearer $Token";
		"X-Tenant-ID" = "$APIidTenant";
		"Content-Type" = "application/json";
		"cache-control" = "no-cache";
	}
		
	
	# Split string in case of multiple group memberships
	$groups = $item.groupids.split("{;}")
	
	# Replace original groups 
	$item.PSobject.Properties.Remove('groupIds')
	$item | Add-Member -NotePropertyName groupIds -NotePropertyValue $groups

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

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

	# Invoke Request
	$Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $APIHeaders -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..."

#*08a csv template file required for user import with groups. Name it importUserWithGroups.csv

name,firstName,lastName,email,exchangeLogin,groupids
User1,User,One,user1@one.com,user1,0ff4be1b-8992-4ca6-84d1-70490725477c
User2,User,Two,user2@two.com,user2,0ff4be1b-8992-4ca6-84d1-70490725477c

#*09 Assign Admin Role - Basic sample of assigning roles to Central Admin admin accounts

# 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:
$TenantAPIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
    "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 "[API] Getting details of already existing Administrators"
# Search all Admin Accounts in Tennant
$GetAdmins = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/common/v1/admins?pageTotal=true" -Headers $TenantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
foreach($Admin in $GetAdmins.items){
    if ($Admin.profile.email -eq $AdminEmail) {
        $AdminId=$Admin.id
        $AdminRole=$Admin.roleAssignments.RoleId
    }
}

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

Write-Output "[API] Getting the ID of the Super Admin Role"
# Search ID of SuperAdmin Role
$GetRoles = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/common/v1/roles?pageTotal=true" -Headers $TenantAPIHeaders -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 = $APIdataRegion + "/common/v1/admins"
} else {
    $body = '{ "roleId": "' + $RoleId + '"}'
    $uri = $APIdataRegion + "/common/v1/admins/" + $AdminId + "/role-assignments"
}

try {
    $Result = (Invoke-RestMethod -Uri $uri -Method Post -Headers $TenantAPIHeaders -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.'

#*10 Download Installer - Basic sample of retrieving the URL to the installer from a given Central Admin account

# 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:
$TentantAPIHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Tenant-ID" = "$APIidTenant";
}

#List all Installers sorted by PLatform and Platform Type
$Getinstallers = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/endpoint/v1/downloads" -Headers $TentantAPIHeaders -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}}
   

#*11 Device Migration - Basic sample of migrating installed Endpoints from one Central Admin account to another. Works standalone and does not need the auth code in front.

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

#*11a Device Migration - Part two of the device migration. Works standalone and does not need the auth code in front.

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

#*12 Central Partner Billing Usage - Basic sample of retrieving monthly usage for Sophos MSP Flex Partners. Works standalone and does not need the auth code in front.

param ([switch] $SaveCredentials, $month, $year)
<#
    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
                -month -> will select the month for reporting
                -year -> will select the reporing year
#>
Clear-Host
Write-Output "==============================================================================="
Write-Output "Sophos API - 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"
    $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
}

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

# 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
$APIidPartner = $APIWhoamiResult.id
$APIidType = $APIWhoamiResult.idType
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion
$APIHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Partner-ID" = "$APIidPartner";
}
# Post Request to Partner API:
$Response = (Invoke-RestMethod -Method Get -Uri "https://api.central.sophos.com/partner/v1/billing/usage/$year/$month" -Headers $APIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
# Store result in CSV File
$Response.items | Export-Csv -Path $PSScriptRoot"\monthlyusage.csv"

#*13 Endpoint Status - Basic sample of retrieving device health information

# 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:
$TentantAPIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
}

do {
    $GetEndpoints = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/endpoint/v1/endpoints?pageTotal=true&pageFromKey=$NextKey&fields=hostname,tamperProtectionEnabled,health,os&view=summary&sort=healthStatus" -Headers $TentantAPIHeaders -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}}

#*14 Endpoint Exclusion Check - Basic sample of listing global, endpoint and server policy exlusions

# 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:
$APIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
}

    Write-Host("===============================================================================")
    Write-Host("Global Exclusions")
    Write-Host("===============================================================================")

if ($null -ne $APIdataRegion){
	# Post Request to Central API:
	$Result = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/endpoint/v1/settings/exclusions/scanning" -Headers $APIHeaders -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-Host("===============================================================================")
Write-Host("Endpoint Policy Exclusions")
Write-Host("===============================================================================")

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

foreach ($item in $Result.items) {
    if($item.settings.'endpoint.threat-protection.exclusions.scanning'.value.Count -gt 0){
        Write-Host "`n---------------------------------"
        Write-Host $item.name
        Write-Host "---------------------------------"
        $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-Host("===============================================================================")
Write-Host("Server Policy Exclusions")
Write-Host("===============================================================================")

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

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

#*15 Import Endpoint exclusions - Basic sample of importing global exlcusions: path, process and website

# 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 "Importing global exclusions from CSV..."
Write-Output ""

# Import data from CSV
$importFile = Import-Csv $PSScriptRoot\exclusions.csv

Write-Output "Creating global exclusions in Sophos Central..."
Write-Output ""

# Iterate through all exclusions from CSV
foreach ($item in $importFile){

    # Request headers
    $APIHeaders = [ordered]@{
		"Authorization" = "Bearer $token";
		"X-Tenant-ID" = "$APIidTenant";
		"cache-control" = "no-cache";
	}
    
    # Create request body by converting to JSON
    $body = $item | ConvertTo-Json

    # Set correct URI
    $uri = $APIdataRegion+"/endpoint/v1/settings/exclusions/scanning"

    # Invoke Request
    $Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $APIHeaders -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..."

#*15a csv template for exclusions. Name it exclusions.csv

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

#*16 Import Website Tags - Basic sample of importing Website Tags to be used with Endpoint Web Control and website recategorization

# 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 "Importing sites from CSV..."
Write-Output ""

# Import data from CSV
$importFile = Import-Csv $PSScriptRoot\websites.csv

Write-Output "Creating local sites in Sophos Central..."
Write-Output ""

# Iterate through all sites from CSV
foreach ($item in $importFile){

    # Request headers
    $APIHeaders = [ordered]@{
		"Authorization" = "Bearer $token";
		"X-Tenant-ID" = "$APIidTenant";
		"cache-control" = "no-cache";
	}

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

    # Replace original tags
    $item.PSobject.Properties.Remove('tags')
	$item | Add-Member -NotePropertyName tags -NotePropertyValue $tags
    
    # Create request body by converting to JSON
    $body = $item | ConvertTo-Json

    # Set correct URI
    $uri = $APIdataRegion+"/endpoint/v1/settings/web-control/local-sites"

    # Invoke Request
    $Result = (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers $APIHeaders -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..."

#*16a csv template for Website Tags. Name it websites.csv

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

#*17 Web Policy Tagging - Basic sample of assigning policy rules to website tags in a Central Admin account

# 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

# Request headers
$APIHeaders = @{
	"Authorization" = "Bearer $token";
	"X-Tenant-ID" = "$APIidTenant";
    "Content-Type" = "application/json";
}

# Get all Web Control Policies
$Policies = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/endpoint/v1/policies?pageTotal=true&policyType=web-control" -Headers $APIHeaders -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 = $APIdataRegion + "/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 $APIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
        Write-Output "Base Policy Updated"
    }
}

#*18 Endpoint Policy Update - Basic sample of activating Machine Learning in all policies in a Central Admin account

# 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

# Request headers
$APIHeaders = @{
	"Authorization" = "Bearer $token";
	"X-Tenant-ID" = "$APIidTenant";
    "Content-Type" = "application/json";
}

# Get all Policies
$Policies = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/endpoint/v1/policies?pageTotal=true&pageSize=200" -Headers $APIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)

# Define action to enable Deep Learning
$Body = '{ "settings": { "endpoint.threat-protection.malware-protection.deep-learning.enabled": { "value": true }}}'

Write-Output "Scanning for Threat Protection policies with Deep Learning deactivated..."
foreach ($Policy in $Policies.items) {

    if ($Policy.type -eq "server-threat-protection") {
        $PolicyType = "Server"
    } else {
        $PolicyType = "Endpoint"
    }

    if (($Policy.type -eq "server-threat-protection") -or ($Policy.type -eq "threat-protection")) {

        if ($false -eq $Policy.settings.'endpoint.threat-protection.malware-protection.deep-learning.enabled'.value) {

            Write-Output "+ Deep Learning is not active in the $($PolicyType) '$($Policy.name)' policy --> activating..."
            $PolicyID = $Policy.id
            $uri = $APIdataRegion + "/endpoint/v1/policies/" + $PolicyID

            $Result = (Invoke-RestMethod -Method Patch -Uri $uri -Body $Body -Headers $APIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
            Write-Output "+-> Policy updated!"
        } else {
            Write-Output "+ Deep Learning is active in the $($PolicyType) '$($Policy.name)' policy --> no action needed..."
        }
    }
}
Write-Output "" "Policy Scan completed"

#*19 Firewall Inventory - Basic sample of listing the status of all Sophos Firewalls managed in Central Admin

# 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:
$APIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
}
if ($null -ne $APIdataRegion){
	# Post Request to Firewall API:
	$Result = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/firewall/v1/firewalls" -Headers $APIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
}

#Output in table format
$Result.items | Format-Table -Property @{label='Firewall Hostname';e={$_.hostname}},@{label='Firewall Name';e={$_.name}}, @{label='Firewall Group';e={$_.group}}, @{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}}

#*20 Firewall Upgrade Check - Basic sample of listing the update status of all Sophos Firewalls managed in Central Admin. Works standalone and does not need the auth code in front.

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 - Policy update"
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
$APIidType = $APIWhoamiResult.idType	
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion

################# 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:
$APIHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Tenant-ID" = "$APIidTenant";
    "Content-Type" = "application/json";
}

# Post Request to Firewall API:
$FWList = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/firewall/v1/firewalls" -Headers $APIHeaders -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 $APIdataRegion"/firewall/v1/firewalls/actions/firmware-upgrade-check" -Headers $APIHeaders -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

#*21 Firewall Upgrade Check Partner - Basic sample sample of listing the update status of all customer firewalls - when being able to manage the tenant. Works standalone and does not need the auth code in front.

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
$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
Write-Host("[API] Authenticate to Sophos Central...")
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
$APIidPartner = $APIWhoamiResult.id
$APIidType = $APIWhoamiResult.idType	
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion

# Check if we are using partner cerdentials
if ($APIidType -ne "partner") {
    Write-Output "Aborting script - idType does not match partner!"
    Break
}

# SOPHOS Partner API Headers:
$APIpartnerHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Partner-ID" = "$APIidPartner";
}

# 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 "[API] 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 $APIpartnerHeaders -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

        $APIidTenant = $Tenant.id
        $APIdataRegion = $Tenant.apiHost

            
        # SOPHOS API Headers:
        $APIHeaders = @{
            "Authorization" = "Bearer $Token";
            "X-Tenant-ID" = "$APIidTenant";
            "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 $APIdataRegion) {
        
            # Post Request to Firewall API:
            try {
                $FWList = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/firewall/v1/firewalls" -Headers $APIHeaders -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 $APIdataRegion"/firewall/v1/firewalls/actions/firmware-upgrade-check" -Headers $APIHeaders -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

#*22 XDR ad hoc query template - 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.

# 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:
$APIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
    "Content-Type" = "application/json";
}


if ($null -ne $APIdataRegion){
        
    ###################################################################################################################################
    # 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 $APIdataRegion"/xdr-query/v1/queries/runs" -Headers $APIHeaders -Body $body)              

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

    if ($Response.result -eq "succeeded") {
        $Response = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/xdr-query/v1/queries/runs/$($Response.id)/results?maxSize=1000" -Headers $APIHeaders -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)
    }
}

#*23 Firewall IPS Alerts - Basic sample of listing all Firewall IPS alerts through Central Firewall Reporting

# 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:
$XDRAPIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
    "Content-Type" = "application/json";
}

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

# Define variables used in the query
$classification = "%"
$message = "%"
$os_name = "%"
$src_ip = "%"
$dst_ip = "%"
$src_country = "%"

# Define query in easily readable form and then format it for XDR
$template = @"
    SELECT
	    device_serial_id,
        classification,
        message,
        os_name,
        signature_id,
        src_ip,
        dst_ip,
        src_country,
        SUM(hits) AS hits
    FROM
        xgfw_data
    WHERE
        log_type IN ('IDP')
        AND log_component IN ('Anomaly', 'Signatures')
        AND COALESCE(LOWER(classification),'') LIKE LOWER('$classification')
        AND COALESCE(LOWER(message),'') LIKE LOWER('$message')
        AND COALESCE(LOWER(os_name),'') LIKE LOWER('$os_name')
        AND COALESCE(LOWER(src_ip),'') LIKE LOWER('$src_ip')
        AND COALESCE(LOWER(dst_ip),'') LIKE LOWER('$dst_ip')
        AND COALESCE(LOWER(src_country),'') LIKE LOWER('$src_country')
    GROUP BY
	    device_serial_id,
        classification,
        message,
        os_name,
        signature_id,
        src_ip,
        dst_ip,
        src_country
    ORDER BY
        hits DESC
"@ -replace "(?m)^\s+"  -replace "`r","" -replace "`n","\n"

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



if ($null -ne $APIDataRegion){

    Write-Output("[XDR] Send Request to run Ad-Hoc Query for finding Firewall IPS Events...")
    $XDR_Response = (Invoke-RestMethod -Method POST -Uri $APIDataRegion"/xdr-query/v1/queries/runs" -Headers $XDRAPIHeaders -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 $APIDataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)" -Headers $XDRAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
    }

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

        Write-Output("[XDR] Query results:")
        Write-Output $XDR_Response.items | Format-Table -Property @{label='Device';e={$_.device_serial_id}}, @{label='Class';e={$_.classification}}, @{label='message';e={$_.message}}, @{label='os_name';e={$_.os_name}}, @{label='signature_id';e={$_.signature_id}}, @{label='src IP';e={$_.src_ip}}, @{label='dst IP';e={$_.dst_ip}}, @{label='Hits';e={$_.hits}}
        Write-Output "[XDR] Request completed, $($XDR_Response.Items.Count) entries were found!"  
    } else {
        Write-Output ("Request failed!")
        Write-Output ($XDR_Response)
    }
}

#*24 Threat Indicators - Basic sample of the detections screen output of the data shown in the XDR detections screen in Central Admin

# 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:
$XDRAPIHeaders = @{
	"Authorization" = "Bearer $Token";
	"X-Tenant-ID" = "$APIidTenant";
    "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")

# Define query in easily readable form and then format it for XDR
$template = @"
    SELECT 
        ioc_detection_weight, 
        meta_hostname, 
        ioc_detection_attack, 
        ioc_detection_description, 
        detection_item, 
        name, cmdline, 
        calendar_time, 
        username 
    FROM xdr_ti_data 
    ORDER BY ioc_detection_weight DESC
"@ -replace "(?m)^\s+"  -replace "`r","" -replace "`n","\n"

$body= '{ "adHocQuery": { "template": "' + $template + '" }, "from": "' + $fromtime + '" , "to": "' + $tilltime + '" }'
# the SELECT statement can be enhanced with e.g. WHERE ioc_detection_weight > 6 to mimic the default behaviour of the detections screen 
# or WHERE ioc_detection_description LIKE '%Login%' to search for specific events like failed windows login

if ($null -ne $APIDataRegion){
    Write-Output("[XDR] Send Request to run Ad-Hoc Query for retreiving Threat Indicator Data...")
    $XDR_Response = (Invoke-RestMethod -Method POST -Uri $APIDataRegion"/xdr-query/v1/queries/runs" -Headers $XDRAPIHeaders -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 $APIDataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)" -Headers $XDRAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
    }

    if ($XDR_Response.result -eq "succeeded") {
        Write-Output("[XDR] Query finished, retreiving results")
        $XDR_Response = (Invoke-RestMethod -Method Get -Uri $APIDataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)/results?pageSize=1000" -Headers $XDRAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)   
        
        Write-Output("[XDR] Query results:")
        $XDR_Response.items | Format-Table -Property @{label='Weight';e={$_.ioc_detection_weight}}, @{label='time';e={$_.calendar_time}}, @{label='Host';e={$_.meta_hostname}}, @{label='Attack';e={$_.ioc_detection_attack}}, @{label='Description';e={$_.ioc_detection_description}}
        Write-Output "[XDR] Request completed, $($XDR_Response.Items.Count) entries were found!"	    
    } else {
        Write-Output ("Request failed!")
        Write-Output ($XDR_Response)
    }
}



#*25 XDR SHA256 search - Basic sample of searching for SHA256 (PE and productivity documents). Works standalone and does not need the auth code in front.

param ([string[]] $SHA256, [switch] $SaveCredentials)
<#
    Description: Search Data Lake for SHA256 IoCs 
    Parameters:  -SHA256 -> 
                 -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 Academy - Search for SHA-256 based IoC's"
Write-Output "==============================================================================="

if ($null -eq $SHA256) {
    Write-Output "You need to specify at least one SHA-256 as parameter for this script to work!"
    Write-Output "Multiple SHA-256 values can be specified by using a comma as separator, "
    Write-Output "for example use: -SHA256 SHA256-Value1,SHA256-Value2"
    exit
} else {
    $SHA256 = $SHA256 -join "|"
}

# 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
$APIidType = $APIWhoamiResult.idType	
$APIdataRegion = $APIWhoamiResult.ApiHosts.dataRegion


# SOPHOS XDR API Headers:
$XDRAPIHeaders = @{
    "Authorization" = "Bearer $Token";
    "X-Tenant-ID" = "$APIidTenant";
    "Content-Type" = "application/json";
}

# Build Query Text

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

$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
        
        UNION 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
        
        UNION 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 $APIdataRegion) {
    $XDR_Response = (Invoke-RestMethod -Method POST -Uri $APIdataRegion"/xdr-query/v1/queries/runs" -Headers $XDRAPIHeaders -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 $APIdataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)" -Headers $XDRAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
    }

    if ($XDR_Response.result -eq "succeeded") {
        Write-Output("[XDR] Query finished, retreiving results")
        $XDR_Response = (Invoke-RestMethod -Method Get -Uri $APIdataRegion"/xdr-query/v1/queries/runs/$($XDR_Response.id)/results?pageSize=2000" -Headers $XDRAPIHeaders -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)
    }
}