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]