SophosCentral.psm1

# ./SophosCentral/private/Get-SophosCentralAuthHeader.ps1
function Get-SophosCentralAuthHeader {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param (
        [switch]$Initial,
        [switch]$PartnerInitial
    )

    if ((Test-SophosCentralAuth) -eq $true) {
        $header = @{
            Authorization = 'Bearer ' + (Unprotect-Secret -Secret $SCRIPT:SophosCentral.access_token)
        }
        if (($Initial -ne $true) -and ($PartnerInitial -ne $true)) {
            switch ($SCRIPT:SophosCentral.IDType) {
                'partner' {
                    $header.Add('X-Tenant-ID', $SCRIPT:SophosCentral.CustomerTenantID)
                }
                'tenant' {
                    $header.Add('X-Tenant-ID', $SCRIPT:SophosCentral.TenantID)
                }
                'organization' {
                    $header.Add('X-Tenant-ID', $SCRIPT:SophosCentral.CustomerTenantID)
                }
            }
        }
        if ($PartnerInitial) {
            switch ($SCRIPT:SophosCentral.IDType) {
                'partner' {
                    $header.Add('X-Partner-ID', $SCRIPT:SophosCentral.TenantID)
                }
                'organization' {
                    $header.Add('X-Organization-ID', $SCRIPT:SophosCentral.TenantID)
                }
            }
            
        }
        $header
    }
}
# ./SophosCentral/private/Invoke-SophosCentralWebRequest.ps1
function Invoke-SophosCentralWebRequest {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.URI]$Uri,
        
        [System.Collections.Hashtable]$CustomHeader,

        [ValidateSet('Get', 'Post', 'Put', 'Delete')]
        [string]$Method = 'Get',

        [System.Collections.Hashtable]$Body
    )

    if ($PSVersionTable.PSVersion.Major -lt 7) {
        Write-Warning 'Unsupported version of PowerShell detected'
    }
    
    if ($null -ne $CustomHeader) {
        $header = $CustomHeader
    } else {
        try {
            $header = Get-SophosCentralAuthHeader
        } catch {
            throw $_
        }
    }


    #inital request
    $webRequest = @{
        Uri             = $uri
        Headers         = $header
        UseBasicParsing = $true
        Method          = $Method
    }
    if (($null -eq $body) -and ($Method -in ('Post', 'Put'))) {
        #API endpoints that use a 'post'/'put' require a body. If no body is present it will give an error back, so supply an empty body
        $bodyTmp = @{} | ConvertTo-Json
        $webRequest.Add('Body', $bodyTmp)
    } elseif (($null -ne $Body) -and ($Method -eq 'Get')) {
        $webRequest.Add('Body', $Body)
    } elseif ($null -ne $Body) {
        $webRequest.Add('Body', ($Body | ConvertTo-Json -Depth 5))
    }

    if ($Method -notin ('Delete', 'Get')) {
        $webRequest.Add('ContentType', 'application/json')
    }

    #query api and return the first page
    $response = Invoke-RestMethod @webRequest
    if ($null -ne $response.items) {
        $response.items
    } else {
        $response
    }
    
    #pagination
    $finished = $false
    if ($response.pages.total -gt 1) {
        #enterprise/partner tenant pagination - based on total returned from the initial lookup
        #see here for details on pagination https://developer.sophos.com/getting-started > step 4
        #doco says the initial page is 1, so the following pages start at 2 onwards
        for ($i = 2; $i -le $response.pages.total; $i++) {
            $webRequest['Uri'] = $uri.AbsoluteUri.Replace('pageTotal=true', "page=$($i.ToString())")
            
            $responseLoop = Invoke-RestMethod @webRequest
            if ($null -ne $response.items) {
                $responseLoop.items
            } else {
                $responseLoop
            }
        }
    } else {
        #standard pagination - based on nextKey value returned from the previous lookup
        do {
            if ($response.pages.nextKey) {
                if ($uri.AbsoluteUri -like '*`?*') {
                    $webRequest['Uri'] = $uri.AbsoluteUri + '&pageFromKey=' + $response.pages.nextKey
                } else {
                    $webRequest['Uri'] = $uri.AbsoluteUri + '?pageFromKey=' + $response.pages.nextKey
                }
                $response = Invoke-RestMethod @webRequest
                $response.items
            } else {
                $finished = $true
            }
        } while ($finished -eq $false)
    }
}

# ./SophosCentral/private/New-UriWithQuery.ps1
function New-UriWithQuery {
    [CmdletBinding()]
    [OutputType([System.Uri])]
    param (
        [Parameter(Mandatory = $true)]
        [System.Uri]$Uri,

        [Parameter(Mandatory = $true)]
        [hashtable]$OriginalPsBoundParameters,

        [Parameter(Mandatory = $false)]
        [Alias('filteredParmeters')]
        [array]$FilteredParameters
    )

    $uriBuilder = [System.UriBuilder]::New($Uri.AbsoluteUri)
    $blockedKeys = 'Verbose', 'Force', 'Debug', 'WhatIf' + $FilteredParameters
    $keys = $OriginalPsBoundParameters.Keys | Where-Object { $blockedKeys -notcontains $_ } 
    
    foreach ($param in $keys) {
        if (($null -ne $OriginalPsBoundParameters[$param]) -and ($null -ne $param)) {
            $paraCaseSensitive = $param.ToString()[0].ToString().ToLower() + $param.ToString().Substring(1)
            if ( $OriginalPsBoundParameters[$param] -is [array]) {
                $queryPart = $paraCaseSensitive + '=' + ($OriginalPsBoundParameters[$param] -join ',')
            } else {
                #convert time time to ISO 8601 Universal Format Specifier
                if ($OriginalPsBoundParameters[$param].GetType().Name -eq 'DateTime') {
                    $OriginalPsBoundParameters[$param] = $OriginalPsBoundParameters[$param].ToUniversalTime().ToString('u').Replace(' ', 'T')
                }
                $queryPart = $paraCaseSensitive + '=' + $OriginalPsBoundParameters[$param]
            }
            if (($null -eq $uriBuilder.Query) -or ($uriBuilder.Query.Length -le 1 )) {
                $uriBuilder.Query = $queryPart
            } else {
                $uriBuilder.Query = $uriBuilder.Query.Substring(1) + '&' + $queryPart
            }
        }
    }

    [System.Uri]::New($uriBuilder.Uri)
}

