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.
There is a KB explaining how license usage works: https://support.sophos.com/support/s/article/KB-000035892?language=en_US
Server licenses sum the number of servers online in the last 30 days.
User/computer licenses sum:
- the number of users with at least one device online in the last 30 days
- the number of devices with no last user
Identifying which specific users and/or devices are contributing to current usage can be done manually or programmatically.
Manual steps and a script to do it automatically are included below.
Manual steps to determine user/computer license usage:
- Export the computers report (not users)
- Open in Excel or equivalent
- Sort by last connected time, and remove any offline >30 days
- Count those with no "last user".
- Deduplicate the "last user" field to leave only unique entries
- Count the number of deduplicated users (note: don't include a blank entry as it will have already been counted as part of step 4).
- Add up the totals from step 4 and 6 to give the current license usage. It should be [devices online in the last 30 days with no user] + [users with one or more devices online in the last 30 days]
A PowerShell script to do the same task using the Sophos Central public APIs is provided below:
By using or accessing the Software below, you agree to be bound by the terms of the Sophos End User License Agreement.
param ([switch] $ShowDetails)
<#
Author.....: Sophos Sales Engineering
Description: Script to calculate license usage from Sophos Central
Version 1.0: Initial release
#>
$DeviceListA = @{}
$DeviceListU = @()
$ServerListU = @()
# Check if Central API Credentials have been stored, if not then prompt the user to add them
if ((Test-Path $env:userprofile\sophos_central_admin.json) -eq $false){
# Prompt for Credentials
$clientId = Read-Host "Please Enter your Client ID"
$clientSecret = Read-Host "Please Enter your Client Secret" -AsSecureString | ConvertFrom-SecureString
# Out to JSON Config File
ConvertTo-Json $ClientID, $ClientSecret | Out-File $env:userprofile\sophos_central_admin.json -Force
}
# Read Credentials from JSON Config File
$credentials = Get-Content $env:userprofile\sophos_central_admin.json | ConvertFrom-Json
$clientId = $credentials[0]
$clientSecret = $credentials[1] | ConvertTo-SecureString
# Create PSCredential Object for Credentials
$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 OAuth2 token:
$APIAuthResult = (Invoke-RestMethod -Method Post -Uri $TokenURI -Body $TokenRequestBody -Headers $TokenRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
# If there's an error requesting the token, say so, display the error, and break:
if ($ScriptError) {
Write-Output "FAILED - Unable to retreive SOPHOS API Authentication Token - $($ScriptError)"
Break
}
# Set the Token for use later on:
$script: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 $script:Token";
}
# Post Request to SOPHOS for Whoami Details:
$APIWhoamiResult = (Invoke-RestMethod -Method Get -Uri $WhoamiURI -Headers $WhoamiRequestHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
# Set TenantID and ApiHost for use later on:
$script:ApiTenantId = $APIWhoamiResult.id
$script:ApiHost = $APIWhoamiResult.apiHosts.dataRegion
# SOPHOS Endpoint API Headers:
$TentantAPIHeaders = @{
"Authorization" = "Bearer $script:Token";
"X-Tenant-ID" = "$script:ApiTenantId";
}
if ($apihost -ne $null){
# Get List of Servers that were active in the last 30 days:
do {
$ServersCounted = (Invoke-RestMethod -Method Get -Uri $script:ApiHost"/endpoint/v1/endpoints?pageTotal=true&pageFromKey=$NextKey&type=server&lastSeenAfter=-P30D&fields=hostname%2ClastSeenAt%2CassociatedPerson&sort=lastSeenAt" -Headers $TentantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
$script:NextKey = $ServersCounted.pages.nextKey
foreach ($device in $ServersCounted.items) {
$script:Hostname = $($device.hostname)
$ServerListU += $Hostname
}
} while ($NextKey -ne $null)
# Calculate the number of servers that were active in the last 30 days
$script:ServersCountedTotal = $ServerListU.Count
# Get List of Devices that were active in the last 30 days:
do {
$DevicesCounted = (Invoke-RestMethod -Method Get -Uri $script:ApiHost"/endpoint/v1/endpoints?pageTotal=true&pageFromKey=$NextKey&type=computer&lastSeenAfter=-P30D&fields=hostname%2ClastSeenAt%2CassociatedPerson&sort=lastSeenAt" -Headers $TentantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
$script:NextKey = $DevicesCounted.pages.nextKey
foreach ($device in $DevicesCounted.items) {
$script:Hostname = $($device.hostname)
if ($($device.associatedPerson.ID) -eq $null) {
$DeviceListU += $Hostname
} else {
$script:UserInfo = @{}
$script:UserInfo.Name = $($device.associatedPerson.name)
$script:UserInfo.ID = $($device.associatedPerson.ID)
$script:UserInfo.Login = $($device.associatedPerson.viaLogin)
$DeviceListA.add($Hostname, $UserInfo)
}
}
} while ($NextKey -ne $null)
# Calculate the number of unique users and endpoints without users assigned that were active in the last 30 days
$script:DevicesCountedTotal = $DevicesCounted.pages.items
$script:UsersCountedTotal = ($DeviceListA.Values.ID | select -Unique).Count
$script:DevLicsCountedTotal = $DeviceListU.Count
# Get List of Servers that have not been active during the last 30 days:
$ServersIgnored = (Invoke-RestMethod -Method Get -Uri $script:ApiHost"/endpoint/v1/endpoints?pageTotal=true&type=server&lastSeenBefore=-P30D&fields=hostname%2ClastSeenAt%2CassociatedPerson&sort=lastSeenAt" -Headers $TentantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
$script:ServersIgnoredTotal = $ServersIgnored.pages.items
# Get List of Devices that have not been active during the last 30 days:
$DevicesIgnored = (Invoke-RestMethod -Method Get -Uri $script:ApiHost"/endpoint/v1/endpoints?pageTotal=true&type=computer&lastSeenBefore=-P30D&fields=hostname%2ClastSeenAt%2CassociatedPerson&sort=lastSeenAt" -Headers $TentantAPIHeaders -ErrorAction SilentlyContinue -ErrorVariable ScriptError)
$script:DevicesIgnoredTotal = $DevicesIgnored.pages.items
}
Write-Output "Sophos Central - License Usage"
Write-Output "=========================================================================================="
Write-Output "For the calculation of the license usage the following rules apply:"
Write-Output "* Server Protection is always licensed by device"
Write-Output "* Endpoint Protection is always licensed by user."
Write-Output "- If the user is unknown then the device itself will consume a license"
Write-Output "* Devices that have been offline for more then 30 days do not consume licenses."
Write-Output ""
Write-Output "Summary:"
Write-Output "--------"
Write-Output "Number of servers consuming licenses............: $ServersCountedTotal"
Write-Output "Number of servers not using licenses............: $ServersIgnoredTotal"
Write-Output ""
Write-Output "Number of devices active in the last 30 days....: $DevicesCountedTotal"
Write-Output "Number of devices not active in the last 30 days: $DevicesIgnoredTotal"
Write-Output ""
Write-Output "Number of devices consuming licenses............: $DevLicsCountedTotal"
Write-Output "Number of users consuming licenses..............: $UsersCountedTotal"
Write-Output ""
if ($ShowDetails) {
Write-Output ""
Write-Output "Details:"
Write-Output "--------"
Write-Output "Servers consuming licenses:"
foreach ($device in $ServerListU) {
Write-Output "$device "
}
Write-Output ""
Write-Output "Devices without a user assigned consuming licenses:"
foreach ($device in $DeviceListU) {
Write-Output "$device "
}
Write-Output ""
Write-Output "Devices assigned to users consuming licenses:"
foreach ($key in $DeviceListA.keys) {
$message = '{0} assigned to {1}' -f $key, $DeviceListA[$key].Name
Write-Output "$message "
}
}
If you call the script with the optional parameter -ShowDetails the script will output the computer names and the names of the user they are assigned to as well.
Updated disclaimer
[edited by: Qoosh at 9:44 PM (GMT -7) on 31 Mar 2023]