# ./SophosCentral/private/Show-UntestedWarning.ps1
function Show-UntestedWarning {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$CustomWarning
    )
    if ($null -eq $SCRIPT:TestWarning) {
        $SCRIPT:TestWarning = $true
    }
    
    if ($SCRIPT:TestWarning -eq $true) {
        if ($null -ne $CustomWarning) {
            Write-Warning $CustomWarning
        } else {
            Write-Warning 'This command has not been tested'
        }
        
        $action = Read-Host -Prompt "Continue?`ny: Continue`nn: Stop`nf: Ignore untested warnings for this session`nEnter response here"
        switch ($action) {
            'y' {  }
            'n' { throw 'cancelling' }
            'f' { $SCRIPT:TestWarning = $false }
            Default { throw 'cancelling' }
        }
    }
}
# ./SophosCentral/private/Test-SophosCentralAuth.ps1
function Test-SophosCentralAuth {
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        
    )
    if ($SCRIPT:SophosCentral) {
        $date = Get-Date
        if ($SCRIPT:SophosCentral.expires_at -le $date) {
            Write-Verbose 'Access token has expired'
            #request new token
            try {
                Write-Verbose 'Attempting to obtain new access token'
                Connect-SophosCentral -ClientID $SCRIPT:SophosCentral.client_id -ClientSecret $SCRIPT:SophosCentral.client_secret -AccessTokenOnly
                Write-Verbose 'Testing new access token'
                Test-SophosCentralAuth
            } catch {
                Write-Error 'error requesting new token'
                return $false
            }           
        } else {
            return $true
        }
    } else {
        Write-Error "You must connect with 'Connect-SophosCentral' before running any commands"
        return $false
    }
}
# ./SophosCentral/private/Test-SophosCentralConnected.ps1
function Test-SophosCentralConnected {
    [CmdletBinding()]
    param (
        
    )
    if ($null -eq $SCRIPT:SophosCentral.access_token) {
        throw "You must connect with 'Connect-SophosCentral' before running any commands"
    } 
}
# ./SophosCentral/private/Test-SophosEnterprise.ps1
function Test-SophosEnterprise {
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        
    )
    if ($SCRIPT:SophosCentral.IDType -ne 'organization') {
        return $false
    } else {
        Write-Verbose 'currently logged in using a Sophos Central Partner Service Principal'
        return $true
    }
}
# ./SophosCentral/private/Test-SophosPartner.ps1
function Test-SophosPartner {
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        
    )
    if ($SCRIPT:SophosCentral.IDType -ne 'partner') {
        return $false
    } else {
        Write-Verbose 'currently logged in using a Sophos Central Partner Service Principal'
        return $true
    }
}
# ./SophosCentral/private/Unprotect-Secret.ps1
function Unprotect-Secret {
    <#
    .SYNOPSIS
        Convert a [SecureString] to a [String]
    .EXAMPLE
        $plaintextsecret = Unprotect-Secret -Secret $clientsecret
    #>

    [CmdletBinding()]
    [OutputType([System.String])]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'The Secure String to convert to plain text')]
        [SecureString]$Secret
    )
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret)
    [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
# ./SophosCentral/public/Connect-SophosCentral.ps1
function Connect-SophosCentral {
    <#
    .SYNOPSIS
        Connect to Sophos Central using your client ID and client secret, from you API credentials/service principal
    .DESCRIPTION
        Connect to Sophos Central using your client ID and client secret, from you API credentials/service principal

        Sophos customers can connect to their tenant using a client id/secret. Follow Step 1 here to create it
        https://developer.sophos.com/getting-started-tenant

        Sophos partners can use a partner client id/secret to connect to their customer tenants. Follow Step 1 here to create it
        https://developer.sophos.com/getting-started

        Sophos enterprise customers can connect to their tenants using a client/secret. Follow Step 1 here to create it
        <https://developer.sophos.com/getting-started-organization>
    .PARAMETER ClientID
        The client ID from the Sophos Central API credential/service principal
    .PARAMETER ClientSecret
        The client secret from the Sophos Central API credential/service principal
    .PARAMETER SecretVault
        Login using a client ID and client secret retrieved from Secret Vault (Microsoft.PowerShell.SecretManagement).
        Setup example found in https://github.com/simon-r-watson/SophosCentral/wiki/AzureKeyVaultExample
        This is not exclusive to using it with Azure Key Vault, you can use other providers supported by Microsoft.PowerShell.SecretManagement, such as a local one
    .PARAMETER AzKeyVault
        Calls Connect-AzAccount before retrieving the secrets from the secret vault, this can be skipped if you are already connected
        Uses the Microsoft.PowerShell.SecretManagement and Az.KeyVault modules for retrieving secrets
        Setup example found in https://github.com/simon-r-watson/SophosCentral/wiki/AzureKeyVaultExample
    .PARAMETER AccessTokenOnly
        Internal use (for this module) only. Used to generate a new access token when the current one expires
    .PARAMETER SecretVaultName
        Name of the secret vault, defaults to AzKV
    .PARAMETER SecretVaultClientIDName
        Name of the secret containing the client ID in the vault
    .PARAMETER SecretVaultClientSecretName
        Name of the secret containing the client secret in the vault
    .EXAMPLE
        Connect-SophosCentral -ClientID "asdkjsdfksjdf" -ClientSecret (Read-Host -AsSecureString -Prompt "Client Secret:")
    .EXAMPLE
        Connect-SophosCentral -SecretVault -AzKeyVault

        Connect using default values for Secret Key Vault name, Client ID name, and Client Secret name, with secret vault configured to use Azure Key Vault
    .EXAMPLE
        Connect-SophosCentral -SecretVault -AzKeyVault -SecretVaultName 'secrets' -SecretVaultClientIDName 'sophosclientid' -SecretVaultClientSecretName 'sophosclientsecret'

        Connect using custom Secret Vault name, Client ID Name, and Client Secret Name, with secret vault configured to use Azure Key Vault
    .EXAMPLE
        Connect-SophosCentral -SecretVault -SecretVaultName 'secrets' -SecretVaultClientIDName 'sophosclientid' -SecretVaultClientSecretName 'sophosclientsecret'

        Connect using custom Secret Vault name, Client ID Name, and Client Secret Name when using a locally stored Secret Vault (or another Microsoft.PowerShell.SecretManagement integration)
    .LINK
        https://developer.sophos.com/getting-started-tenant
    .LINK
        https://developer.sophos.com/getting-started
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'SecureStrings main usage is to stop items from appearing in the console, and not encrypting memory', Scope = 'Function')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'StdAuth',
            HelpMessage = 'The client ID from the Sophos Central API credential/service principal')]
        [String]$ClientID,

        [Parameter(ParameterSetName = 'StdAuth',
            HelpMessage = 'The client secret from the Sophos Central API credential/service principal')]
        [SecureString]$ClientSecret,

        [Parameter(ParameterSetName = 'StdAuth',
            HelpMessage = 'Internal use (for this module) only. Used to generate a new access token when the current one expires')]
        [Switch]$AccessTokenOnly,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'SecretVaultAuth',
            HelpMessage = 'Enable secret vault auth using the Microsoft.PowerShell.SecretManagement module')]
        [Switch]$SecretVault,

        [Parameter(ParameterSetName = 'SecretVaultAuth',
            HelpMessage = 'Calls Connect-AzAccount before retrieving the secrets from the secret vault')]
        [Switch]$AzKeyVault,

        [Parameter(ParameterSetName = 'SecretVaultAuth',
            HelpMessage = 'Name of the secret vault, defaults to AzKV')]
        [String]$SecretVaultName = 'AzKV',

        [Parameter(ParameterSetName = 'SecretVaultAuth',
            HelpMessage = 'Name of the secret containing the client ID in the vault')]
        [String]$SecretVaultClientIDName = 'SophosCentral-Partner-ClientID',

        [Parameter(ParameterSetName = 'SecretVaultAuth',
            HelpMessage = 'Name of the secret containing the client secret in the vault')]
        [String]$SecretVaultClientSecretName = 'SophosCentral-Partner-ClientSecret'
    )

    if ($PSVersionTable.PSVersion.Major -lt 7) {
        Write-Warning 'Unsupported version of PowerShell detected'
    }

    if ($PsCmdlet.ParameterSetName -eq 'StdAuth') {
        if ($null -eq $ClientSecret) {
            $ClientSecret = Read-Host -AsSecureString -Prompt 'Client Secret:'
        }

        $loginUri = [System.Uri]::new('https://id.sophos.com/api/v2/oauth2/token')

        $body = @{
            grant_type    = 'client_credentials'
            client_id     = $ClientID
            client_secret = Unprotect-Secret -Secret $ClientSecret
            scope         = 'token'
        }
        try {
            $response = Invoke-WebRequest -Uri $loginUri -Body $body -ContentType 'application/x-www-form-urlencoded' -Method Post -UseBasicParsing
        } catch {
            throw "Error requesting access token: $($_)"
        }

        if ($response.Content) {
            $authDetails = $response.Content | ConvertFrom-Json
            $expiresAt = (Get-Date).AddSeconds($authDetails.expires_in - 60)

            if ($AccessTokenOnly -eq $true) {
                $SCRIPT:SophosCentral.access_token = $authDetails.access_token | ConvertTo-SecureString -AsPlainText -Force
                $SCRIPT:SophosCentral.expires_at = $expiresAt
            } else {
                $authDetails | Add-Member -MemberType NoteProperty -Name expires_at -Value $expiresAt
                $authDetails.access_token = $authDetails.access_token | ConvertTo-SecureString -AsPlainText -Force
                $SCRIPT:SophosCentral = $authDetails

                $tenantInfo = Get-SophosCentralTenantInfo
                $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name GlobalEndpoint -Value $tenantInfo.apiHosts.global
                $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name RegionEndpoint -Value $tenantInfo.apiHosts.dataRegion
                $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name TenantID -Value $tenantInfo.id
                $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name IDType -Value $tenantInfo.idType

                $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name client_id -Value $ClientID
                $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name client_secret -Value $ClientSecret
            }
        }
    } elseif ($PsCmdlet.ParameterSetName -eq 'SecretVaultAuth' -or $SecretVault) {
        #verify modules installed
        if ($AzKeyVault -eq $true) {
            $modules = 'Microsoft.PowerShell.SecretManagement', 'Az', 'Az.KeyVault'
        } else {
            $modules = 'Microsoft.PowerShell.SecretManagement'
        }
        foreach ($module in $modules) {
            if (-not(Get-Module $module -ListAvailable)) {
                throw "$module PowerShell Module is not installed"
            }
        }
        #verify secret vault exists
        try {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseDeclaredVarsMoreThanAssignments', '', Justification = 'Only used for checking vault exists')]
            $vault = Get-SecretVault -Name $SecretVaultName
        } catch {
            throw "$SecretVaultName is not registered as a Secret Vault in Microsoft.PowerShell.SecretManagement"
        }
        #connect to Azure if using Key Vault for the Secret Vault
        if ($AzKeyVault -eq $true) {
            try {
                #check whether already logged into Azure
                $context = Get-AzContext
                if ($null -eq $context.Account) {
                    Connect-AzAccount | Out-Null
                }

            } catch {
                throw 'Error connecting to Azure PowerShell'
            }
        }
        #get secrets from vault
        try {
            #try twice, as sometimes the call silently fails
            $clientID = Get-Secret $SecretVaultClientIDName -Vault $SecretVaultName -AsPlainText
            $clientSecret = Get-Secret -Name $SecretVaultClientSecretName -Vault $SecretVaultName

            $clientID = Get-Secret $SecretVaultClientIDName -Vault $SecretVaultName -AsPlainText
            $clientSecret = Get-Secret -Name $SecretVaultClientSecretName -Vault $SecretVaultName
        } catch {
            throw "Error retrieving secrets from Azure Key Vault: $_"
        }
        #connect to Sophos Central
        Connect-SophosCentral -ClientID $clientID -ClientSecret $clientSecret
    }
}
# ./SophosCentral/public/Connect-SophosCentralCustomerTenant.ps1
function Connect-SophosCentralCustomerTenant {
    <#
    .SYNOPSIS
        Connect to a Customer tenant (for Sophos partners/enterprise customers only)
    .DESCRIPTION
        Connect to a Customer tenant (for Sophos partners/enterprise customers only). You must connect with "Connect-SophosCentral" first using a partners/enterprise service principal

        To find the customer tenant ID, use the "Get-SophosCentralCustomerTenant" function.

        Enterprise customers must have the following set within the sub estates/tenants your connecting to - https://support.sophos.com/support/s/article/KB-000036994?language=en_US
        Partners should follow similar instructions in the customer tenants - this is enabled by default for trials/new accounts created from the Partner Dashboard
    .PARAMETER CustomerTenantID
        The Customers tenant ID
    .PARAMETER CustomerNameSearch
        Search the tenants you have access to by their name in Sophos Central, use "*" as a wildcard. For example, if you want to connect to "Contoso Legal" `
        you could enter "Contoso*" here.
    .PARAMETER PerformConnectionTest
        Setting this will perform a connection test to the tenant. The connection test currently works by doing a test call to the alerts API, as all Sophos Central tenants should have this feature enabled.
    .EXAMPLE
        Connect-SophosCentralCustomerTenant -CustomerTenantID "7d565595-e281-4128-9711-c97eb1d202c5"

        Connect to the tenant with an ID of "7d565595-e281-4128-9711-c97eb1d202c5"
    .EXAMPLE
        Connect-SophosCentralCustomerTenant -CustomerNameSearch "Contoso*"

        Connect to Contoso
    .EXAMPLE
        Connect-SophosCentralCustomerTenant -CustomerNameSearch "Contoso*" -PerformConnectionTest

        Connect to Contoso and perform connection test
    .EXAMPLE
        Connect-SophosCentralCustomerTenant -CustomerNameSearch "Contoso*"

        Connect to Contoso whilst refreshing the cache of tenants stored in memory
    .LINK
        https://developer.sophos.com/getting-started
    #>

    [CmdletBinding()]
    [Alias('Select-SophosCentralCustomerTenant', 'Select-SophosCentralEnterpriseTenant', 'Connect-SophosCentralEnterpriseTenant')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'ByID'
        )]
        [string]$CustomerTenantID,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'BySearchString'
        )]
        [string]$CustomerNameSearch,

        [switch]$SkipConnectionTest,

        [switch]$PerformConnectionTest,

        [switch]$ForceTenantRefresh
    )
    Test-SophosCentralConnected
    
    if (((Test-SophosPartner) -or (Test-SophosEnterprise)) -eq $false) {
        throw 'You are not currently logged in using a Sophos Central Partner/Enterprise Service Principal'
    } else {
        Write-Verbose 'currently logged in using a Sophos Central Partner/Enterprise Service Principal'
    }

    if ((-not($SCRIPT:SophosCentralCustomerTenants)) -or ($ForceTenantRefresh -eq $true)) {
        try {
            $SCRIPT:SophosCentralCustomerTenants = Get-SophosCentralCustomerTenant
        } catch {
            throw 'Unable to retrieve customer/enterprise tenants using Get-SophosCentralCustomerTenant'
        }
    }

    if (-not($CustomerTenantID)) {
        $tenantInfo = $SCRIPT:SophosCentralCustomerTenants | Where-Object {
            $_.Name -like $CustomerNameSearch
        }
        switch ($tenantInfo.count) {
            { $PSItem -eq 1 } { Write-Verbose "1 customer tenants returned: $($tenantInfo.Name)" }
            { $PSItem -gt 1 } { throw "$PSItem customer tenants returned: " + (($tenantInfo).name -join ';') }
            { $PSItem -lt 1 } { throw "$PSItem customer tenants returned" }
        }
    } else {
        $tenantInfo = $SCRIPT:SophosCentralCustomerTenants | Where-Object {
            $_.ID -eq $CustomerTenantID
        }
    }

    if ($null -ne $tenantInfo) {
        $SCRIPT:SophosCentral.RegionEndpoint = $tenantInfo.apiHost
        if ($SCRIPT:SophosCentral.CustomerTenantID) {
            $SCRIPT:SophosCentral.CustomerTenantID = $tenantInfo.id
        } else {
            $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name CustomerTenantID -Value $tenantInfo.id
        }
        if ($SCRIPT:SophosCentral.CustomerTenantName) {
            $SCRIPT:SophosCentral.CustomerTenantName = $tenantInfo.Name
        } else {
            $SCRIPT:SophosCentral | Add-Member -MemberType NoteProperty -Name CustomerTenantName -Value $tenantInfo.Name
        }

        if (($PerformConnectionTest -eq $true) -and ($SkipConnectionTest -eq $false)) {
            try {
                [Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseDeclaredVarsMoreThanAssignments', '', Justification = 'Used for checking permissions to the tenant')]
                $alertTest = Get-SophosCentralAlert
            } catch {
                throw "Unable to connect to the tenant, you may not have permissions to the tenant.`n`n $($_)"
            }
        }
        
    } else {
        throw 'Tenant does not exist'
    }
}
# ./SophosCentral/public/Get-SophosCentralAccessToken.ps1
function Get-SophosCentralAccessToken {
    <#
    .SYNOPSIS
        Get all access tokens for a tenant. Currently the only type is 'sophosLinuxSensor'
    .DESCRIPTION
        Get all access tokens for a tenant. Currently the only type is 'sophosLinuxSensor'
    .EXAMPLE
        Get-SophosCentralAccessToken
    .LINK
        https://developer.sophos.com/docs/accounts-v1/1/routes/access-tokens/get
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.GlobalEndpoint + '/accounts/v1/access-tokens')
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralAccountHealthCheck.ps1
function Get-SophosCentralAccountHealthCheck {
    <#
    .SYNOPSIS
        The Account Health Check API allows you to retrieve a health report for your Sophos Central account indicating whether you are making the best use of your Sophos security products.
    .DESCRIPTION
        The Account Health Check API allows you to retrieve a health report for your Sophos Central account indicating whether you are making the best use of your Sophos security products.
    .EXAMPLE
        Get-SophosCentralAccountHealthCheck
    .LINK
        https://developer.sophos.com/account-health-check
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/account-health-check/v1/health-check')
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralAdmin.ps1
function Get-SophosCentralAdmin {
    <#
    .SYNOPSIS
        Get admins listed in Sophos Central
    .DESCRIPTION
        Get admins listed in Sophos Central
    .EXAMPLE
        Get-SophosCentralAdmin
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/admins/get
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    $uriChild = '/common/v1/admins'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralAdminRole.ps1
function Get-SophosCentralAdminRole {
    <#
    .SYNOPSIS
        Get admin roles in Sophos Central
    .DESCRIPTION
        Get admin roles listed in Sophos Central
    .EXAMPLE
        Get-SophosCentralAdminRole
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/roles/get
    #>

    [CmdletBinding()]
    param (
        [ValidateSet('predefined', 'custom')]
        [string]$Type,

        [ValidateSet('user', 'service')]
        [string]$PrincipalType
    )
    Test-SophosCentralConnected
    
    $uriChild = '/common/v1/roles'
    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralAlert.ps1
function Get-SophosCentralAlert {
    <#
    .SYNOPSIS
        Get alerts listed in Sophos Central
    .DESCRIPTION
        Get alerts listed in Sophos Central
    .PARAMETER Product
        Alerts for a product. You can query by product types.
    .PARAMETER Category
        Alert category. You can query by different categories.
    .PARAMETER Severity
        Alerts for a specific severity level. You can query by severity levels.
    .EXAMPLE
        Get-SophosCentralAlert

        Get all active alerts in the tenant
    .EXAMPLE
        Get-SophosCentralAlert -Product 'server' -Severity 'high

        Get all alerts relating to Sophos Central for Server protection wih High severity
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/alerts/get
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/alerts/search/post
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralAlerts')]
    param (
        [Parameter(ParameterSetName = 'search')]
        [ValidateSet('other', 'endpoint', 'server', 'mobile', 'encryption', 'emailGateway', 'webGateway', 'phishThreat', 'wireless', 'iaas', 'firewall')]
        [string[]]$Product,

        [Parameter(ParameterSetName = 'search')]
        [ValidateSet('azure', 'adSync', 'applicationControl', 'appReputation', 'blockListed', 'connectivity', 'cwg', 'denc', 'downloadReputation', 'endpointFirewall', 'fenc', 'forensicSnapshot', 'general', 'iaas', 'iaasAzure', 'isolation', 'malware', 'mtr', 'mobiles', 'policy', 'protection', 'pua', 'runtimeDetections', 'security', 'smc', 'systemHealth', 'uav', 'uncategorized', 'updating', 'utm', 'virt', 'wireless', 'xgEmail')]
        [string[]]$Category,

        [Parameter(ParameterSetName = 'search')]
        [ValidateSet('low', 'medium', 'high')]
        [string[]]$Severity
    )
    Test-SophosCentralConnected
    
    if ($PsCmdlet.ParameterSetName -ne 'search') {
        $uriChild = '/common/v1/alerts'
        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
        Invoke-SophosCentralWebRequest -Uri $uri

    } elseif ($PsCmdlet.ParameterSetName -eq 'search') {
        $uriChild = '/common/v1/alerts/search'
        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)

        $searchParam = @{}
        if ($Product) { $searchParam.Add('product', $Product) }
        if ($Category) { $searchParam.Add('category', $Category) }
        if ($Severity) { $searchParam.Add('severity', $Severity) }

        Invoke-SophosCentralWebRequest -Uri $uri -Body $searchParam -Method Post
    }
}

# ./SophosCentral/public/Get-SophosCentralAllowedItem.ps1
function Get-SophosCentralAllowedItem {
    <#
    .SYNOPSIS
        Get Endpoint allowed Items
    .DESCRIPTION
        Get Endpoint allowed Items
    .EXAMPLE
        Get-SophosCentralAllowedItem
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/settings/allowed-items/get
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralAllowedItems')]
    param (
    )
    Test-SophosCentralConnected
    
    $uriChild = '/endpoint/v1/settings/allowed-items'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralBlockedItem.ps1
function Get-SophosCentralBlockedItem {
    <#
    .SYNOPSIS
        Get Endpoint blocked Items
    .DESCRIPTION
        Get Endpoint blocked Items
    .EXAMPLE
        Get-SophosCentralBlockedItem
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/settings/blocked-items/get
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralBlockedItems')]
    param (
    )
    Test-SophosCentralConnected
    
    $uriChild = '/endpoint/v1/settings/blocked-items'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralCustomerTenant.ps1
function Get-SophosCentralCustomerTenant {
    <#
    .SYNOPSIS
        List Sophos Central customer/enterprise tenants that can be connected too (for Sophos partners/enterprise customers only)
    .DESCRIPTION
        List Sophos Central customer/enterprise tenants that can be connected too (for Sophos partners/enterprise customers only)
    .EXAMPLE
        Get-SophosCentralCustomerTenant
    .LINK
        https://developer.sophos.com/docs/partner-v1/1/routes/tenants/get
    .LINK
        https://developer.sophos.com/getting-started
    .LINK
        https://developer.sophos.com/getting-started-organization
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralCustomerTenants', 'Get-SophosCentralEnterpriseTenant')]
    param (
    )
    Test-SophosCentralConnected
    
    if (((Test-SophosPartner) -or (Test-SophosEnterprise)) -eq $false) {
        throw 'You are not currently logged in using a Sophos Central Partner/Enterprise Service Principal'
    }

    try {
        $header = Get-SophosCentralAuthHeader -PartnerInitial
    } catch {
        throw $_
    }
    switch ($SCRIPT:SophosCentral.IDType) {
        'partner' {
            $uri = [System.Uri]::New('https://api.central.sophos.com/partner/v1/tenants?pageTotal=true')
        }
        'organization' {
            $uri = [System.Uri]::New('https://api.central.sophos.com/organization/v1/tenants?pageTotal=true')
        }
    }
    
    Invoke-SophosCentralWebRequest -Uri $uri -CustomHeader $header
}
# ./SophosCentral/public/Get-SophosCentralEndpoint.ps1
function Get-SophosCentralEndpoint {
    <#
    .SYNOPSIS
        Get Endpoints in Sophos Central (Workstations, Servers)
    .DESCRIPTION
        Get Endpoints in Sophos Central (Workstations, Servers)
    .PARAMETER HealthStatus
        Find endpoints by health status. The following values are allowed: bad, good, suspicious, unknown
    .PARAMETER Type
        Find endpoints by type. The following values are allowed: computer, server, securityVm
    .PARAMETER TamperProtectionEnabled
        Find endpoints by whether Tamper Protection is turned on.
    .PARAMETER LockdownStatus
        Find endpoints by lockdown status.
    .PARAMETER IsolationStatus
        Find endpoints by isolation status.
    .PARAMETER HostnameContains
        Find endpoints where the hostname contains the given string. Only the first 10 characters of the given string are matched.
    .PARAMETER IpAddresses
        Find endpoints by IP addresses.
    .PARAMETER MacAddresses
        Find endpoints by MAC Addresses. Can be in EUI-48 or EUI-64 format, case insensitive, colon, hyphen or dot separated, or with no separator e.g. 01:23:45:67:89:AB, 01-23-45-67-89-ab, 0123.4567.89ab, 0123456789ab, 01:23:45:67:89:ab:cd:ef.
    .PARAMETER LastSeenBefore
        Find endpoints last seen before this. Accepts either [datetime] or a string in the ISO 8601 Duration format (https://en.wikipedia.org/wiki/ISO_8601#Durations)
    .PARAMETER LastSeenAfter
        Find endpoints last seen after this. Accepts either [datetime] or a string in the ISO 8601 Duration format (https://en.wikipedia.org/wiki/ISO_8601#Durations)
    .PARAMETER ID
        Find endpoints with the specified IDs.
    .EXAMPLE
        Get-SophosCentralEndpoint

        List all endpoints in the tenant
    .EXAMPLE
        Get-SophosCentralEndpoint -HealthStatus 'bad'

        List all endpoints with a bad health status
    .EXAMPLE
        Get-SophosCentralEndpoint -TamperProtectionEnabled $false

        List all endpoints with tamper protection disabled
    .EXAMPLE
        Get-SophosCentralEndpoint -LastSeenBefore '-P90D'

        List all endpoints seen more than 90 day ago
    .EXAMPLE
        Get-SophosCentralEndpoint -LastSeenAfter '-P1D'

        List all endpoints seen in the last 1 day
    .EXAMPLE
        Get-SophosCentralEndpoint -LastSeenAfter (Get-Date).AddDays(-1)

        List all endpoints seen in the last 1 day
    .EXAMPLE
        Get-SophosCentralEndpoint -LastSeenAfter '-PT2H'

        List all endpoints seen in the last 2 hours
    .EXAMPLE
        Get-SophosCentralEndpoint -LastSeenAfter '-PT20M'

        List all endpoints seen in the last 20 minutes
    .EXAMPLE
        Get-SophosCentralEndpoint -LastSeenAfter '-P3DT4H5M0S'

        List all endpoints seen in the last 3 days 4 hours 5 minutes and 0 seconds
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/get
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralEndpoints')]
    param (
        [ValidateSet('bad', 'good', 'suspicious', 'unknown')]
        [string[]]$HealthStatus,

        [ValidateSet('computer', 'server', 'securityVm')]
        [string[]]$Type,

        [System.Boolean]$TamperProtectionEnabled,

        [ValidateSet('creatingWhitelist', 'installing', 'locked', 'notInstalled', 'registering', 'starting', 'stopping', 'unavailable', 'uninstalled', 'unlocked')]
        [string[]]$LockdownStatus,

        [ValidateSet('isolated', 'notIsolated')]
        [string]$IsolationStatus,

        [string]$HostnameContains,

        [string]$IpAddresses,

        [string]$MacAddresses,

        [ValidateScript({
                if ($_.GetType().Name -eq 'DateTime') {
                    return $true
                }
                else {
                    #match this duration format https://en.wikipedia.org/wiki/ISO_8601#Durations
                    $regex = '^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$'
                    if ($_ -match $regex) {
                        return $true
                    }
                    else {
                        throw "See 'Get-Help Get-SophosCentralEndpoint -Examples' for some examples"
                    }
                }
            })]
        $LastSeenBefore,

        [ValidateScript({
                if ($_.GetType().Name -eq 'DateTime') {
                    return $true
                }
                else {
                    #match this duration format https://en.wikipedia.org/wiki/ISO_8601#Durations
                    $regex = '^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$'
                    if ($_ -match $regex) {
                        return $true
                    }
                    else {
                        throw "See 'Get-Help Get-SophosCentralEndpoint -Examples' for some examples"
                    }
                }
            })]
        $LastSeenAfter,

        [ValidateScript({
                if ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID'
                }
                else {
                    return $true
                }
            })]
        [string[]]
        $ID
    )
    Test-SophosCentralConnected

    # ID is ids in query string, update PSBoundParameters to match
    if ($ID.count -gt 0) {
        $PsBoundParameters.Add('ids', $PsBoundParameters['ID'])
        $null = $PsBoundParameters.Remove('ID')
    }

    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/endpoints')
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters

    Invoke-SophosCentralWebRequest -Uri $uri
}

# ./SophosCentral/public/Get-SophosCentralEndpointGroup.ps1
function Get-SophosCentralEndpointGroup {
    <#
    .SYNOPSIS
        Get Endpoint groups in the directory
    .DESCRIPTION
        Get Endpoint groups in the directory
    .EXAMPLE
        Get-SophosCentralEndpointGroup
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoint-groups/get
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/endpoint-groups')
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralEndpointInstallerLink.ps1
function Get-SophosCentralEndpointInstallerLink {
    <#
    .SYNOPSIS
        Get all the endpoint installer links for a tenant.
    .DESCRIPTION
        Get all the endpoint installer links for a tenant.
    .EXAMPLE
        Get-SophosCentralEndpointInstallerLink
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/downloads/get
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    $uriChild = '/endpoint/v1/downloads'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralEndpointMigration.ps1
function Get-SophosCentralEndpointMigration {
    <#
    .SYNOPSIS
        Gets all migration jobs for the tenant.
    .DESCRIPTION
        Gets all migration jobs for the tenant.
    .EXAMPLE
        Get-SophosCentralEndpointMigration
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/migrations/get
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    Show-UntestedWarning
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/migrations')
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralEndpointMigrationStatus.ps1
function Get-SophosCentralEndpointMigrationStatus {
    <#
    .SYNOPSIS
        Gets the status of endpoints that are being migrated.
    .DESCRIPTION
        Gets the status of endpoints that are being migrated. This should be able to be run in either tenant.
    .EXAMPLE
        Get-SophosCentralEndpointMigrationStatus -MigrationID 'bbc35a7e-860b-43bc-b668-d48b57cb38ed'
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/migrations/%7BmigrationJobId%7D/endpoints/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$MigrationID
    )
    Test-SophosCentralConnected
    
    Show-UntestedWarning
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + "/endpoint/v1/migrations/$($MigrationID)/endpoints?pageTotal=true")
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralEndpointPolicy.ps1
function Get-SophosCentralEndpointPolicy {
    <#
    .SYNOPSIS
        Get Policies
    .DESCRIPTION
        Get Policies
    .EXAMPLE
        Get-SophosCentralEndpointPolicy -All

        Get all policies
    .EXAMPLE
        Get-SophosCentralEndpointPolicy -BasePolicy

        Get base policies
    .LINK
        https://developer.sophos.com/endpoint-policies#get-all-policies
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralEndpointPolicies')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseProcessBlockForPipelineCommand', 'All', Justification = 'We do not need to use the variable', Scope = 'Function')]
    param (
        [Parameter(Mandatory = $false,
            ParameterSetName = 'ID')]
        [Alias('ID')]
        [string]$PolicyId,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Base')]
        [switch]$BasePolicy,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'All')]
        [switch]$All
    )
    Test-SophosCentralConnected

    if ($BasePolicy) {
        $PolicyId = 'base'
    }

    $uriChild = '/endpoint/v1/policies'
    if ($All) {
        $uriChild = $uriChild + '?pageTotal=true'
    }
    elseif (-not([string]::IsNullOrEmpty($PolicyId))) {
        $uriChild = "$($uriChild)/$($PolicyId)"
    }
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}

# ./SophosCentral/public/Get-SophosCentralEndpointTamperProtection.ps1
function Get-SophosCentralEndpointTamperProtection {
    <#
    .SYNOPSIS
        Get Tamper Protection Status
    .DESCRIPTION
        Get Tamper Protection Status
    .PARAMETER EndpointID
        The ID of the Endpoint. Use Get-SophosCentralEndpoints to list them
    .EXAMPLE
        Get-SophosCentralEndpointTamperProtection -EndpointID '23a920fa-9a34-4869-bc3d-a1626e50f670'
    .EXAMPLE
        Get-SophosCentralEndpointTamperProtection -EndpointID (Get-SophosCentralEndpoints).ID
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/%7BendpointId%7D/tamper-protection/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID
    )
    begin {
        Test-SophosCentralConnected
    
        $uriChild = '/endpoint/v1/endpoints/{0}/tamper-protection'
        $uriString = $SCRIPT:SophosCentral.RegionEndpoint + $uriChild
    }
    process {
        foreach ($endpoint in $EndpointID) {
            $uri = [System.Uri]::New($uriString -f $endpoint)
            Invoke-SophosCentralWebRequest -Uri $uri
        }
    }
}
# ./SophosCentral/public/Get-SophosCentralEvent.ps1
function Get-SophosCentralEvent {
    <#
    .SYNOPSIS
        Get events within the last 24 hours.
    .DESCRIPTION
        Get events within the last 24 hours.
    .PARAMETER Limit
        The maximum number of items to return, min is 200, max is 1000.
    .PARAMETER FromDate
        The starting date from which alerts will be retrieved. Must be within last 24 hours.
    .PARAMETER ExcludeType
        List of types of events to be excluded.
    .EXAMPLE
        Get-SophosCentralEvent

        Get the most recent events
    .EXAMPLE
        Get-SophosCentralEvent -FromDate (Get-Date).AddMinutes(-15)

        Get events from the last 15 minutes
    .EXAMPLE
        Get-SophosCentralEvent -ExcludeType 'Event::Endpoint::UpdateSuccess', 'Event::Endpoint::Application::Detected'

        Get latest events excluding UpdateSuccess and Application Detected events
    .EXAMPLE
        Get-SophosCentralEvent -Limit 205

        Get 205 of the the most recent events
    .LINK
        https://developer.sophos.com/docs/siem-v1/1/routes/events/get
    #>

    [CmdletBinding(DefaultParameterSetName = 'nullParam')]
    param (
        [ValidateRange(200, 1000)]
        [int]$Limit = 200,

        [ValidateScript({
                if (($_ - [datetime]::Now).TotalHours -gt 24) {
                    throw 'Must be within 24 hours'
                }
                return $true
            })]
        [datetime]$FromDate,

        [string[]]$ExcludeType
    )

    Test-SophosCentralConnected

    # Convert datetime to unix timestamp in UTC
    if ($null -ne $FromDate) {
        $timestamp = Get-Date -Date $FromDate.ToUniversalTime() -UFormat '%s'
        if ($timestamp.IndexOf('.') -gt 0) {
            # Powershell v5 returns a value with .#### where pwsh 7 does not...
            $timestamp = $timestamp.Substring(0, $timestamp.IndexOf('.'))
        }
        $PsBoundParameters.Add('from_date', $timestamp)
        $null = $PsBoundParameters.Remove('FromDate')
    }

    # Transform into API name
    if ($ExcludeType.Count -gt 0) {
        $PsBoundParameters.Add('exclude_types', $PsBoundParameters['ExcludeType'])
        $null = $PsBoundParameters.Remove('ExcludeType')
    }

    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/siem/v1/events')
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters

    Invoke-SophosCentralWebRequest -Uri $uri
}

# ./SophosCentral/public/Get-SophosCentralFirewall.ps1
function Get-SophosCentralFirewall {
    <#
    .SYNOPSIS
        Get firewalls in Sophos Central
    .DESCRIPTION
        Get firewalls in Sophos Central
    .EXAMPLE
        Get-SophosCentralFirewall
    .PARAMETER GroupId
        Firewall group ID, or 'ungrouped'
    .PARAMETER Search
        Search
    .EXAMPLE
        Get-SophosCentralFirewall -GroupId 'ungrouped'
        List firewalls that aren't part of a firewall group.
    .EXAMPLE
        Get-SophosCentralFirewall -GroupId 'e25ec1f2-04f5-477e-a10d-71f2e039ebaf'
        List firewalls in the group e25ec1f2-04f5-477e-a10d-71f2e039ebaf
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewalls/get
    #>

    [CmdletBinding()]
    param (
        [ValidateScript({
                if ($_ -eq 'ungrouped') {
                    return $true
                } elseif ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID' 
                } else {
                    return $true
                }
            })]
        [string]$GroupId,
        [string]$Search
    )
    Test-SophosCentralConnected
    
    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewalls?pageTotal=true')
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralFirewallGroup.ps1
function Get-SophosCentralFirewallGroup {
    <#
    .SYNOPSIS
        Get firewall groups in Sophos Central
    .DESCRIPTION
        Get firewall groups in Sophos Central
    .EXAMPLE
        Get-SophosCentralFirewallGroup
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewall-groups/get
    #>

    [CmdletBinding()]
    param (
        [System.Boolean]$RecurseSubgroups,
        [string]$Search
    )
    Test-SophosCentralConnected
    
    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewall-groups')
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters

    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralFirewallUpdate.ps1
function Get-SophosCentralFirewallUpdate {
    <#
    .SYNOPSIS
        Get firmware updates available on a firewall
    .DESCRIPTION
        Get firmware updates available on a firewall
    .PARAMETER FirewallID
        The ID of the firewall. Use Get-SophosCentralFirewall to list them
    .EXAMPLE
        Get-SophosCentralFirewallUpdate -FirewallID "6d41e78e-0360-4de3-8669-bb7b797ee515"
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewalls/actions/firmware-upgrade-check/post
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$FirewallID
    )
    begin {
        Test-SophosCentralConnected
    
        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewalls/actions/firmware-upgrade-check')
        $body = @{
            firewalls = @()
        }
    }
    process {
        foreach ($firewall in $FirewallID) {
            $body['firewalls'] += $firewall
        }
        Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
    }
}
# ./SophosCentral/public/Get-SophosCentralLiveDiscoverQuery.ps1
function Get-SophosCentralLiveDiscoverQuery {
    <#
    .SYNOPSIS
        Get queries matching the given filters.
    .DESCRIPTION
        Get queries matching the given filters.
    .EXAMPLE
        Get-SophosCentralLiveDiscoverQuery
    .LINK
        https://developer.sophos.com/docs/live-discover-v1/1/routes/queries/get
    .LINK
        https://developer.sophos.com/docs/live-discover-v1/1/routes/queries/%7BqueryId%7D/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$categoryId,

        [Parameter(Mandatory = $false)]
        [string]$search,

        [Parameter(Mandatory = $false)]
        [array]$searchFields,        
        
        [Parameter(Mandatory = $false)]
        [ValidateSet('categories', 'code', 'createdAt', 'dataSource', 'description', 'id', 'name', 'performance', 'supportedOSes', 'template', 'type', 'variables')]
        [array]$Fields,

        [Parameter(Mandatory = $false)]
        [string]$QueryID
    )

    $uriChild = '/live-discover/v1/queries'
    if ($null -ne $QueryID) {
        $uriChild = $uriChild + '/' + $QueryID
    }
    $uriChild = $uriChild + '?pageTotal=true'
    
    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralLiveDiscoverQueryCategory.ps1
function Get-SophosCentralLiveDiscoverQueryCategory {
    <#
    .SYNOPSIS
        Get queries matching the given filters.
    .DESCRIPTION
        Get queries matching the given filters.
    .EXAMPLE
       Get-SophosCentralLiveDiscoverQueryCategory
    .LINK
        https://developer.sophos.com/docs/live-discover-v1/1/routes/queries/get
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralLiveDiscoverQueryCategories')]
    param (  
        [Parameter(Mandatory = $false)]
        [array]$Fields,

        [Parameter(Mandatory = $false)]
        [string]$CategoryID
    )

    $uriChild = '/live-discover/v1/queries/categories'
    if ($null -ne $categoryID) {
        $uriChild = $uriChild + '/' + $categoryID
    }
    $uriChild = $uriChild + '?pageTotal=true'
    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $PsBoundParameters
    
    Invoke-SophosCentralWebRequest -Uri $uri
    
}
# ./SophosCentral/public/Get-SophosCentralLiveDiscoverQueryRun.ps1
function Get-SophosCentralLiveDiscoverQueryRun {
    <#
    .SYNOPSIS
        Get the list of query runs, or return the results of one run
    .DESCRIPTION
        Get the list of query runs, or return the results of one run
    .EXAMPLE
        Get-SophosCentralLiveDiscoverQueryRun
    .EXAMPLE
        Get-SophosCentralLiveDiscoverQueryRun -RunID 'a9a5c6a3-5467-4bf3-87b0-ebdd4022056a'
    .EXAMPLE
        Get-SophosCentralLiveDiscoverQueryRun-RunID 'a9a5c6a3-5467-4bf3-87b0-ebdd4022056a' -Results
    .LINK
        https://developer.sophos.com/docs/live-discover-v1/1/routes/queries/runs/get
    .LINK
        https://developer.sophos.com/docs/live-discover-v1/1/routes/queries/runs/%7BrunId%7D/get
    .LINK
        https://developer.sophos.com/docs/live-discover-v1/1/routes/queries/runs/%7BrunId%7D/results/get
    #>

    [CmdletBinding(DefaultParameterSetName = 'nullParam')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'Results')]
        [Parameter(Mandatory = $false, ParameterSetName = 'Endpoints')]
        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [array]$Sort,

        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [Parameter(Mandatory = $false, ParameterSetName = 'RunID')]
        [array]$Fields,

        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [string]$QueryId,

        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [string]$CategoryId,

        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [ValidateSet('pending', 'started', 'finished')]
        [array]$Status,

        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [ValidateSet('notAvailable', 'succeeded', 'failed', 'timedOut')]
        [array]$Result,
        
        [Parameter(Mandatory = $false, ParameterSetName = 'Default')]
        [ValidateSet('service', 'user')]
        [array]$createdByPrincipalType,

        [Parameter(Mandatory = $true, ParameterSetName = 'Results')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Endpoints')]
        [Parameter(Mandatory = $true, ParameterSetName = 'RunID')]
        [string]$RunID,

        [Parameter(Mandatory = $true, ParameterSetName = 'Results')]
        [switch]$Results,

        [Parameter(Mandatory = $true, ParameterSetName = 'Endpoints')]
        [switch]$Endpoints
    )

    $uriChild = '/live-discover/v1/queries/runs'
    if ($null -ne $RunID) {
        $uriChild = $uriChild + '/' + $RunID
        if ($Results -eq $true) {
            $uriChild = $uriChild + '/results'
        }
        if ($endpoints -eq $true) {
            $uriChild = $uriChild + '/endpoints'
        }
    }
    $uriChild = $uriChild + '?pageTotal=true'
    
    $uriTemp = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    $filteredPsBoundParameters = $PsBoundParameters
    #filter out parameters not needed for Query
    $uri = New-UriWithQuery -Uri $uriTemp -OriginalPsBoundParameters $filteredPsBoundParameters -filteredParameters ('Results', 'Endpoints', 'RunID')
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralPartnerAdmin.ps1
function Get-SophosCentralPartnerAdmin {
    <#
    .SYNOPSIS
        List all partner admins.
    .DESCRIPTION
        List all partner admins.
    .EXAMPLE
        Get-SophosCentralPartnerAdmin
    .LINK
        https://developer.sophos.com/docs/partner-v1/1/routes/admins/get
    #>

    [CmdletBinding()]
    param (
    )
    Test-SophosCentralConnected
    
    if ((Test-SophosPartner) -eq $false) {
        throw 'You are not currently logged in using a Sophos Central Partner Service Principal'
    }

    try {
        $header = Get-SophosCentralAuthHeader -PartnerInitial
    } catch {
        throw $_
    }
    
    $uri = [System.Uri]::New('https://api.central.sophos.com/partner/v1/admins')
    Invoke-SophosCentralWebRequest -Uri $uri -CustomHeader $header
}
# ./SophosCentral/public/Get-SophosCentralPartnerBilling.ps1
function Get-SophosCentralPartnerBilling {
    <#
    .SYNOPSIS
        Get billing usage report
    .DESCRIPTION
        Get billing usage report
    .EXAMPLE
        Get-SophosCentralPartnerBilling -LastMonth
    .EXAMPLE
        Get-SophosCentralPartnerBilling -Month 5 -Year 2022
    .LINK
        https://developer.sophos.com/docs/partner-v1/1/routes/billing/usage/%7Byear%7D/%7Bmonth%7D/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0,
            Mandatory = $true,
            ParameterSetName = 'Custom')]
        [ValidateRange(1, 12)]
        [int]$Month,
        
        [Parameter(Position = 1,
            Mandatory = $true,
            ParameterSetName = 'Custom')]
        [ValidateRange(2000, 2050)]
        [int]$Year,

        [Parameter(Position = 2,
            ParameterSetName = 'LastMonth')]
        [switch]$LastMonth
    )
    Test-SophosCentralConnected
    
    if ((Test-SophosPartner) -eq $false) {
        throw 'You are not currently logged in using a Sophos Central Partner Service Principal'
    }

    if ($LastMonth) {
        $date = (Get-Date).AddMonths(-1)
        $Year = $date.Year
        $Month = $date.Month
    }

    try {
        $header = Get-SophosCentralAuthHeader -PartnerInitial
    } catch {
        throw $_
    }
    
    $uri = [System.Uri]::New("https://api.central.sophos.com/partner/v1/billing/usage/$year/$month")
    Invoke-SophosCentralWebRequest -Uri $uri -CustomHeader $header
}
# ./SophosCentral/public/Get-SophosCentralTenantInfo.ps1
function Get-SophosCentralTenantInfo {
    <#
    .SYNOPSIS
        Get information of the tenant the service principal resides in
    .DESCRIPTION
        Get information of the tenant the service principal resides in
    .EXAMPLE
        Get-SophosCentralTenantInfo
    .LINK
        https://developer.sophos.com/docs/whoami-v1/1/routes/get
    #>

    Test-SophosCentralConnected
    
    $uri = [System.Uri]::New('https://api.central.sophos.com/whoami/v1')
    $header = Get-SophosCentralAuthHeader -Initial
    Invoke-SophosCentralWebRequest -Uri $uri -CustomHeader $header
}
# ./SophosCentral/public/Get-SophosCentralUser.ps1
function Get-SophosCentralUser {
    <#
    .SYNOPSIS
        Get users listed in Sophos Central
    .DESCRIPTION
        Get users listed in Sophos Central
    .EXAMPLE
        Get-SophosCentralUser -ID 0eb3048c-ed91-4a01-8d39-923c6ca90868

        Find the user with the ID of 0eb3048c-ed91-4a01-8d39-923c6ca90868
    .EXAMPLE
        Get-SophosCentralUser -Search 'John'

        Search for users full name containing John
    .EXAMPLE
        Get-SophosCentralUser -Search 'Smith' -SearchField 'lastName'

        Search for users last name containing Smith
    .EXAMPLE
        Get-SophosCentralUser -SourceType 'activeDirectory'

        Search for users sourced from Active Directory
    .PARAMETER ID
        ID to match
    .PARAMETER Search
        Search for items that match the given terms
    .PARAMETER SearchFields
        Search only within the specified fields. When not specified, the default behavior is to search the full names of users only.
    .PARAMETER SourceType
        Types of sources of directory information. All users and groups created using this API have the source type custom. All users and groups synced from Active Directory or Azure Active Directory have the source type activeDirectory or azureActiveDirectory.
    .PARAMETER GroupId
        Search for users in a group that has this ID.
    .PARAMETER Domain
        List the items that match the given domain.
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/directory/users/get
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralUsers')]
    param (

        [ValidateScript({
                if ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID' 
                } else {
                    return $true
                }
            })]
        [string]$ID,
        
        [string]$Search,

        [ValidateSet('name', 'firstName', 'lastName', 'email', 'exchangeLogin')]
        [string]$SearchField,

        [ValidateSet('custom', 'activeDirectory', 'azureActiveDirectory')]
        [string]$SourceType,
        
        [ValidateScript({
                if ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID' 
                } else {
                    return $true
                }
            })]
        [string]$GroupID,
        
        [string]$Domain
    )
    Test-SophosCentralConnected

    $body = @{}
    if ($ID) { $body.Add('ids', $id) }
    if ($Search) { $body.Add('search', $Search) }
    if ($SearchField) { $body.Add('searchFields', $SearchField) }
    if ($SourceType) { $body.Add('sourceType', $SourceType) }
    if ($GroupID) { $body.Add('groupId', $GroupID) }
    if ($Domain) { $body.Add('domain', $Domain) }

    $uriChild = '/common/v1/directory/users?pageTotal=true'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)

    if ($body.Keys.Count -eq 0) {
        Invoke-SophosCentralWebRequest -Uri $uri
    } else {
        Invoke-SophosCentralWebRequest -Uri $uri -Body $body
    }
}
# ./SophosCentral/public/Get-SophosCentralUserGroup.ps1
function Get-SophosCentralUserGroup {
    <#
    .SYNOPSIS
        Get User Groups listed in Sophos Central
    .DESCRIPTION
        Get User Groups listed in Sophos Central
    .EXAMPLE
        Get-SophosCentralUserGroup
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/directory/user-groups/get
    #>

    [CmdletBinding()]
    [Alias('Get-SophosCentralUserGroups')]
    param (
    )
    Test-SophosCentralConnected
    
    $uriChild = '/common/v1/directory/user-groups'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralXDRQuery.ps1
function Get-SophosCentralXDRQuery {
    <#
    .SYNOPSIS
        Get the details of a query.
    .DESCRIPTION
        Get the details of a query.
    .EXAMPLE
        Get-SophosCentralXDRQuery
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/%7BqueryId%7D/get
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$QueryID
    )

    Show-UntestedWarning

    $uriChild = '/xdr-query/v1/queries'
    if ($null -ne $QueryID) {
        $uriChild = $uriChild + '/' + $QueryID
    }
    $uriChild = $uriChild + '?pageTotal=true'
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralXDRQueryCategory.ps1
function Get-SophosCentralXDRQueryCategory {
    <#
    .SYNOPSIS
        Fetch all categories, built-in as well as custom
    .DESCRIPTION
        Fetch all categories, built-in as well as custom
    .EXAMPLE
        Get-SophosCentralXDRQueryCategory
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/categories/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$CategoryID
    )
    
    Show-UntestedWarning

    $uriChild = '/xdr-query/v1/queries/categories'
    if ($null -ne $CategoryID) {
        $uriChild = $uriChild + '/' + $CategoryID
    }
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Get-SophosCentralXDRQueryRun.ps1
function Get-SophosCentralXDRQueryRun {
    <#
    .SYNOPSIS
        Get the list of query runs, or return the results of one run
    .DESCRIPTION
        Get the list of query runs, or return the results of one run
    .EXAMPLE
        Get-SophosCentralXDRQueryRun
    .EXAMPLE
        Get-SophosCentralXDRQueryRun -RunID 'a9a5c6a3-5467-4bf3-87b0-ebdd4022056a'
    .EXAMPLE
        Get-SophosCentralXDRQueryRun -RunID 'a9a5c6a3-5467-4bf3-87b0-ebdd4022056a' -Results
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/runs/get
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/runs/%7BrunId%7D/get
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/runs/%7BrunId%7D/results/get
    #>

    [CmdletBinding(DefaultParameterSetName='nullParam')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Run ID')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Results')]
        [string]$RunID,

        [Parameter(Mandatory = $true, ParameterSetName = 'Results')]
        [switch]$Results
    )
    
    $uriChild = '/xdr-query/v1/queries/runs'
    if ($null -ne $RunID) {
        $uriChild = $uriChild + '/' + $RunID
        if ($Results -eq $true) {
            $uriChild = $uriChild + '/results'
        }
    }
    $uriChild = $uriChild + '?pageTotal=true'
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
    Invoke-SophosCentralWebRequest -Uri $uri
}
# ./SophosCentral/public/Invoke-SophosCentralEndpointInstallerDownload.ps1
function Invoke-SophosCentralEndpointInstallerDownload {
    <#
    .SYNOPSIS
        Download the endpoint installer.
    .DESCRIPTION
        Download the endpoint installer.
    .EXAMPLE
        Invoke-SophosCentralEndpointInstallerDownload -RequestedProduct interceptX -Platform windows -FilePath 'C:\SophosSetup.exe'
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/downloads/get
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateSet('coreAgent', 'interceptX', 'xdr', 'endpointProtection', 'deviceEncryption', 'mtr', 'mtd', 'ztna')]
        [string]$RequestedProduct = 'interceptX',

        [Parameter(Mandatory = $false)]
        [ValidateSet('windows', 'linux', 'macOS')]
        [string]$Platform = 'windows',

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
                if ($_ | Test-Path) {
                    throw 'File already exists'
                }
                return $true
            })]
        [System.IO.FileInfo]$FilePath
    )
    Test-SophosCentralConnected
    
    $links = Get-SophosCentralEndpointInstallerLink
    foreach ($installers in $links.installers) {
        if (($installers.supportedProducts -contains $RequestedProduct) -and ($installers.platform -eq $Platform)) {
            Invoke-WebRequest -Uri $installers.downloadUrl -UseBasicParsing -OutFile $FilePath
        }
    }
    if (-not(Test-Path $FilePath)) {
        Write-Error 'Installer was not downloaded. Ensure you selected the correct product/platform'
    }
}
# ./SophosCentral/public/Invoke-SophosCentralEndpointMigrationReceive.ps1
function Invoke-SophosCentralEndpointMigrationReceive {
    <#
    .SYNOPSIS
        Create a migration job in the receiving tenant.
    .DESCRIPTION
        Create a migration job in the receiving tenant. This command will cause your session to connect to the destination tenant.

        This is Step 1 of a migration.
    .PARAMETER EndpointID
        The ID of the Endpoints. Use Get-SophosCentralEndpoints to list them
    .PARAMETER SourceTenantID
        The ID of the source tenant. Use Get-SophosCentralCustomerTenant to list them
    .PARAMETER DestinationTenantID
        The ID of the destination tenant. Use Get-SophosCentralCustomerTenant to list them
    .EXAMPLE
        $jobDetails = Invoke-SophosCentralEndpointMigrationReceive -EndpointID '6d41e78e-0360-4de3-8669-bb7b797ee515' -SourceTenantID 'c4ce7035-d6c1-44b9-9b11-b4a8b13e979b' -DestinationTenantID 'c924009e-1fac-4174-aace-8ccbe4296f95'
    .EXAMPLE
        $jobDetails = Invoke-SophosCentralEndpointMigrationReceive -EndpointID (Get-SophosCentralEndpoint).ID -SourceTenantID 'c4ce7035-d6c1-44b9-9b11-b4a8b13e979b' -DestinationTenantID 'c924009e-1fac-4174-aace-8ccbe4296f95'

        With this example you would connect to the source tenant first, so that '(Get-SophosCentralEndpoint).ID' runs in its context
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/migrations/post
    .LINK
        https://developer.sophos.com/endpoint-migrations
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID,

        [Parameter(Mandatory = $true)]
        [string]$SourceTenantID,

        [Parameter(Mandatory = $true)]
        [string]$DestinationTenantID,

        [switch]$Force
    )
    begin {
        Test-SophosCentralConnected
    }

    process {
        if ($DestinationTenantID -ne $SCRIPT:SophosCentral.CustomerTenantID) {
            try {
                Write-Warning "Connecting to $($DestinationTenantID), this connection will overwrite the tenant you were connected to previously ($($SCRIPT:SophosCentral.CustomerTenantName))"
                Connect-SophosCentralCustomerTenant -CustomerTenantID $DestinationTenantId
            } catch {
                throw 'Unable to connect to the destination tenant, check the ID is correct and you have the correct permissions to it'
            }
        }

        $body = @{
            'fromTenant' = $SourceTenantID
            'endpoints'  = $EndpointID
        }

        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/migrations')
        if ($Force -or $PSCmdlet.ShouldProcess('Create Receive Job', $DestinationTenantID )) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
        }
    }
    end { }
}
# ./SophosCentral/public/Invoke-SophosCentralEndpointMigrationSend.ps1
function Invoke-SophosCentralEndpointMigrationSend {
    <#
    .SYNOPSIS
        Start a migration job in the sending tenant.
    .DESCRIPTION
        Start a migration job in the sending tenant. This command will cause your session to connect to the source tenant.

        This is Step 2 of a migration.
    .PARAMETER EndpointID
        The ID of the Endpoints. Use Get-SophosCentralEndpoints to list them
    .PARAMETER SourceTenantID
        The ID of the source tenant. Use Get-SophosCentralCustomerTenant to list them
    .PARAMETER MigrationID
        id returned from Invoke-SophosCentralEndpointMigrationReceive
    .PARAMETER MigrationToken
        token returned from Invoke-SophosCentralEndpointMigrationReceive
    .EXAMPLE
        $jobDetails = Invoke-SophosCentralEndpointMigrationSend -EndpointID '6d41e78e-0360-4de3-8669-bb7b797ee515','245fe806-9ff8-4da1-b136-eea2a1d14812' -SourceTenantID 'c4ce7035-d6c1-44b9-9b11-b4a8b13e979b' -MigrationID 'bbc35a7e-860b-43bc-b668-d48b57cb38ed' -MigrationToken 'eyJ0b2tlbiI6ICJUaGlzIGlzIG9ubHkgYSBzYW1wbGUgdG9rZW4uIn0='
    .EXAMPLE
        $jobDetails = Invoke-SophosCentralEndpointMigrationSend -EndpointID (Get-SophosCentralEndpoint).ID -SourceTenantID 'c4ce7035-d6c1-44b9-9b11-b4a8b13e979b'

        With this example you would connect to the source tenant first, so that '(Get-SophosCentralEndpoint).ID' runs in its context
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/migrations/%7BmigrationJobId%7D/put
    .LINK
        https://developer.sophos.com/endpoint-migrations
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID,

        [Parameter(Mandatory = $true)]
        [string]$SourceTenantID,

        [Parameter(Mandatory = $true)]
        [string]$MigrationID,

        [Parameter(Mandatory = $true)]
        [string]$MigrationToken,

        [switch]$Force
    )
    begin {
        Test-SophosCentralConnected
    }

    process {
        if ($SourceTenantID -ne $SCRIPT:SophosCentral.CustomerTenantID) {
            try {
                Write-Warning "Connecting to $($SourceTenantID), this connection will overwrite the tenant you were connected to previously ($($SCRIPT:SophosCentral.CustomerTenantName))"
                Connect-SophosCentralCustomerTenant -CustomerTenantID $SourceTenantID
            } catch {
                throw 'Unable to connect to the source tenant, check the ID is correct and you have the correct permissions to it'
            }
        }

        $body = @{
            'token'     = $MigrationToken
            'endpoints' = $EndpointID
        }

        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/migrations/' + $MigrationID)
        if ($Force -or $PSCmdlet.ShouldProcess('Create Send Job', $SourceTenantID )) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Put -Body $body
        }
    }

    end { }
}
# ./SophosCentral/public/Invoke-SophosCentralEndpointScan.ps1
function Invoke-SophosCentralEndpointScan {
    <#
    .SYNOPSIS
        Trigger a scan on Endpoints in Sophos Central
    .DESCRIPTION
        Trigger a scan on Endpoints in Sophos Central
    .PARAMETER EndpointID
        The ID of the Endpoint. Use Get-SophosCentralEndpoints to list them
    .EXAMPLE
        Invoke-SophosCentralEndpointScan -EndpointID "6d41e78e-0360-4de3-8669-bb7b797ee515"
    .EXAMPLE
        Invoke-SophosCentralEndpointScan -EndpointID (Get-SophosCentralEndpoint).ID
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/%7BendpointId%7D/scans/post
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID
    )
    begin {
        Test-SophosCentralConnected
    
        $uriChild = '/endpoint/v1/endpoints/{0}/scans'
        $uriString = $SCRIPT:SophosCentral.RegionEndpoint + $uriChild
    }
    process {
        foreach ($endpoint in $EndpointID) {
            $uri = [System.Uri]::New($uriString -f $endpoint)
            Invoke-SophosCentralWebRequest -Uri $uri -Method Post
        }
    }
}
# ./SophosCentral/public/Invoke-SophosCentralEndpointUpdate.ps1
function Invoke-SophosCentralEndpointUpdate {
    <#
    .SYNOPSIS
        Trigger an update on an Endpoint in Sophos Central
    .DESCRIPTION
        Trigger an update on an Endpoint in Sophos Central
    .PARAMETER EndpointID
        The ID of the Endpoint. Use Get-SophosCentralEndpoints to list them
    .EXAMPLE
        Invoke-SophosCentralEndpointUpdate -EndpointID "6d41e78e-0360-4de3-8669-bb7b797ee515"
    .EXAMPLE
        Invoke-SophosCentralEndpointUpdate -EndpointID (Get-SophosCentralEndpoint).ID
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/%7BendpointId%7D/update-checks/post
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID
    )
    begin {
        Test-SophosCentralConnected
    
        $uriChild = '/endpoint/v1/endpoints/{0}/update-checks'
        $uriString = $SCRIPT:SophosCentral.RegionEndpoint + $uriChild
    }
    process {
        foreach ($endpoint in $EndpointID) {
            $uri = [System.Uri]::New($uriString -f $endpoint)
            Invoke-SophosCentralWebRequest -Uri $uri -Method Post
        }
    }
}
# ./SophosCentral/public/Invoke-SophosCentralFirewallApproval.ps1
function Invoke-SophosCentralFirewallApproval {
    <#
    .SYNOPSIS
        Approve management of a firewall
    .DESCRIPTION
        Approve management of a firewall
    .PARAMETER FirewallID
        The ID of the firewall. Use Get-SophosCentralFirewall to list them
    .EXAMPLE
        Invoke-SophosCentralFirewallApproval -FirewallID "6d41e78e-0360-4de3-8669-bb7b797ee515"
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewalls/%7BfirewallId%7D/action/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$FirewallID,

        [switch]$Force
    )
    begin {
        Test-SophosCentralConnected
    
        $uriTemp = $SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewalls/{0}/action '
        $body = @{
            action = 'approveManagement'
        }
    }
    process {
        foreach ($firewall in $FirewallID) {
            $uri = [System.Uri]::New($uriTemp -f $firewall)
            if ($Force -or $PSCmdlet.ShouldProcess('Approve', $firewall)) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
            }
        }
        
    }
}
# ./SophosCentral/public/Invoke-SophosCentralFirewallUpdate.ps1
function Invoke-SophosCentralFirewallUpdate {
    <#
    .SYNOPSIS
        Trigger an update on an firewall in Sophos Central
    .DESCRIPTION
        Trigger an update on an firewall in Sophos Central
    .PARAMETER FirewallID
        The ID of the firewall. Use Get-SophosCentralFirewall to list them
    .PARAMETER UpgradeToVersion
        The version to upgrade to. Check available firmware updates using Get-SophosCentralFirewallUpdate
    .PARAMETER UpgradeAt
        The time to perform the update. If not specified it'll queue the update immediately
    .EXAMPLE
        Invoke-SophosCentralFirewallUpdate -FirewallID "6d41e78e-0360-4de3-8669-bb7b797ee515" -UpgradeToVersion 'some-version'
    .EXAMPLE
        Invoke-SophosCentralFirewallUpdate -FirewallID "6d41e78e-0360-4de3-8669-bb7b797ee515" -UpgradeToVersion 'some-version' -UpgradeAt (Get-Date).AddHours(8)
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewalls/actions/firmware-upgrade/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$FirewallID,

        [string]$UpgradeToVersion,

        [datetime]$UpgradeAt,

        [switch]$Force
    )
    begin {
        Test-SophosCentralConnected

        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewalls/actions/firmware-upgrade')
        $body = @{
            firewalls = @()
        }
    }
    process {
        foreach ($firewall in $FirewallID) {
            $firewallHash = @{
                id               = $firewall
                upgradeToVersion = $UpgradeToVersion
            }
            if ($UpgradeAt) {
                $UpgradeAt = $UpgradeAt.ToUniversalTime().ToString('u').Replace(' ', 'T')
                $firewallHash.Add('upgradeAt', $UpgradeAt)
            }
            $body['firewalls'] += $firewallHash
        }
        if ($Force -or $PSCmdlet.ShouldProcess('Update', ($FirewallID -join ', '))) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
        }
    }
}

# ./SophosCentral/public/Invoke-SophosCentralLiveDiscoverQueryRun.ps1
function Invoke-SophosCentralLiveDiscoverQueryRun {
    <#
    .SYNOPSIS
        Run a saved EDR query or an ad hoc query on remote endpoints.
    .DESCRIPTION
        Run a saved EDR query or an ad hoc query on remote endpoints.

    .PARAMETER CustomBody
        The query to run as a hashtable, see this for query options - https://developer.sophos.com/docs/live-discover-query-v1/1/routes/queries/runs/post
        The values in the example bodies below may not be correct (such as the variables sub hash tables), but the structure of the hashtable should be correct

    .PARAMETER categoryId
        string (uuid)
        Query category ID.

    .PARAMETER queryId
        string (uuid)
        Saved query ID.

    .PARAMETER queryName
        string
        Name for the ad hoc query. 1 ≤ length ≤ 300.
    .PARAMETER query
        string
        SQL statement for the query. This can contain replacement variables wrapped in $$ (double dollar sign) delimiters. 15 ≤ length ≤ 50000

    .PARAMETER variables
        Values of variables to be replaced in the template SQL. Array of hashes each contains the following keys:
            name Variable name
            dataType Data types supported for EDR Data Lake queries.
                        The following values are allowed:
                            double, integer, text, dateTime, boolean
            value String value of the variable.
            pivotType The meaning of an input parameter of a query.
                        The following values are allowed:
                            deviceId, deviceName, sophosPid, ipAddress, username, sha256, filePath, registryKey, url

     Filter parameters - up to 5 may be specified - if no filter parameter are specified the matchEndpoints=all is added

    .PARAMETER healthStatus
        array
        Find endpoints by health status.

    .PARAMETER type
        array
        Find endpoints by type.

    .PARAMETER tamperProtectionEnabled
        boolean
        Find endpoints by whether Tamper Protection is turned on.

    .PARAMETER lockdownStatus
        array
        Find endpoints by lockdown status.

    .PARAMETER ids
        array
        Find endpoints with the specified IDs. Must contain from 1 to 1000 items.

    .PARAMETER lastSeenBefore
        string
        Find endpoints that were last seen before the given date and time (UTC) or a duration relative to the current date and time (exclusive).

    .PARAMETER lastSeenAfter
        string
        Find endpoints that were last seen after the given date and time (UTC) or a duration relative to the current date and time (inclusive).

    .PARAMETER hostnameContains
        string
        Find endpoints where the hostname contains the given string.

    .PARAMETER associatedPersonContains
        string
        Find endpoints where the name of the person associated with the endpoint contains the given string.

    .PARAMETER groupNameContains
        string
        Find endpoints where the name of the group the endpoint is in contains the given string.

    .PARAMETER os
        array
        Matches endpoints with any of the supplied operating system versions.

    .PARAMETER ipAddresses
        array
        Find endpoints by IP addresses.

    .PARAMETER search
        string
        Term to search for in the specified search fields.

    .PARAMETER searchFields
        array
        List of search fields for finding the given search term. Defaults to all applicable fields.

    .EXAMPLE
        Full custom body:

        $body = @{
            'matchEndpoints' = @{
                'filters' = @(
                    @{
                        'hostnameContains' = '17'
                    }
                )
            }
            'adHocQuery' = @{
                'template' = 'select * from file WHERE path = ''c:\windows\system32\drivers\etc\hosts'''
                'name' = 'HostFile'
            }
        }
        $query = Invoke-SophosCentralLiveDiscoverQueryRun -customBody $body
    .EXAMPLE
        Custom query with filters:
        Invoke-SophosCentralLiveDiscoverQueryRun -query "select * from file WHERE path = 'c:\windows\system32\drivers\etc\hosts'" -endpointFilters (@{hostnameContains="17"},@{type=@("computer")})
    .EXAMPLE
        Invoke-SophosCentralLiveDiscoverQueryRun -query "select * from file WHERE path = 'c:\windows\system32\drivers\etc\hosts'" -ipAddresses ("192.168.0.1") -hostnameContains "cthulu"
    .EXAMPLE
        Custom query with variables:
        Invoke-SophosCentralLiveDiscoverQueryRun -query 'select * from file WHERE path = $$path$$' -ipAddresses ("192.168.0.1") -variables (@{name='path';dataType='text';value='c:\windows\system32\drivers\etc\host'})
    .EXAMPLE
        Canned query with variables and filter:
        Variables are an array of hashes!:
        Invoke-SophosCentralLiveDiscoverQueryRun -queryID c2942032-fecc-4b04-b3e8-f0485bc3f40a -variables @(
            @{name='start_time';dataType='dateTime';value=(get-date).addDays(-5)},
            @{name='ip_address';dataType='text';value='8.8.8.8'},
            @{name='end_time';dataType='dateTime';value=get-date}) -ipAddresses ("192.168.0.1")

    .LINK
        https://developer.sophos.com/docs/Live-Discover-query-v1/1/routes/queries/runs/post
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'CustomBody')]
        [hashtable]$customBody,

        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [string]$categoryId,

        [Parameter(Mandatory = $true, ParameterSetName = 'CannedQuery')]
        [string]$queryId,

        [Parameter(Mandatory = $true, ParameterSetName = 'CustomQuery')]
        [string]$query,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [string]$queryName = 'AdHoc',

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [ValidateSet('good', 'suspicious', 'bad', 'unknown')]
        [array]$healthStatus,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [ValidateSet('computer', 'server', 'securityVm')]
        [array]$type,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [Boolean]$tamperProtectionEnabled,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [ValidateSet('creatingWhitelist', 'installing', 'locked', 'notInstalled', 'registering', 'starting', 'stopping', 'unavailable', 'uninstalled', 'unlocked')]
        [array]$lockdownStatus,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [array]$ids,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [ValidateScript({
                if ($_.GetType().Name -eq 'DateTime') {
                    return $true
                } else {
                    #match this duration format https://en.wikipedia.org/wiki/ISO_8601#Durations
                    $regex = '^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$'
                    if ($_ -match $regex) {
                        return $true
                    } else {
                        try {
                            $_ | Get-Date -ErrorAction stop
                        } catch {
                            throw 'lastSeenBefore should be [datetime] or ISO_8601 or something get-date recognises'
                        }

                    }
                }
            })]
        [string]$lastSeenBefore,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [ValidateScript({
                if ($_.GetType().Name -eq 'DateTime') {
                    return $true
                } else {
                    #match this duration format https://en.wikipedia.org/wiki/ISO_8601#Durations
                    $regex = '^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$'
                    if ($_ -match $regex) {
                        return $true
                    } else {
                        try {
                            $_ | Get-Date -ErrorAction stop
                        } catch {
                            throw 'lastSeen After should be [datetime] or ISO_8601 or something get-date recognises'
                        }

                    }
                }
            })]
        [string]$lastSeenAfter,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [string]$hostnameContains,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [string]$associatedPersonContains,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [string]$groupNameContains,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [array]$os,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [array]$ipAddresses,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [string]$search,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [ValidateSet('hostname', 'groupName', 'associatedPersonName', 'ipAddresses', 'osName')]
        [array]$searchFields,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'CannedQuery')]
        [array]$variables


    )
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/live-discover/v1/queries/runs')
    if ($PSCmdlet.ParameterSetName -eq 'CustomBody') {
        Write-Verbose 'Custom Body'
        $body = $customBody
    } else {
        $body = @{}
        $endpointFilters = @{}
        if ($healthStatus) {
            $endpointFilters.healthStatus = $healthStatus
        }
        if ($type) {
            $endpointFilters.type = $type
        }

        if ($tamperProtectionEnabled) {
            $endpointFilters.tamperProtectionEnabled = $tamperProtectionEnabled
        }
        if ($lockdownStatus) {
            $endpointFilters.lockdownStatus = $lockdownStatus
        }

        if ($ids) {
            $endpointFilters.ids = $ids
        }
        if ($lastSeenBefore) {
            try {
                $lastSeenBeforeTemp = $lastSeenBefore | Get-Date -AsUTC -Format 'yyyy-MM-dd"t"hh:mm:ssZ' -ErrorAction Stop
            } catch {
                $lastSeenBeforeTemp = $lastSeenBefore
            }
            $endpointFilters.lastSeenBefore = $lastSeenBeforeTemp
        }

        if ($lastSeenAfter) {
            try {
                $lastSeenAfterTemp = $lastSeenAfter | Get-Date -AsUTC -Format 'yyyy-MM-dd"t"hh:mm:ssZ' -ErrorAction Stop
            } catch {
                $lastSeenAfterTemp = $lastSeenAfter
            }
            $endpointFilters.lastSeenAfter = $lastSeenAfterTemp
        }

        if ($hostnameContains) {
            $endpointFilters.hostnameContains = $hostnameContains
        }

        if ($associatedPersonContains) {
            $endpointFilters.associatedPersonContains = $associatedPersonContains
        }

        if ($groupNameContains) {
            $endpointFilters.groupNameContains = $groupNameContains
        }

        if ($os) {
            $endpointFilters.os = $os
        }

        if ($ipAddresses) {
            $endpointFilters.ipAddresses = $ipAddresses
        }

        if ($search) {
            $endpointFilters.search = $search
        }

        if ($searchFields) {
            $endpointFilters.searchFields = $searchFields
        }

        if ($endpointFilters) {
            $body.matchEndpoints = @{
                'filters' = @($endpointFilters)
            }
        } else {
            $body.matchEndpoints = @{
                'all' = 'true'
            }
        }
        # force times into format acceptable to Sophos
        if ($variables) {
            foreach ($var in $variables) {
                if ($var.dataType -eq 'datetime') {
                    try {
                        $var.value = $var.value | Get-Date -AsUTC -Format 'yyyy-MM-dd"t"hh:mm:ssZ' -ErrorAction Stop
                    } catch {
                        throw "Invalid dateTime variable format for variable $($var.name)"
                    }
                }
            }
            $body.variables = $variables
        }
        if ($PSCmdlet.ParameterSetName -eq 'CustomQuery') {
            Write-Verbose 'Custom Query'
            $body.'adHocQuery' = @{
                'template' = $query
                'name'     = $queryName
            }
        } else {
            Write-Verbose 'Canned Query'
            $body.savedQuery = @{
                'queryId'    = $queryId
                'categoryId' = $categoryId
            }
        }
    }
    Write-Verbose ($body | ConvertTo-Json -Depth 5)
    Write-Verbose $uri
    Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
}

# ./SophosCentral/public/Invoke-SophosCentralXDRQueryRun.ps1
function Invoke-SophosCentralXDRQueryRun {
    <#
    .SYNOPSIS
        Run a query against the Sophos Data Lake.
    .DESCRIPTION
        Run a query against the Sophos Data Lake.

        The values in the example bodies below may not be correct (such as the variables sub hashtables), but the structure of the hashtable should be correct
    .PARAMETER CustomBody
        The query to run as a hashtable, see this for query options - https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/runs/post
    
    .PARAMETER categoryId
        Query category ID.

    .PARAMETER queryId (required)
        Saved query ID.

    .PARAMETER adHocQuery
        Ad hoc query to run. Required if a saved query isn't supplied.

    .PARAMETER name
        Name for the ad hoc query.

    .PARAMETER template (required)
        SQL statement for the query. This can contain replacement variables wrapped in $$ (double dollar sign) delimiters.

    .PARAMETER variables
        Values of variables to be replaced in the template SQL. Array of hashes each containg the following keys:
            name Variable name
            dataType Data types supported for EDR Data Lake queries.
                        The following values are allowed:
                            double, integer, text, dateTime, boolean
            value String value of the variable.
            pivotType The meaning of an input parameter of a query.
                        The following values are allowed:
                            deviceId, deviceName, sophosPid, ipAddress, username, sha256, filePath, registryKey, url

    .PARAMETER ids
        array
        Find endpoints with the specified IDs. Must contain from 1 to 1000 items. If not specfied then all endpoints are queried
    .PARAMETER from
        Start of time range that is applied when running the query (inclusive). It can be in ISO duration format, full UTC timestamp or date only.
    .PARAMETER to
        End of time range that is applied when running the query (inclusive). It can be in ISO duration format, full UTC timestamp or date only.
    .EXAMPLE
        $body = @{
            'adHocQuery' = @{
                'template' = 'select * from \"xdr_data\" limit 10'
                'name' = 'test search'
            }
            'from' = '2022-01-01T12:02:01.000Z'
            'to' = '2022-01-21T12:02:01.700Z'
        }
        $query = Invoke-SophosCentralXDRQueryRun -CustomBody $body
    .EXAMPLE
        $body = @{
            'adHocQuery' = @{
                'template' = 'select * from \"xdr_data\" limit 10'
                'name' = 'test search'
            }
            'from' = '2022-01-01T12:02:01.000Z'
            'to' = '2022-01-21T12:02:01.700Z'
            'variables' = @{
                    'name' = 'var1'
                    'dataType' = 'text'
                    'value' = 'asdfwsdfsdf'
                    'pivotType' = 'deviceId'
                }, @{
                    'name' = 'var2'
                    'dataType' = 'double'
                    'value' = 'asdfwsdfsdf'
                    'pivotType' = 'sha256'
                }
            'matchEndpoints' = @{
                'filters' = @(
                    @{
                        'ids' = @(
                            '7076e453-662f-40b9-bac6-5589691bd6de',
                            '7edf66a6-325f-40a3-bcb6-3b63ecbcba74'
                        )
                    }
                )
            }
        }
        $query = Invoke-SophosCentralXDRQueryRun -CustomBody $body
    .EXAMPLE
        $query = Invoke-SophosCentralXDRQueryRun -Query "SELECT name, meta_hostname FROM xdr_data WHERE query_name = 'windows_startup_items' AND name = 'someSVC'" -From (Get-Date).AddDays(-7) -to get-date
    .EXAMPLE
        $query = Invoke-SophosCentralXDRQueryRun -Query "SELECT name, meta_hostname FROM xdr_data WHERE query_name = 'windows_startup_items' AND name = 'imDmsSvc'" -From (Get-Date).AddDays(-7) -to get-date
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/runs/post
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'SavedQuery')]
        [string]$categoryId,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'SavedQuery')]
        [string]$queryId,

        [Parameter(Mandatory = $true, ParameterSetName = 'CustomBody')]
        [hashtable]$customBody,

        [Parameter(Mandatory = $true, ParameterSetName = 'CustomQuery')]
        [string]$Query,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SavedQuery')]
        [ValidateScript({
                if ($_.GetType().Name -eq 'DateTime') {
                    return $true
                } else {
                    #match this duration format https://en.wikipedia.org/wiki/ISO_8601#Durations
                    $regex = '^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$'
                    if ($_ -match $regex) {
                        return $true
                    } else {
                        throw 'Invaid From time - should be either a [datetime] or a ISO_8601 duration'
                    }
                }
            })]
        $From,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SavedQuery')]
        [ValidateScript({
                if ($_.GetType().Name -eq 'DateTime') {
                    return $true
                } else {
                    #match this duration format https://en.wikipedia.org/wiki/ISO_8601#Durations
                    $regex = '^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$'
                    if ($_ -match $regex) {
                        return $true
                    } else {
                        throw 'Invaid To time - should be either a [datetime] or a ISO_8601 duration'
                    }
                }
            })]
        $To,

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [string]$queryName = 'AdHoc',

        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SavedQuery')]
        [array]$ids,
        
        [Parameter(Mandatory = $false, ParameterSetName = 'CustomQuery')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SavedQuery')]
        [array]$variables
    )
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/xdr-query/v1/queries/runs')
    if ($PSCmdlet.ParameterSetName -eq 'CustomBody') {
        Write-Verbose 'Custom Body'
        $body = $customBody
    } else {
        $body = @{}
        # force times into format acceptable to Sophos
        if ($variables) {
            foreach ($var in $variables) {
                if ($var.dataType -eq 'datetime') {
                    try {
                        $var.value = $var.value | Get-Date -AsUTC -Format 'yyyy-MM-dd"t"hh:mm:ssZ' -ErrorAction Stop
                    } catch {    
                        throw "Invalid dateTime variable format for variable $($var.name)"
                    }
                }
            }
        }
        $body.variables = $variables
        if ($From) {
            if ($From.GetType().name -eq 'DateTime') {
                $body.from = $From | Get-Date -AsUTC -Format 'yyyy-MM-dd"t"hh:mm:ssZ'
            } else {
                $body.from = $From
            }
        }
        if ($To) {
            if ($To.GetType().name -eq 'DateTime') {
                $body.to = $To | Get-Date -AsUTC -Format 'yyyy-MM-dd"t"hh:mm:ssZ'
            } else {
                $body.to = $To
            }
        }
        if ($ids) {
            $endpointFilters += @{ids = $ids }
        }   
        if ($endpointFilters) {
            $body.matchEndpoints = @{
                'filters' = @(
                    @{
                        'ids' = $ids
                    }
                )
            }
        }
        if ($PSCmdlet.ParameterSetName -eq 'CustomQuery') {
            Write-Verbose 'Custom Query'
            $body.adHocQuery = @{
                'template' = $query
                'name'     = $queryName
            }
        } else {     
            Write-Verbose 'Canned Query'
            $body.savedQuery = @{
                'queryId'    = $queryId
                'categoryId' = $categoryId
            }
            if ($variables) {
                $body.variables = $variables
            }
        }
    }
    Write-Verbose ($body | ConvertTo-Json -Depth 5)
    Write-Verbose $uri
    Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $Body
}
# ./SophosCentral/public/New-SophosCentralAccessToken.ps1
function New-SophosCentralAccessToken {
    <#
    .SYNOPSIS
        Create access token for a tenant. Currently the only type is 'sophosLinuxSensor'
    .DESCRIPTION
        Create access token for a tenant. Currently the only type is 'sophosLinuxSensor'
    .EXAMPLE
        New-SophosCentralAccessToken
    .LINK
        https://developer.sophos.com/docs/accounts-v1/1/routes/access-tokens/post
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Label,

        [Parameter(Mandatory = $true)]
        [ValidateSet('sophosLinuxSensor')]
        [string]$Type,

        [datetime]$ExipresAt
    )
    Test-SophosCentralConnected
    
    $body = @{
        label = $Label
        type  = $Type
    }
    if ($ExipresAt) { $body.Add('expiresAt', $ExipresAt) }
    
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.GlobalEndpoint + '/accounts/v1/access-tokens')
    Invoke-SophosCentralWebRequest -Uri $uri -Body $body -Method Post
}
# ./SophosCentral/public/New-SophosCentralAdmin.ps1
function New-SophosCentralAdmin {
    <#
    .SYNOPSIS
        Create a tenant admin from a directory user.
    .DESCRIPTION
        Create a tenant admin from a directory user.
    .PARAMETER UserID
        ID of an existing user to add admin roles to.
    .PARAMETER RoleID
        ID of roles to assign to the user
    .EXAMPLE
        New-SophosCentralAdmin -UserID 'd5a81643-34db-4c44-a942-d83207ca402c' -RoleID 'fd18b044-a832-4b1a-a7a3-85e663366303'
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/admins/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({
                if ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID' 
                } else {
                    return $true
                }
                
            })]
        [string]$UserID,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
                foreach ($role in $_) {
                    if ($false -eq [System.Guid]::TryParse($role, $([ref][guid]::Empty))) {
                        throw 'Not a valid GUID' 
                    } else {
                        return $true
                    }
                }
            })]
        [string[]]$RoleID,

        [switch]$Force
    )
    Test-SophosCentralConnected
    
    $uriChild = '/common/v1/admins'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)

    $body = @{
        userId          = $UserID
        roleAssignments = @()
    }
    foreach ($role in $RoleID) {
        $roleHash = @{
            roleId = $role
        }
        $body['roleAssignments'] += $roleHash
    }

    if ($Force -or $PSCmdlet.ShouldProcess('Assign admin role', ($UserID))) {
        Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
    }
}
# ./SophosCentral/public/New-SophosCentralCustomerTenant.ps1
function New-SophosCentralCustomerTenant {
    <#
    .SYNOPSIS
        Create a new tenant.
    .DESCRIPTION
        Create a new tenant.
    .PARAMETER Name
        Name of the tenant
    .EXAMPLE
        New-SophosCentralCustomerTenant -Name 'Test User' -DataGeography AU -BillingType 'trial' -FirstName 'John' -LastName 'Smith' -Email 'test@contoso.com' -Phone '+612000000' -Address1 '31 Bligh St' -City 'Sydney' -CountryCode 'AU' -PostalCode '2000' -State 'New South Whales'
    .LINK
        https://developer.sophos.com/docs/partner-v1/1/routes/tenants/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [ValidateSet('US', 'IE', 'DE', 'CA', 'AU', 'JP')]
        [string]$DataGeography,

        [Parameter(Mandatory = $true)]
        [ValidateSet('trial', 'usage')]
        [string]$BillingType,
        
        [Parameter(Mandatory = $true)]
        [string]$FirstName,
        
        [Parameter(Mandatory = $true)]
        [string]$LastName,
        
        [Parameter(Mandatory = $true)]
        [string]$Email,
        
        [Parameter(Mandatory = $true)]
        [string]$Phone,
        
        [Parameter(Mandatory = $true)]
        [string]$Address1,
        
        [string]$Address2,
        
        [string]$Address3,
        
        [Parameter(Mandatory = $true)]
        [string]$City,
        
        [string]$State,
        
        [Parameter(Mandatory = $true)]
        [string]$CountryCode,
        
        [Parameter(Mandatory = $true)]
        [string]$PostalCode,
        
        [switch]$Force
    )
    Test-SophosCentralConnected
    
    if ((Test-SophosPartner) -eq $false) {
        throw 'You are not currently logged in using a Sophos Central Partner Service Principal'
    }

    $uri = [System.Uri]::New('https://api.central.sophos.com/partner/v1/tenants')

    $body = @{
        name          = $Name
        dataGeography = $DataGeography
        billingType   = $billingType
        contact       = @{
            firstName = $firstName
            lastName  = $lastName
            email     = $email
            phone     = $Phone
            address   = @{
                address1    = $address1
                city        = $City
                countryCode = $countryCode
                postalCode  = $postalCode
            }
        }
    }
    if ($address2) { $body['contact']['address'].Add('address2', $address2) }
    if ($address3) { $body['contact']['address'].Add('address3', $address3) }
    if ($state) { $body['contact']['address'].Add('state', $state) }

    try {
        $header = Get-SophosCentralAuthHeader -PartnerInitial
    } catch {
        throw $_
    }

    if ($Force -or $PSCmdlet.ShouldProcess($Name, ($body.keys -join ', '))) {
        Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body -CustomHeader $header
    }
}
# ./SophosCentral/public/New-SophosCentralUser.ps1
function New-SophosCentralUser {
    <#
    .SYNOPSIS
        Create a new user in Sophos Central
    .DESCRIPTION
        Create a new user in Sophos Central
    .PARAMETER Name
        This parameter is mandatory
    .PARAMETER GroupIDs
        A list/array of group ID's to add them to. To determine the ID of the groups use Get-SophosCentralUserGroups
    .EXAMPLE
        New-SophosCentralUser -Name "John Smith" -FirstName "John" -LastName "Smith" -Email "jsmith@contoso.com"
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/directory/users/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [string]$FirstName,
        [string]$LastName,
        [string]$Email,
        [string]$ExchangeLogin,
        [string[]]$GroupIDs,
        [switch]$Force
    )
    Test-SophosCentralConnected
    
    $uriChild = '/common/v1/directory/users'
    $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)

    $body = @{
        name = $Name
    }
    if ($firstName) { $body.Add('firstName', $FirstName) }
    if ($LastName) { $body.Add('lastName', $LastName) }
    if ($email) { $body.Add('email', $email) }
    if ($exchangeLogin) { $body.Add('exchangeLogin', $exchangeLogin) }
    if ($groupIds) { $body.Add('groupIds', $groupIds) }

    if ($Force -or $PSCmdlet.ShouldProcess('Create user', ($Name))) {
        Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
    }
}
# ./SophosCentral/public/Remove-SophosCentralAccessToken.ps1
function Remove-SophosCentralAccessToken {
    <#
    .SYNOPSIS
        Revoke access token for a tenant.
    .DESCRIPTION
        Revoke access token for a tenant.
    .EXAMPLE
        Remove-SophosCentralAccessToken
    .LINK
        https://developer.sophos.com/docs/accounts-v1/1/routes/access-tokens/%7BtokenId%7D/delete
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true)]
        [Alias('ID')]
        [string[]]$TokenID
    )
    Test-SophosCentralConnected
    
    foreach ($token in $TokenID) {
        $uri = [System.Uri]::New("$($SCRIPT:SophosCentral.GlobalEndpoint)/accounts/v1/access-tokens/$($token)")
        if ($Force -or $PSCmdlet.ShouldProcess('Remove access token', $token)) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
        }
    }
}
# ./SophosCentral/public/Remove-SophosCentralAdmin.ps1
function Remove-SophosCentralAdmin {
    <#
    .SYNOPSIS
        Remove an admin by ID.
    .DESCRIPTION
        Remove an admin by ID.
    .EXAMPLE
        Remove-SophosCentralAdmin -UserID 8a823dcd-5f09-463b-a5f3-e019cf280d79
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/admins/%7BadminId%7D/delete
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true)]
        [Alias('ID')]
        [string[]]$UserID
    )
    Test-SophosCentralConnected
    
    foreach ($idTmp in $UserID) {
        $uri = [System.Uri]::New("$($SCRIPT:SophosCentral.RegionEndpoint)/common/v1/admins/$($idTmp)")
        if ($Force -or $PSCmdlet.ShouldProcess('Remove admin', $idTmp)) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
        }
    }
}
# ./SophosCentral/public/Remove-SophosCentralEndpoint.ps1
function Remove-SophosCentralEndpoint {
    <#
    .SYNOPSIS
        Deletes a specified endpoint.
    .DESCRIPTION
        Deletes a specified endpoint.
    .PARAMETER EndpointID
        The ID of the endpoint
    .EXAMPLE
        Remove-SophosCentralEndpoint -EndpointID 59412c23-0ef1-49d6-9f4f-3b8b4863c176
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/get
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID,

        [switch]$Force
    )
    
    begin {
        Test-SophosCentralConnected
    }
    
    process {
        foreach ($endpoint in $EndpointID) {
            $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/endpoints/' + $endpoint)
            if ($Force -or $PSCmdlet.ShouldProcess('Remove Endpoint', $Endpoint)) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
            }
        }
    }

    end { }
}
# ./SophosCentral/public/Remove-SophosCentralFirewallGroup.ps1
function Remove-SophosCentralFirewallGroup {
    <#
    .SYNOPSIS
        Remove a firewall group
    .DESCRIPTION
        Remove a firewall group
    .PARAMETER GroupID
        The ID of the group. Use Get-SophosCentralFirewallGroup to list them
    .EXAMPLE
        Remove-SophosCentralFirewallGroup -GroupID 6fed0eb5-d8ff-44ee-9c37-df8a9924373d
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewall-groups/%7BgroupId%7D/delete
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$GroupID,

        [switch]$Force
    )
    
    begin {
        Test-SophosCentralConnected
    }
    
    process {
        foreach ($group in $GroupID) {
            $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewall-groups/' + $group)
            if ($Force -or $PSCmdlet.ShouldProcess('Remove', $group )) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
            }
        }
    }

    end { }
}
# ./SophosCentral/public/Remove-SophosCentralFirewallUpdate.ps1
function Remove-SophosCentralFirewallUpdate {
    <#
    .SYNOPSIS
        Cancel a scheduled firewall update
    .DESCRIPTION
        Cancel a scheduled firewall update
    .PARAMETER FirewallID
        The ID of the firewall. Use Get-SophosCentralFirewall to list them
    .EXAMPLE
        Remove-SophosCentralFirewallUpdate -FirewallID 6fed0eb5-d8ff-44ee-9c37-df8a9924373d
    .LINK
        https://developer.sophos.com/docs/firewall-v1/1/routes/firewalls/actions/firmware-upgrade/delete
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$FirewallID,

        [switch]$Force
    )

    begin {
        Test-SophosCentralConnected
    }

    process {
        foreach ($firewall in $FirewallID) {
            $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/firewall/v1/firewalls/actions/firmware-upgrade?ids=' + $firewall)
            if ($Force -or $PSCmdlet.ShouldProcess('Cancel update', $firewall )) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
            }
        }
    }

    end { }
}
# ./SophosCentral/public/Remove-SophosCentralUser.ps1
function Remove-SophosCentralUser {
    <#
    .SYNOPSIS
        Delete a user by ID
    .DESCRIPTION
        Delete a user by ID
    .EXAMPLE
        Remove-SophosCentralUser -UserID 8a823dcd-5f09-463b-a5f3-e019cf280d79
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/directory/users/%7BuserId%7D/delete
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true)]
        [Alias('ID')]
        [string[]]$UserID
    )
    Test-SophosCentralConnected
    
    foreach ($idTmp in $UserID) {
        $uri = [System.Uri]::New("$($SCRIPT:SophosCentral.RegionEndpoint)/common/v1/directory/users/$($idTmp)")
        if ($Force -or $PSCmdlet.ShouldProcess('Remove user', $idTmp)) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
        }
    }
}
# ./SophosCentral/public/Remove-SophosCentralXDRQueryRun.ps1
function Remove-SophosCentralXDRQueryRun {
    <#
    .SYNOPSIS
        Cancel a query run by ID.
    .DESCRIPTION
        Cancel a query run by ID.
    .PARAMETER RunId
        Query run ID.
    .EXAMPLE
        Remove-SophosCentralXDRQueryRun -RunID 6fed0eb5-d8ff-44ee-9c37-df8a9924373d
    .LINK
        https://developer.sophos.com/docs/xdr-query-v1/1/routes/queries/runs/%7BrunId%7D/cancel/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$RunID,

        [switch]$Force
    )
    
    begin {
        Test-SophosCentralConnected
    }
    
    process {
        foreach ($run in $RunID) {
            $uriChild = "/xdr-query/v1/queries/runs/$($run)/cancel"
            $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + $uriChild)
            if ($Force -or $PSCmdlet.ShouldProcess('Remove', $run)) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Post
            }
        }
    }

    end { }
}
# ./SophosCentral/public/Set-SophosCentralAlertAction.ps1
function Set-SophosCentralAlertAction {
    <#
    .SYNOPSIS
        Update an alert in Sophos Central
    .DESCRIPTION
        Update an alert in Sophos Central
    .PARAMETER AlertID
        The ID of the alert. Use Get-SophosCentralAlerts to list them
    .PARAMETER Action
        The alert action to perform. To get the possible actions for an alert, check the results from Get-SophosCentralAlerts

        The action must be in the same capitalization as listed, otherwise it will fail

        Possible options: 'acknowledge', 'cleanPua', 'cleanVirus', 'authPua', 'clearThreat', 'clearHmpa', 'sendMsgPua', 'sendMsgThreat'
    .PARAMETER Message
        Message to send for the action.
    .EXAMPLE
        Set-SophosCentralAlertAction -AlertID "6d41e78e-0360-4de3-8669-bb7b797ee515" -Action "clearThreat"
    .LINK
        https://developer.sophos.com/docs/common-v1/1/routes/alerts/%7BalertId%7D/actions/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true)]
        [Alias('ID')]
        [string[]]$AlertID,

        [Parameter(Mandatory = $true)]
        [ValidateSet('acknowledge', 'cleanPua', 'cleanVirus', 'authPua', 'clearThreat', 'clearHmpa', 'sendMsgPua', 'sendMsgThreat')]
        [string]$Action,

        [string]$Message,

        [switch]$Force
    )
    begin {   
        Test-SophosCentralConnected
         
        $uriChild = '/common/v1/alerts/{0}/actions'
        $uriString = $SCRIPT:SophosCentral.RegionEndpoint + $uriChild
    }
    process {
        foreach ($alert in $AlertID) {
            
            $uri = [System.Uri]::New($uriString -f $alert)
            $body = @{
                action = $Action
            }
            if ($Message) {
                $body.Add('message', $Message)
            }
            if ($Force -or $PSCmdlet.ShouldProcess($alert, $Action)) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
            }
            
        }
    }
}
# ./SophosCentral/public/Set-SophosCentralEndpointIsolation.ps1
function Set-SophosCentralEndpointIsolation {
    <#
    .SYNOPSIS
        Turn on or off endpoint isolation for multiple endpoints.
    .DESCRIPTION
        Turn on or off endpoint isolation for multiple endpoints.
    .PARAMETER EndpointID
        The ID of the Endpoint. Use Get-SophosCentralEndpoints to list them
    .PARAMETER Enabled
        Use $true to enable isolation, $false to disable
    .PARAMETER Comment
        Reason the endpoints should be isolated or not
    .EXAMPLE
        Set-SophosCentralEndpointIsolation -EndpointID '23a920fa-9a34-4869-bc3d-a1626e50f670' -Enabled $false -Comment "disable iso"
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/isolation/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Update Status')]
        [System.Boolean]$Enabled,

        [Parameter(Mandatory = $true)]
        [string]$Comment,

        [switch]$Force
    )
    begin {
        Test-SophosCentralConnected
    
        $uri = [System.Uri]::New($SCRIPT:SophosCentral.RegionEndpoint + '/endpoint/v1/endpoints/isolation')
    }
    process {
        
        $body = @{
            enabled = $Enabled
            comment = $Comment
            ids     = @()
        }
        foreach ($endpoint in $EndpointID) {
            $body['ids'] += $endpoint
        }
        
        if ($Force -or $PSCmdlet.ShouldProcess('isolation', ($EndpointID -join ', '))) {
            Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
        } 
    }
    
}
# ./SophosCentral/public/Set-SophosCentralEndpointTamperProtection.ps1
function Set-SophosCentralEndpointTamperProtection {
    <#
    .SYNOPSIS
        Update Tamper Protection settings
    .DESCRIPTION
        Update Tamper Protection settings
    .PARAMETER EndpointID
        The ID of the Endpoint. Use Get-SophosCentralEndpoints to list them
    .PARAMETER Enabled
        Use $true to enable Tamper Protection, $false to disable
    .PARAMETER RegeneratePassword
        Use this switch to generate a new Tamper Protection password
    .EXAMPLE
        Set-SophosCentralEndpointTamperProtection -EndpointID '23a920fa-9a34-4869-bc3d-a1626e50f670' -Enabled $false
    .EXAMPLE
        Set-SophosCentralEndpointTamperProtection -EndpointID '23a920fa-9a34-4869-bc3d-a1626e50f670' -RegeneratePassword
    .LINK
        https://developer.sophos.com/docs/endpoint-v1/1/routes/endpoints/%7BendpointId%7D/tamper-protection/post
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [string[]]$EndpointID,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Update Status')]
        [System.Boolean]$Enabled,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Regenerate Password')]
        [switch]$RegeneratePassword,

        [switch]$Force
    )
    begin {
        Test-SophosCentralConnected
    
        $uriChild = '/endpoint/v1/endpoints/{0}/tamper-protection'
        $uriString = $SCRIPT:SophosCentral.RegionEndpoint + $uriChild
    }
    process {
        foreach ($endpoint in $EndpointID) {
            $uri = [System.Uri]::New($uriString -f $endpoint)
            $body = @{}
            if ($Enabled) { $body.Add('enabled', $Enabled) }
            if ($RegeneratePassword) { $body.Add('regeneratePassword', $RegeneratePassword) }
            
            if ($Force -or $PSCmdlet.ShouldProcess($EndpointID, ($body.keys -join ', '))) {
                Invoke-SophosCentralWebRequest -Uri $uri -Method Post -Body $body
            }
        }
    }
}