PSSymantecSEPM.psm1

#Region '.\Classes\Exceptions-Policy.ps1' -1


<#
Class to manage exceptions policy.
Create a policy exceptions object and add exception types to it with custom methods
Structure follows the API documentation : https://apidocs.securitycloud.symantec.com/#/doc?id=policies
Section : Update Exceptions Policy
#>

class SEPMPolicyExceptionsStructure {
    <# Define the class. Try constructors, properties, or methods. #>
    [object] $configuration
    [object] $lockedoptions
    [Nullable[bool]] $enabled
    [string] $desc
    [string] $name
    SEPMPolicyExceptionsStructure() {
        $this.configuration = [object]@{
            files                      = [System.Collections.Generic.List[object]]::new()
            non_pe_rules               = [System.Collections.Generic.List[object]]::new()
            directories                = [System.Collections.Generic.List[object]]::new()
            webdomains                 = [System.Collections.Generic.List[object]]::new()
            certificates               = [System.Collections.Generic.List[object]]::new()
            applications               = [System.Collections.Generic.List[object]]::new()
            denylistrules              = [System.Collections.Generic.List[object]]::new()
            applications_to_monitor    = [System.Collections.Generic.List[object]]::new()
            mac                        = [object]@{
                files = [System.Collections.Generic.List[object]]::new()
            }
            linux                      = [object]@{
                directories    = [System.Collections.Generic.List[object]]::new()
                extension_list = [object]::new()
            }
            extension_list             = [object]::new()
            knownrisks                 = [System.Collections.Generic.List[object]]::new()
            tamper_files               = [System.Collections.Generic.List[object]]::new()
            dns_and_host_applications  = [System.Collections.Generic.List[object]]::new()
            dns_and_host_denylistrules = [System.Collections.Generic.List[object]]::new()
        }
        $this.lockedoptions = [object]@{}
    }

    # Method to Update lockedoptions object
    [void] UpdateLockedOptions(
        [Nullable[bool]] $knownrisks = $null,
        [Nullable[bool]] $extension = $null,
        [Nullable[bool]] $file = $null,
        [Nullable[bool]] $domain = $null,
        [Nullable[bool]] $securityrisk = $null,
        [Nullable[bool]] $sonar = $null,
        [Nullable[bool]] $application = $null,
        [Nullable[bool]] $dnshostfile = $null,
        [Nullable[bool]] $certificate = $null
    ) {
        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $knownrisks) { $this.lockedoptions.knownrisks = $knownrisks }
        if ($null -ne $extension) { $this.lockedoptions.extension = $extension }
        if ($null -ne $file) { $this.lockedoptions.file = $file }
        if ($null -ne $domain) { $this.lockedoptions.domain = $domain }
        if ($null -ne $securityrisk) { $this.lockedoptions.securityrisk = $securityrisk }
        if ($null -ne $sonar) { $this.lockedoptions.sonar = $sonar }
        if ($null -ne $application) { $this.lockedoptions.application = $application }
        if ($null -ne $dnshostfile) { $this.lockedoptions.dnshostfile = $dnshostfile }
        if ($null -ne $certificate) { $this.lockedoptions.certificate = $certificate }
    }

    # Method to add description
    [void] AddDescription(
        [string] $description
    ) {
        $this.desc = $description
    }

    # Method to create a file hashtable
    [hashtable] CreateFilesHashTable(
        [Nullable[bool]] $sonar = $null,
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $scancategory = "",
        [string] $pathvariable = "",
        [string] $path = "",
        [Nullable[bool]] $applicationcontrol = $null,
        [Nullable[bool]] $securityrisk = $null,
        [Nullable[bool]] $recursive = $null
    ) {
        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $sonar) { $HashTable['sonar'] = $sonar }
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($scancategory)) { $HashTable['scancategory'] = $scancategory }
        if (![string]::IsNullOrEmpty($pathvariable)) { $HashTable['pathvariable'] = $pathvariable }
        if (![string]::IsNullOrEmpty($path)) { $HashTable['path'] = $path }
        if ($null -ne $applicationcontrol) { $HashTable['applicationcontrol'] = $applicationcontrol }
        if ($null -ne $securityrisk) { $HashTable['securityrisk'] = $securityrisk }
        if ($null -ne $recursive) { $HashTable['recursive'] = $recursive }

        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add files exceptions
    [void] AddConfigurationFilesExceptions(
        [hashtable] $file # Use CreateFilesHashTable method
    ) {
        $this.configuration.files.Add($file)
    }

    # Method to create a file hashtable
    [hashtable] CreateNonPEFilesHashTable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $file_sha2 = "",
        [string] $file_md5 = "",
        [string] $file_name = "",
        [string] $file_company = "",
        [Nullable[Int64]] $file_size = $null,
        [string] $file_description = "",
        [string] $file_directory = "",
        [string] $action = "",
        [string] $actor_sha2 = "",
        [string] $actor_md5 = "",
        [string] $actor_name = "",
        [string] $actor_company = "",
        [Nullable[Int64]] $actor_size = $null,
        [string] $actor_description = "",
        [string] $actor_directory = ""

    ) {
        return @{
            deleted   = $deleted
            rulestate = [PSCustomObject]@{
                enabled = $rulestate_enabled
                source  = $rulestate_source
            }
            file      = [PSCustomObject]@{
                sha2        = $file_sha2
                md5         = $file_md5
                name        = $file_name
                company     = $file_company
                size        = $file_size
                description = $file_description
                directory   = $file_directory
            }
            action    = $action
            actor     = [PSCustomObject]@{
                sha2        = $actor_sha2
                md5         = $actor_md5
                name        = $actor_name
                company     = $actor_company
                size        = $actor_size
                description = $actor_description
                directory   = $actor_directory
            }
        }
    }

    # Method to add non PE files exceptions
    [void] AddConfigurationNonPEFilesExceptions(
        [hashtable] $non_pe_file # Use CreateNonPEFilesHashTable method
    ) {
        $this.configuration.non_pe_rules.Add($non_pe_file)
    }

    # Method to create a directory hashtable
    [hashtable] CreateDirectoryHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $scancategory = "",
        [string] $scantype = "",
        [string] $pathvariable = "",
        [string] $directory = "",
        [Nullable[bool]] $recursive = $null
    ) {
        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($scancategory)) { $HashTable['scancategory'] = $scancategory }
        if (![string]::IsNullOrEmpty($scantype)) { $HashTable['scantype'] = $scantype }
        if ($null -ne $recursive) { $HashTable['recursive'] = $recursive }

        # Add key/value pairs to the hashtable only if the value is not $null or empty or throw an error
        if (![string]::IsNullOrEmpty($pathvariable)) {
            $HashTable['pathvariable'] = $pathvariable
        } else {
            throw "The 'pathvariable' parameter is mandatory and cannot be $null or empty."
        }

        if (![string]::IsNullOrEmpty($directory)) {
            $HashTable['directory'] = $directory
        } else {
            throw "The 'directory' parameter is mandatory and cannot be $null or empty."
        }

        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add directories
    [void] AddConfigurationDirectoriesExceptions(
        [hashtable] $directory # Use CreateDirectoryHashtable method
    ) {
        $this.configuration.directories.Add($directory)
    }

    # Method to create extensions hashtable
    [hashtable] CreateExtensionListHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        # $extensions is a PSOBject list
        [string] $scancategory = "",
        [PSObject[]] $extensions = @()
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # extensions = $extensions
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($extensions)) { $HashTable['extensions'] = $extensions }
        if (![string]::IsNullOrEmpty($scancategory)) { $HashTable['scancategory'] = $scancategory }

        # Verify if $Extensions is not an empty list
        if ($extensions.Count -eq 0) {
            throw "The 'extensions' parameter is mandatory and cannot be an empty list."
        } else {
            # Verify if $Extensions is not an empty list
            foreach ($extension in $extensions) {
                if ([string]::IsNullOrEmpty($extension)) {
                    throw "The 'extensions' parameter is mandatory and cannot be an empty list."
                }
            }
        }

        # Add 'extensions' to the main hashtable
        $HashTable['extensions'] = $extensions

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add extensions
    [void] AddExtensionsList(
        [hashtable] $extensions # Use CreateExtensionListHashtable method
    ) {
        $this.configuration.extension_list = $extensions
    }
    
    # Method to create a webdomains hashtable
    [hashtable] CreateWebdomainsHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $domain = ""
    ) {
        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if ([string]::IsNullOrEmpty($domain)) {
            throw "The 'domain' parameter is mandatory and cannot be $null or empty."
        } else {
            $HashTable['domain'] = $domain
        }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add webdomains
    [void] AddWebdomains(
        [hashtable] $webdomains # Use CreateWebdomainsHashtable method
    ) {
        $this.configuration.webdomains.Add($webdomains)
    }

    # Method to create a certificate hashtable
    [hashtable] CreateCertificatesHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $signature_fingerprint_algorith = "",
        [string] $signature_fingerprint_value = "",
        [string] $signature_company_name = "",
        [string] $signature_issuer = ""
    ) {
        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($signature_company_name)) { $HashTable['signature_company_name'] = $signature_company_name }
        if (![string]::IsNullOrEmpty($signature_issuer)) { $HashTable['signature_issuer'] = $signature_issuer }

        # SIGNATURE FINGERPRINT
        # Create an empty hashtable for 'signature_fingerprint'
        $signature_fingerprint = @{}

        # Add 'algorithm' to 'signature_fingerprint' or throw an error if it's $null or empty
        if ([string]::IsNullOrEmpty($signature_fingerprint_algorith)) {
            throw "The 'algorithm' parameter is mandatory and cannot be $null or empty."
        } else {
            $signature_fingerprint['algorithm'] = $signature_fingerprint_algorith
        }

        # Add 'value' to 'signature_fingerprint' or throw an error if it's $null or empty
        if ([string]::IsNullOrEmpty($signature_fingerprint_value)) {
            throw "The 'value' parameter is mandatory and cannot be $null or empty."
        } else {
            $signature_fingerprint['value'] = $signature_fingerprint_value
        }

        # Add 'signature_fingerprint' to the main hashtable
        $HashTable['signature_fingerprint'] = [PSCustomObject]$signature_fingerprint
        
        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add certificates
    [void] AddCertificates(
        [hashtable] $certificates # Use CreateCertificatesHashtable method
    ) {
        $this.configuration.certificates.Add($certificates)
    }

    # Method to create a applications hashtable
    [hashtable] CreateApplicationsHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $processfile_sha2 = "",
        [string] $processfile_md5 = "",
        [string] $processfile_name = "",
        [string] $processfile_company = "",
        [Nullable[Int64]] $processfile_size = $null,
        [string] $processfile_description = "",
        [string] $processfile_directory = "",
        [string] $action = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # processfile = [PSCustomObject]@{
        # sha2 = $processfile_sha2
        # md5 = $processfile_md5
        # name = $processfile_name
        # company = $processfile_company
        # size = $processfile_size
        # description = $processfile_description
        # directory = $processfile_directory
        # }
        # action = $action
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($action)) { $HashTable['action'] = $action }
        
        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        # PROCESSFILE
        # Create an empty hashtable for 'processfile'
        $processfile = @{}

        # Add 'sha2' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_sha2)) {
            $processfile['sha2'] = $processfile_sha2
        }

        # Add 'md5' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_md5)) {
            $processfile['md5'] = $processfile_md5
        }

        # Add 'name' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_name)) {
            $processfile['name'] = $processfile_name
        }

        # Add 'company' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_company)) {
            $processfile['company'] = $processfile_company
        }

        # Add 'size' to 'processfile' only if it's not $null or empty
        if ($null -ne $processfile_size) {
            $processfile['size'] = $processfile_size
        }

        # Add 'description' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_description)) {
            $processfile['description'] = $processfile_description
        }

        # Add 'directory' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_directory)) {
            $processfile['directory'] = $processfile_directory
        }

        # Add 'processfile' to the main hashtable
        $HashTable['processfile'] = [PSCustomObject]$processfile

        return $HashTable
    }

    # Method to add applications
    [void] AddApplications(
        [hashtable] $applications # Use CreateApplicationsHashtable method
    ) {
        $this.configuration.applications.Add($applications)
    }

    # Method to create a denylistrules hashtable
    [hashtable] CreateDenylistrulesHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $processfile_sha2 = "",
        [string] $processfile_md5 = "",
        [string] $processfile_name = "",
        [string] $processfile_company = "",
        [Nullable[Int64]] $processfile_size = $null,
        [string] $processfile_description = "",
        [string] $processfile_directory = "",
        [string] $action = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # processfile = [PSCustomObject]@{
        # sha2 = $processfile_sha2
        # md5 = $processfile_md5
        # name = $processfile_name
        # company = $processfile_company
        # size = $processfile_size
        # description = $processfile_description
        # directory = $processfile_directory
        # }
        # action = $action
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($action)) { $HashTable['action'] = $action }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        # PROCESSFILE
        # Create an empty hashtable for 'processfile'
        $processfile = @{}

        # Add 'sha2' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_sha2)) {
            $processfile['sha2'] = $processfile_sha2
        }

        # Add 'md5' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_md5)) {
            $processfile['md5'] = $processfile_md5
        }

        # Add 'name' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_name)) {
            $processfile['name'] = $processfile_name
        }

        # Add 'company' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_company)) {
            $processfile['company'] = $processfile_company
        }

        # Add 'size' to 'processfile' only if it's not $null or empty
        if ($null -ne $processfile_size) {
            $processfile['size'] = $processfile_size
        }

        # Add 'description' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_description)) {
            $processfile['description'] = $processfile_description
        }

        # Add 'directory' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_directory)) {
            $processfile['directory'] = $processfile_directory
        }

        # Add 'processfile' to the main hashtable
        $HashTable['processfile'] = [PSCustomObject]$processfile

        return $HashTable
    }

    # Method to add denylistrules
    [void] AddDenylistrules(
        [hashtable] $denylistrules # Use CreateDenylistrulesHashtable method
    ) {
        $this.configuration.denylistrules.Add($denylistrules)
    }

    # Method to create a applications_to_monitor hashtable
    [hashtable] CreateApplicationsToMonitorHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $name = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # name = $name
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($name)) { $HashTable['name'] = $name }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add applications_to_monitor
    [void] AddApplicationsToMonitor(
        [hashtable] $applications_to_monitor # Use CreateApplicationsToMonitorHashtable method
    ) {
        $this.configuration.applications_to_monitor.Add($applications_to_monitor)
    }

    # Method to create a mac_files hashtable
    [hashtable] CreateMacFilesHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $pathvariable = "",
        [string] $path = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # pathvariable = $pathvariable
        # path = $path
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($pathvariable)) { $HashTable['pathvariable'] = $pathvariable }
        if (![string]::IsNullOrEmpty($path)) { $HashTable['path'] = $path }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add mac_files
    [void] AddMacFiles(
        [hashtable] $mac_files # Use CreateMacFilesHashtable method
    ) {
        $this.configuration.mac.files.Add($mac_files)
    }

    # Method to create a linux_directories hashtable
    [hashtable] CreateLinuxDirectoryHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $scancategory = "",
        [string] $pathvariable = "",
        [string] $directory = "",
        [Nullable[bool]] $recursive = $null
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # scancategory = $scancategory
        # pathvariable = $pathvariable
        # directory = $directory
        # recursive = $recursive
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($scancategory)) { $HashTable['scancategory'] = $scancategory }
        if ($null -ne $recursive) { $HashTable['recursive'] = $recursive }

        # Add key/value pairs to the hashtable only if the value is not $null or empty or throw an error
        if (![string]::IsNullOrEmpty($pathvariable)) {
            $HashTable['pathvariable'] = $pathvariable
        } else {
            throw "The 'pathvariable' parameter is mandatory and cannot be $null or empty."
        }

        if (![string]::IsNullOrEmpty($directory)) {
            $HashTable['directory'] = $directory
        } else {
            throw "The 'directory' parameter is mandatory and cannot be $null or empty."
        }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add linux_directories
    [void] AddLinuxDirectory(
        [hashtable] $linux_directories # Use CreateLinuxDirectoryHashtable method
    ) {
        $this.configuration.linux.directories.Add($linux_directories)
    }

    # Method to create a linux_extension_list hashtable
    [hashtable] CreateLinuxExtensionListHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        # $extensions is a PSOBject list
        [string] $scancategory = "",
        [PSObject[]] $extensions = @()
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # extensions = $extensions
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($extensions)) { $HashTable['extensions'] = $extensions }
        if (![string]::IsNullOrEmpty($scancategory)) { $HashTable['scancategory'] = $scancategory }

        # Verify if $Extensions is not an empty list
        if ($extensions.Count -eq 0) {
            throw "The 'extensions' parameter is mandatory and cannot be an empty list."
        } else {
            # Verify if $Extensions is not an empty list
            foreach ($extension in $extensions) {
                if ([string]::IsNullOrEmpty($extension)) {
                    throw "The 'extensions' parameter is mandatory and cannot be an empty list."
                }
            }
        }

        # Add 'extensions' to the main hashtable
        $HashTable['extensions'] = $extensions

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add linux_extension_list
    [void] AddLinuxExtensionList(
        [hashtable] $linux_extension_list # Use CreateLinuxExtensionListHashtable method
    ) {
        # $this.configuration.linux.extension_list.Add($linux_extension_list)
        $this.configuration.linux.extension_list = $linux_extension_list
    }


    # Method to create a knownrisks hashtable
    [hashtable] CreateKnownrisksHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $threat_id = "",
        [string] $threat_name = "",
        [string] $action = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # threat_id = $threat_id
        # threat_name = $threat_name
        # action = $action
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($action)) { $HashTable['action'] = $action }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        # THREAT
        # Create an empty hashtable for 'threat'
        $threat = @{}

        # Add 'id' to 'threat' only if it's not $null or empty or throw an error
        if (![string]::IsNullOrEmpty($threat_id)) {
            $threat['id'] = $threat_id
        } else {
            throw "The 'id' parameter is mandatory and cannot be $null or empty."
        }

        # Add 'name' to 'threat' only if it's not $null or empty or throw an error
        if (![string]::IsNullOrEmpty($threat_name)) {
            $threat['name'] = $threat_name
        } else {
            throw "The 'name' parameter is mandatory and cannot be $null or empty."
        }

        # Add 'threat' to the main hashtable
        $HashTable['threat'] = [PSCustomObject]$threat

        return $HashTable
    }

    # Method to add knownrisks
    [void] AddKnownrisks(
        [hashtable] $knownrisks # Use CreateKnownrisksHashtable method
    ) {
        $this.configuration.knownrisks.Add($knownrisks)
    }

    # Method to create a tamper_files hashtable
    [hashtable] CreateTamperFilesHashtable(
        [Nullable[bool]] $sonar = $null,
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $scancategory = "",
        [string] $pathvariable = "",
        [string] $path = "",
        [Nullable[bool]] $applicationcontrol = $null,
        [Nullable[bool]] $securityrisk = $null,
        [Nullable[bool]] $recursive = $null
    ) {
        # return @{
        # sonar = $sonar
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # scancategory = $scancategory
        # pathvariable = $pathvariable
        # applicationcontrol = $applicationcontrol
        # securityrisk = $securityrisk
        # recursive = $recursive
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $sonar) { $HashTable['sonar'] = $sonar }
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($scancategory)) { $HashTable['scancategory'] = $scancategory }
        if (![string]::IsNullOrEmpty($pathvariable)) { $HashTable['pathvariable'] = $pathvariable }
        if (![string]::IsNullOrEmpty($path)) { $HashTable['path'] = $path }
        if ($null -ne $applicationcontrol) { $HashTable['applicationcontrol'] = $applicationcontrol }
        if ($null -ne $securityrisk) { $HashTable['securityrisk'] = $securityrisk }
        if ($null -ne $recursive) { $HashTable['recursive'] = $recursive }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        return $HashTable
    }

    # Method to add tamper_files
    [void] AddTamperFiles(
        [hashtable] $tamper_files # Use CreateTamperFilesHashtable method
    ) {
        $this.configuration.tamper_files.Add($tamper_files)
    }

    # Method to create a dns_and_host_applications hashtable
    [hashtable] CreateDnsAndHostApplicationsHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $processfile_sha2 = "",
        [string] $processfile_md5 = "",
        [string] $processfile_name = "",
        [string] $processfile_company = "",
        [Nullable[Int64]] $processfile_size = $null,
        [string] $processfile_description = "",
        [string] $processfile_directory = "",
        [string] $action = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # processfile = [PSCustomObject]@{
        # sha2 = $processfile_sha2
        # md5 = $processfile_md5
        # name = $processfile_name
        # company = $processfile_company
        # size = $processfile_size
        # description = $processfile_description
        # directory = $processfile_directory
        # }
        # action = $action
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($action)) { $HashTable['action'] = $action }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        # PROCESSFILE
        # Create an empty hashtable for 'processfile'
        $processfile = @{}

        # Add 'sha2' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_sha2)) {
            $processfile['sha2'] = $processfile_sha2
        }

        # Add 'md5' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_md5)) {
            $processfile['md5'] = $processfile_md5
        }

        # Add 'name' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_name)) {
            $processfile['name'] = $processfile_name
        }

        # Add 'company' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_company)) {
            $processfile['company'] = $processfile_company
        }

        # Add 'size' to 'processfile' only if it's not $null or empty
        if ($null -ne $processfile_size) {
            $processfile['size'] = $processfile_size
        }

        # Add 'description' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_description)) {
            $processfile['description'] = $processfile_description
        }

        # Add 'directory' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_directory)) {
            $processfile['directory'] = $processfile_directory
        }

        # Add 'processfile' to the main hashtable
        $HashTable['processfile'] = [PSCustomObject]$processfile

        return $HashTable
    }

    # Method to add dns_and_host_applications
    [void] AddDnsAndHostApplications(
        [hashtable] $dns_and_host_applications # Use CreateDnsAndHostApplicationsHashtable method
    ) {
        $this.configuration.dns_and_host_applications.Add($dns_and_host_applications)
    }

    # Method to create a dns_and_host_denyrules hashtable
    [hashtable] CreateDnsAndHostDenyrulesHashtable(
        [Nullable[bool]] $deleted = $null,
        [Nullable[bool]] $rulestate_enabled = $null,
        [string] $rulestate_source = "PSSymantecSEPM",
        [string] $processfile_sha2 = "",
        [string] $processfile_md5 = "",
        [string] $processfile_name = "",
        [string] $processfile_company = "",
        [Nullable[Int64]] $processfile_size = $null,
        [string] $processfile_description = "",
        [string] $processfile_directory = "",
        [string] $action = ""
    ) {
        # return @{
        # deleted = $deleted
        # rulestate = [PSCustomObject]@{
        # enabled = $rulestate_enabled
        # source = $rulestate_source
        # }
        # processfile = [PSCustomObject]@{
        # sha2 = $processfile_sha2
        # md5 = $processfile_md5
        # name = $processfile_name
        # company = $processfile_company
        # size = $processfile_size
        # description = $processfile_description
        # directory = $processfile_directory
        # }
        # action = $action
        # }

        # Create an empty hashtable
        $HashTable = @{}

        # Add key/value pairs to the hashtable only if the value is not $null or empty
        if ($null -ne $deleted) { $HashTable['deleted'] = $deleted }
        if (![string]::IsNullOrEmpty($action)) { $HashTable['action'] = $action }

        # RULESTATE
        # Create an empty hashtable for 'rulestate'
        $rulestate = @{}

        # Add 'enabled' to 'rulestate' only if it's not $null
        if ($null -ne $rulestate_enabled) {
            $rulestate['enabled'] = $rulestate_enabled
        }

        # Add 'source' to 'rulestate' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($rulestate_source)) {
            $rulestate['source'] = $rulestate_source
        }

        # Add 'rulestate' to the main hashtable only if it's not empty
        if ($rulestate.Count -gt 0) {
            $HashTable['rulestate'] = [PSCustomObject]$rulestate
        }

        # PROCESSFILE
        # Create an empty hashtable for 'processfile'
        $processfile = @{}

        # Add 'sha2' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_sha2)) {
            $processfile['sha2'] = $processfile_sha2
        }

        # Add 'md5' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_md5)) {
            $processfile['md5'] = $processfile_md5
        }

        # Add 'name' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_name)) {
            $processfile['name'] = $processfile_name
        }

        # Add 'company' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_company)) {
            $processfile['company'] = $processfile_company
        }

        # Add 'size' to 'processfile' only if it's not $null or empty
        if ($null -ne $processfile_size) {
            $processfile['size'] = $processfile_size
        }

        # Add 'description' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_description)) {
            $processfile['description'] = $processfile_description
        }

        # Add 'directory' to 'processfile' only if it's not $null or empty
        if (![string]::IsNullOrEmpty($processfile_directory)) {
            $processfile['directory'] = $processfile_directory
        }

        # Add 'processfile' to the main hashtable
        $HashTable['processfile'] = [PSCustomObject]$processfile

        return $HashTable
    }
}
#EndRegion '.\Classes\Exceptions-Policy.ps1' 1259
#Region '.\Private\Build-SEPMQueryURI.ps1' -1

function Build-SEPMQueryURI {
    <#
    .SYNOPSIS
        Constructs a URI from a base URI and query strings
    .DESCRIPTION
        Constructs a URI from a base URI and query strings
    .PARAMETER BaseURI
        The base URI to use
    .PARAMETER QueryStrings
        A hashtable of query strings to add to the URI
    .NOTES
        helper function
    .EXAMPLE
        $BaseURI = "https://gdc8ap0030:8446/sepm/api/v1/computers"
        $QueryStrings = @{
            sort = "COMPUTER_NAME"
            pageIndex = 1
            pageSize = 100
        }
        $URI = Build-SEPMQueryURI -BaseURI $BaseURI -QueryStrings $QueryStrings
    #>

    
    
    param (
        [string]$BaseURI,
        [hashtable]$QueryStrings
    )

    # Construct the URI
    $builder = New-Object System.UriBuilder($BaseURI)
    $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
    foreach ($param in $QueryStrings.GetEnumerator()) {
        $query[$param.Key] = $param.Value
    }
    $builder.Query = $query.ToString()
    $BaseURI = $builder.ToString()

    return $BaseURI
}
#EndRegion '.\Private\Build-SEPMQueryURI.ps1' 40
#Region '.\Private\Import-SepmConfiguration.ps1' -1

function Import-SepmConfiguration {
    <#
    .SYNOPSIS
        Loads in the default configuration values, and then updates the individual properties
        with values that may exist in a file.
 
    .DESCRIPTION
        Loads in the default configuration values, and then updates the individual properties
        with values that may exist in a file.
 
    .PARAMETER Path
        The file that may or may not exist with a serialized version of the configuration
        values for this module.
 
    .OUTPUTS
        PSCustomObject
 
    .NOTES
        Internal helper method.
        No side-effects.
 
    .EXAMPLE
        Import-SepmConfiguration -Path 'c:\foo\config.json'
         
        Creates a new default config object and updates its values with any that are found
        within a deserialized object from the content in $Path. The configuration object
        is then returned.
#>

    [CmdletBinding()]
    param(
        [string] $Path
    )

    # Create a configuration object with all the default values. We can then update the values
    # with any that we find on disk.

    $config = [PSCustomObject]@{
        'ServerAddress' = ''
        'port'          = '8446'
        'domain'        = ''
    }

    $jsonObject = Read-SepmConfiguration -Path $Path
    Get-Member -InputObject $config -MemberType NoteProperty |
        ForEach-Object {
            $name = $_.Name
            $type = $config.$name.GetType().Name
            $config.$name = Resolve-PropertyValue -InputObject $jsonObject -Name $name -Type $type -DefaultValue $config.$name
        }

    return $config
}
#EndRegion '.\Private\Import-SepmConfiguration.ps1' 53
#Region '.\Private\Initialize-PolicyExceptionStructure.ps1' -1

function Initialize-PolicyExceptionStructure {

    <#
    .SYNOPSIS
        Initializes the skeleton of the body structure to update the exception policy.
    .DESCRIPTION
        Initializes the skeleton of the body structure to update the exception policy.
        Returns the body structure and its associated policy ID.
    .PARAMETER PolicyName
        The name of the policy to update.
    .NOTES
        This is an internal helper function.
    .EXAMPLE
        PS C:\> Initialize-PolicyExceptionStructure -PolicyName "Default"
    #>

    
    
    [CmdletBinding()]
    param (
        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName
    )

    process {
        # Get all policies
        $policies = Get-SEPMPoliciesSummary

        # Get Policy ID from policy name
        $PolicyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id

        # Instantiates the skeleton of the body structure to update the exception policy
        $ObjBody = [SEPMPolicyExceptionsStructure]::new()

        # Update the body structure with the mandatory parameters
        $ObjBody.name = $PolicyName

        $return = [PSCustomObject]@{
            ObjBody  = $ObjBody
            PolicyID = $PolicyID
        }

        # Return ObjBody & policy ID
        return $return
    }
    
}
#EndRegion '.\Private\Initialize-PolicyExceptionStructure.ps1' 52
#Region '.\Private\Invoke-ABRestMethod.ps1' -1

function Invoke-ABRestMethod {
    <#
    .SYNOPSIS
        Invokes a REST method with a PS version-appropriate method
    .DESCRIPTION
        Invokes a REST method with a PS version-appropriate method
        Handles the differences between PS versions 5 and 6 for certificate validation skipping
        Tests the certificate of the server if self signed
    .NOTES
        Helper function for Invoke-ABRestMethod
    .PARAMETER params
        A hashtable of parameters to pass to the Invoke-RestMethod cmdlet
    .EXAMPLE
        $params = @{
            Method = 'POST'
            Uri = $URI
            headers = $headers
        }
        Invoke-ABRestMethod -params $params
    #>

    
    
    param (
        # Hashtable of parameters
        [Parameter(
            Mandatory = $true
        )]
        [hashtable]
        $params
    )

    # Test the certificate if self signed
    if (-not $script:SkipCert) {
        Test-SEPMCertificate -URI $params.Uri
    }

    switch ($PSVersionTable.PSVersion.Major) {
        { $_ -ge 6 } { 
            try {
                if ($script:SkipCert -eq $true) {
                    $resp = Invoke-RestMethod @params -SkipCertificateCheck
                } else {
                    $resp = Invoke-RestMethod @params
                }
            } catch {
                Write-Warning -Message "Error: $_"
                return "Error: $_"
            }
        }
        default {
            try {
                if ($script:SkipCert -eq $true) {
                    Skip-Cert
                    $resp = Invoke-RestMethod @params
                } else {
                    $resp = Invoke-RestMethod @params
                }
            } catch {
                Write-Warning -Message "Error: $_"
                return "Error: $_"
            }
        }
    }
    
    # return the response
    return $resp
}
#EndRegion '.\Private\Invoke-ABRestMethod.ps1' 68
#Region '.\Private\Optimize-ExceptionPolicyStructure.ps1' -1

function Optimize-ExceptionPolicyStructure {
    <#
    .SYNOPSIS
        This function is used to optimize the structure of the exception policy object.
    .DESCRIPTION
        This function is used to optimize the structure of the exception policy object.
        It will remove empty properties and nested objects that are empty.
    .PARAMETER obj
        The object to optimize
         
    .EXAMPLE
        Optimize-ExceptionPolicyStructure -obj $exceptionPolicy
    .OUTPUTS
        System.Management.Automation.PSCustomObject
 
    #>

    
    

    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true
        )]
        [object]
        $obj
    )

    process {
        # convert the object to a PSCustomObject (trick to convert custom class to PSCustomObject)
        # There might be cleaner ways to do this
        $obj = $obj | ConvertTo-Json -Depth 100 | ConvertFrom-Json -Depth 100

        # Listing all properties of the object
        $AllProperties = $obj | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
        foreach ($property in $AllProperties) {
            # Conditional nested objects lookup
            switch ($property) {
                "configuration" {
                    # recursively call the function to dig deeper
                    $obj.$property = Optimize-ExceptionPolicyStructure $obj.$property
                    
                    # If configuration object is empty, remove it
                    if (($obj.$property | Get-Member -MemberType NoteProperty).count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "lockedoptions" {
                    # TODO Change the lockedoptions cleanup way via a custom method in the class
                    # # list all properties of the lockedoptions object
                    # $lockedproperties = $obj.$property | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
                    
                    # # Parse the lockedoptions properties and remove the ones with $null values
                    # foreach ($lockedproperty in $lockedproperties) {
                    # if ($null -eq $obj.$property.$lockedproperty) {
                    # $obj.$property = $obj.$property | Select-Object -ExcludeProperty $lockedproperty
                    # }
                    # }

                    # If lockedoptions object is empty, remove it
                    if (($obj.$property | Get-Member -MemberType NoteProperty).count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "extension_list" {
                    # If no extensions are defined, remove the extension_list property
                    if ($obj.$property.extensions.count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "mac" {
                    # If no files are defined, remove the mac property
                    if ($obj.$property.files.count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "linux" {
                    # If no directories are defined, remove the directories list
                    if ($obj.$property.directories.count -eq 0) {
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "directories"
                    }

                    # If no extensions are defined, remove them from the linux object
                    if ($obj.$property.extension_list.extensions.count -eq 0) {
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "extension_list"
                    }

                    # If linux object is empty, remove it
                    if (($obj.$property | Get-Member -MemberType NoteProperty).count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
            }

            # If the property is empty, remove the property
            if ($obj.$property.count -eq 0) {
                $obj = $obj | Select-Object -ExcludeProperty $property
            }

            # If the property is null, remove the property
            if ($null -eq $obj.$property) {
                $obj = $obj | Select-Object -ExcludeProperty $property
            }
        }
        return $obj
    }
    
}
#EndRegion '.\Private\Optimize-ExceptionPolicyStructure.ps1' 110
#Region '.\Private\Read-SepmConfiguration.ps1' -1

function Read-SepmConfiguration {
    <#
    .SYNOPSIS
        Loads in the default configuration values and returns the deserialized object.
 
    .DESCRIPTION
        Loads in the default configuration values and returns the deserialized object.
 
    .PARAMETER Path
        The file that may or may not exist with a serialized version of the configuration
        values for this module.
 
    .OUTPUTS
        PSCustomObject
 
    .NOTES
        Internal helper method.
        No side-effects.
 
    .EXAMPLE
        Read-SepmConfiguration -Path 'c:\foo\config.json'
 
        Returns back an object with the deserialized object contained in the specified file,
        if it exists and is valid.
#>

    [CmdletBinding()]
    param(
        [string] $Path
    )

    $content = Get-Content -Path $Path -Encoding UTF8 -ErrorAction Ignore
    if (-not [String]::IsNullOrEmpty($content)) {
        try {
            return ($content | ConvertFrom-Json)
        } catch {
            $message = 'The configuration file for this module is in an invalid state. Use Reset-SEPMConfiguration to recover.'
            Write-Warning -Message $message
        }
    }

    return [PSCustomObject]@{}
}
#EndRegion '.\Private\Read-SepmConfiguration.ps1' 43
#Region '.\Private\Remove-NestedNullOrEmptyProperties.ps1' -1

function Remove-NestedNullOrEmptyProperties {
    <#
    .SYNOPSIS
        Remove nested properties with $null or empty values from a PSObject
    .DESCRIPTION
        This function will recursively iterate over all properties of a PSObject or list of PSObjects and remove the ones with $null or empty values.
    .EXAMPLE
        $obj = [PSCustomObject]@{
            "property1" = "value1"
            "property2" = $null
            "property3" = ""
            "property4" = [PSCustomObject]@{
                "property5" = "value5"
                "property6" = $null
                "property7" = ""
                "property8" = [PSCustomObject]@{
                    "property9" = "value9"
                    "property10" = $null
                    "property11" = ""
                }
            }
        }
        $obj = Remove-NestedNullOrEmptyProperties -InputObject $obj
        $obj | ConvertTo-Json
        {
        "property1": "value1",
        "property4": {
                        "property5": "value5",
                        "property8": {
                                            "property9": "value9"
                                        }
                }
    }
    .NOTES
        helper function
    #>


    
    param (
        [Parameter(Mandatory = $true)]
        [PSObject] $InputObject
    )

    # Get all properties of the input object
    $properties = $InputObject | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name

    # Iterate over the properties and remove the ones with $null values
    foreach ($property in $properties) {
        # If the property value is $null, remove the property
        if ($null -eq $InputObject.$property) {
            $InputObject = $InputObject | Select-Object -ExcludeProperty $property
        }
        # If the property value is an empty string, remove the property
        elseif ($InputObject.$property -eq "") {
            $InputObject = $InputObject | Select-Object -ExcludeProperty $property
        }
        # If the property value is another PSObject, recursively call this function
        elseif ($InputObject.$property -is [PSObject]) {
            $InputObject.$property = Remove-NestedNullOrEmptyProperties -InputObject $InputObject.$property -ErrorAction SilentlyContinue
        }
        # If the property value is a list of PSObjects, iterate over the list and recursively call this function on each item
        elseif ($InputObject.$property -is [System.Collections.IEnumerable] -and 
            $InputObject.$property -isnot [string]) {
            $InputObject.$property = $InputObject.$property | ForEach-Object {
                if ($_ -is [PSObject]) {
                    Remove-NestedNullOrEmptyProperties -InputObject $_
                } else {
                    $_
                }
            }
        }
    }

    # Return the modified object
    return $InputObject
}
#EndRegion '.\Private\Remove-NestedNullOrEmptyProperties.ps1' 77
#Region '.\Private\Resolve-PropertyValue.ps1' -1

function Resolve-PropertyValue {
    <#
    .SYNOPSIS
        Returns the requested property from the provided object, if it exists and is a valid
        value. Otherwise, returns the default value.
 
    .DESCRIPTION
        Returns the requested property from the provided object, if it exists and is a valid
        value. Otherwise, returns the default value.
 
    .PARAMETER InputObject
        The object to check the value of the requested property.
 
    .PARAMETER Name
        The name of the property on InputObject whose value is desired.
 
    .PARAMETER Type
        The type of the value stored in the Name property on InputObject. Used to validate
        that the property has a valid value.
 
    .PARAMETER DefaultValue
        The value to return if Name doesn't exist on InputObject or is of an invalid type.
 
    .EXAMPLE
        Resolve-PropertyValue -InputObject $config -Name defaultOwnerName -Type String -DefaultValue $null
 
        Checks $config to see if it has a property named "defaultOwnerName". If it does, and it's a
        string, returns that value, otherwise, returns $null (the DefaultValue).
#>

    [CmdletBinding()]
    param(
        [PSCustomObject] $InputObject,

        [Parameter(Mandatory)]
        [string] $Name,

        [Parameter(Mandatory)]
        [ValidateSet('String', 'Boolean', 'Int32', 'Int64')]
        [String] $Type,

        $DefaultValue
    )

    if ($null -eq $InputObject) {
        return $DefaultValue
    }

    $typeType = [String]
    if ($Type -eq 'Boolean') { $typeType = [Boolean] }
    if ($Type -eq 'Int32') { $typeType = [Int32] }
    if ($Type -eq 'Int64') { $typeType = [Int64] }
    $numberEquivalents = @('Int32', 'Int64', 'long', 'int')

    if (Test-PropertyExists -InputObject $InputObject -Name $Name) {
        if (($InputObject.$Name -is $typeType) -or
            (($Type -in $numberEquivalents) -and ($InputObject.$Name.GetType().Name -in $numberEquivalents))) {
            return $InputObject.$Name
        } else {
            return $DefaultValue
        }
    } else {
        return $DefaultValue
    }
}
#EndRegion '.\Private\Resolve-PropertyValue.ps1' 65
#Region '.\Private\Save-SepmConfiguration.ps1' -1

function Save-SepmConfiguration {
    <#
    .SYNOPSIS
        Serializes the provided settings object to disk as a JSON file.
 
    .DESCRIPTION
        Serializes the provided settings object to disk as a JSON file.
 
    .PARAMETER Configuration
        The configuration object to persist to disk.
 
    .PARAMETER Path
        The path to the file on disk that Configuration should be persisted to.
 
    .NOTES
        Internal helper method.
 
    .EXAMPLE
        Save-SepmConfiguration -Configuration $config -Path 'c:\foo\config.json'
 
        Serializes $config as a JSON object to 'c:\foo\config.json'
#>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject] $Configuration,

        [Parameter(Mandatory)]
        [string] $Path
    )

    if (-not $PSCmdlet.ShouldProcess('Sepm Configuration', 'Save')) {
        return
    }

    $null = New-Item -Path $Path -Force
    ConvertTo-Json -InputObject $Configuration |
        Set-Content -Path $Path -Force -ErrorAction SilentlyContinue -ErrorVariable ev

    if (($null -ne $ev) -and ($ev.Count -gt 0)) {
        $message = "Failed to persist these updated settings to disk. They will remain for this PowerShell session only."
        Write-Warning -Message $message
    }
}
#EndRegion '.\Private\Save-SepmConfiguration.ps1' 45
#Region '.\Private\Skip-Cert.ps1' -1

    function Skip-Cert {
        <#
    .SYNOPSIS
        This function allows skipping the SSL/TLS Secure channel check in the event that there is not a valid certificate available
    .DESCRIPTION
        This function allows skipping the SSL/TLS Secure channel check in the event that there is not a valid certificate available
    .NOTES
        Required for self-signed certificates skipping with Windows Powershell 5.1 and below
        This function is used internally by the module and should not be called directly
    .PARAMETER
    None
    .EXAMPLE
        Skip-Cert
    .OUTPUTS
        None
    #>

        if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) {
            $certCallback = @"
        using System;
        using System.Net;
        using System.Net.Security;
        using System.Security.Cryptography.X509Certificates;
        public class ServerCertificateValidationCallback
        {
            public static void Ignore()
            {
                if(ServicePointManager.ServerCertificateValidationCallback ==null)
                {
                    ServicePointManager.ServerCertificateValidationCallback +=
                        delegate
                        (
                            Object obj,
                            X509Certificate certificate,
                            X509Chain chain,
                            SslPolicyErrors errors
                        )
                        {
                            return true;
                        };
                }
            }
        }
"@

            Add-Type $certCallback
        }
        [ServerCertificateValidationCallback]::Ignore()
    }
#EndRegion '.\Private\Skip-Cert.ps1' 48
#Region '.\Private\Test-PropertyExists.ps1' -1

function Test-PropertyExists {
    <#
    .SYNOPSIS
        Determines if an object contains a property with a specified name.
 
    .DESCRIPTION
        Determines if an object contains a property with a specified name.
 
        This is essentially using Get-Member to verify that a property exists,
        but additionally adds a check to ensure that InputObject isn't null.
 
    .PARAMETER InputObject
        The object to check to see if it has a property named Name.
 
    .PARAMETER Name
        The name of the property on InputObject that is being tested for.
 
    .EXAMPLE
        Test-PropertyExists -InputObject $listing -Name 'title'
 
        Returns $true if $listing is non-null and has a property named 'title'.
        Returns $false otherwise.
 
    .NOTES
        Internal-only helper method.
#>

    [CmdletBinding()]
    [OutputType([bool])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Exists isn't a noun and isn't violating the intention of this rule.")]
    param(
        [Parameter(Mandatory)]
        [AllowNull()]
        $InputObject,

        [Parameter(Mandatory)]
        [String] $Name
    )

    return (($null -ne $InputObject) -and
            ($null -ne (Get-Member -InputObject $InputObject -Name $Name -MemberType Properties)))
}
#EndRegion '.\Private\Test-PropertyExists.ps1' 42
#Region '.\Private\Test-SEPMAccessToken.ps1' -1

function Test-SEPMAccessToken {
    <#
    .SYNOPSIS
        Test if the access token is still valid
    .DESCRIPTION
        Test if the access token is still valid.
        If no token is passed, will test the cached token.
 
        Returns $true if the token is still valid, $false otherwise
    .PARAMETER TokenInfo
        The token to test
    .OUTPUTS
        System.Boolean
    .NOTE
        Internal helper method.
        This function is used internally by the module and should not be called directly.
    #>

    
    
    param (
        [Alias('AccessToken', 'Token')]
        [PSCustomObject]$TokenInfo
    )

    # If no paramater is passed, test the cached token
    if ($null -eq $TokenInfo) {
        # if token in memory
        if (-not [string]::IsNullOrEmpty($script:accessToken.token) ) {
            # if token still valid
            if ($script:accessToken.tokenExpiration -gt (Get-Date)) {
                return $true
            } 
        }
    }

    # Check if the access token has expired
    if ($TokenInfo.tokenExpiration -gt (Get-Date)) {
        return $true
    }

    # If we get here, no valid token was found
    return $false
}
#EndRegion '.\Private\Test-SEPMAccessToken.ps1' 44
#Region '.\Private\Test-SEPMCertificate.ps1' -1

function Test-SEPMCertificate {
    <#
    .SYNOPSIS
        This function tests a webserver to see if it is using a self-signed certificate
    .DESCRIPTION
        This function tests a webserver to see if it is using a self-signed certificate
        If so, sets the $script:SkipCert variable to $true to continue with the connection
    .PARAMETER URI
        The URI of the webserver to test
    .INPUTS
        System.String
    .OUTPUTS
        None
    .EXAMPLE
        Test-SEPMCertificate -URI https://www.example.com
 
        Tests the webserver at https://www.example.com to see if it is using a self-signed certificate
    #>

    
    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $URI
    )
    
    try {
        # Test the certificate
        Invoke-WebRequest $URI # -AllowUnencryptedAuthentication

        # If no error, then the certificate is valid
        $script:SkipCert = $false
    } catch {
        if ($_.Exception.HttpRequestError -eq "SecureConnectionError") {
            # Get SEPM server name from URI
            $ServerName = (New-Object System.Uri($URI)).Host

            # Get the error message
            $message = "SSL Certificate test failed. The certificate for $ServerName is likely self-signed."
            Write-Warning -Message $message

            # Prompt for user input to continue
            # TODO add a remove option for user interaction with -skipcertificationcheck
            if ($PSVersionTable.PSVersion.Major -lt 6) {
                Skip-Cert
            }
            $script:SkipCert = $true
        } else {
            throw $_
        }
    }
}
#EndRegion '.\Private\Test-SEPMCertificate.ps1' 57
#Region '.\Public\Add-SEPMFileFingerprintList.ps1' -1

function Add-SEPMFileFingerprintList {
    <#
    .SYNOPSIS
        Adds a blacklist as a file fingerprint list
    .DESCRIPTION
        Adds a blacklist as a file fingerprint list
    .PARAMETER name
        The name of the blacklist to be added
    .PARAMETER domainId
        The domain id of the domain to add the blacklist to
        Only takes the domain id. Can be found using Get-SEPMDomain
    .PARAMETER HashType
        The type of hash to use for the blacklist
        Valid values are SHA256 and MD5
    .PARAMETER description
        The description of the blacklist
    .PARAMETER hashlist
        The hash list to add to the blacklist
        Can be generated using Get-FileHash or takes a string array of hashes
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        $DomainId = Get-SEPMDomain | Where-Object { $_.name -eq "Default" }
        $HashList = ls -file C:\Users\$env:USERNAME\Downloads\*.exe | Get-FileHash -algorithm SHA256
        Add-SEPMFileFingerprintList -name "My Blacklist" -domainId $domainId -HashType "SHA256" -description "My Blacklist" -hashlist $hashlist.hash
 
        Gets the domain id for the default domain
        Create a hash list of all the .exe files in the downloads folder of the currently logged in user
        Adds the hash list as a blacklist to the default domain
#>


    [CmdletBinding()]
    param (
        [Parameter()]
        [string]$name,

        [Parameter()]
        [string]$domainId,

        [Parameter()]
        [ValidateSet('SHA256', 'MD5')]
        [string]$HashType,

        [Parameter()]
        [string]$description,

        [Parameter()]
        $hashlist,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        $URI = $script:BaseURLv1 + "/policy-objects/fingerprints"

        # Construct the body & required fields
        $body = @{
            name        = $name
            domainId    = $domainId
            hashType    = $HashType
            description = $description
            data        = $hashlist
        }

        $params = @{
            Method      = 'POST'
            Uri         = $URI
            headers     = $headers
            Body        = $body | ConvertTo-Json
            ContentType = 'application/json'
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Add-SEPMFileFingerprintList.ps1' 95
#Region '.\Public\Add-SEPMMacFileException.ps1' -1

function Add-SEPMMacFileException {

    <# TODO update help
    .SYNOPSIS
        Add a Mac File Exception to a Symantec Endpoint Protection Manager Policy
    .DESCRIPTION
        Add a Windows File Exception to a Symantec Endpoint Protection Manager Policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Path
        Path to add to the exception list
    .PARAMETER PathVariable
        Path variable to use for the path
    .PARAMETER Sonar
        Add the exception to the SONAR exclusions
    .PARAMETER SecurityRiskCategory
        Add the exception to the Security Risk exclusions
        Takes the following values:
            AllScans
            AutoProtect
            ScheduledAndOndemand
    .PARAMETER SkipCertificateCheck
        Skip the certificate check when connecting to the SEPM
    .EXAMPLE
        Add-SEPMMacFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -Sonar
 
        Exclude the file C:\Temp\file1.exe from SONAR scans in the policy Default
    .EXAMPLE
        Add-SEPMMacFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -SecurityRiskCategory "AllScans"
 
        Exclude the file C:\Temp\file1.exe from all Security Risk scans in the policy Default
    .EXAMPLE
        Add-SEPMMacFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -ApplicationControl
 
        Exclude the file C:\Temp\file1.exe from Application Control scans in the policy Default
    .EXAMPLE
        Add-SEPMMacFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -ApplicationControl -ExcludeChildProcesses
 
        Exclude the file C:\Temp\file1.exe from Application Control scans in the policy Default and exclude child processes
    .EXAMPLE
        Add-SEPMMacFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -AllScans
 
        Exclude the file C:\Temp\file1.exe from all scan types in the policy Default (SONAR / AutoProtect / Scheduled scans / Application Control)
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Scancategory - requires securityrisk to be set to true
        [ValidateSet(
            'AllScans',
            'AutoProtect',
            'ScheduledAndOndemand'
        )]
        [ValidateScript({
                if ($ScanType -ne "SecurityRisk") {
                    throw "The -SecurityRiskCategory parameter requires the -ScanType parameter to be set to 'SecurityRisk'."
                }
                return $true
            })]
        [string] 
        $SecurityRiskCategory,

        # Pathvariable
        [ValidateSet(
            '[NONE]', 
            '[HOME]', 
            '[APPLICATION]', 
            '[LIBRARY ]'
        )]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("^/([^/ ]+(/|$))+[^/ ]+\.[^/ ]+$")]
        [string] 
        $Path,

        # ScanType
        [ValidateSet(
            'SecurityRisk',
            'SONAR',
            'All'
        )]
        [string]
        $ScanType = 'All'
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExceptionParams.path = $path
        $ExceptionParams.pathvariable = $PathVariable
        $ExceptionParams.RulestateSource = $script:ModuleName

        # API does not support seem to support scan type for Mac exceptions
        # TODO: Test for scan types and report potential issue to Symantec

        # # Parse the optional parameters
        # switch ($PSBoundParameters.Keys) {
        # "SecurityRiskCategory" {
        # $ExceptionParams.securityrisk = $true
        # $ExceptionParams.scancategory = $SecurityRiskCategory
        # }
        # }

        # Create the file exception object with CreateFilesHashTable
        # Method parameters have to be in the same order as in the method definition
        $DirectoryHashTable = $Policy.ObjBody.CreateMacFilesHashtable(
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.pathvariable,
            $ExceptionParams.path
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddMacFiles($DirectoryHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Add-SEPMMacFileException.ps1' 174
#Region '.\Public\Add-SEPMWindowsExtensionException.ps1' -1

function Add-SEPMWindowsExtensionException {

    <#
    .SYNOPSIS
        Add a Windows extension exception to a SEPM policy
    .DESCRIPTION
        Add a Windows extension exception to a SEPM policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Extension
        Extension to add to the exception list
        Accepts multiple values
    .PARAMETER ScanType
        Type of scan to apply the exception to
        Valid values are:
            AllScans
            AutoProtect
            ScheduledAndOndemand
    .EXAMPLE
        Add-SEPMWindowsExtensionException -PolicyName "Workstations Default Exception Policy" -Extension tmp,tmp2
 
        Add 2 Windows extensions exception, tmp and tmp2 to the "Workstations Default Exception Policy" policy
    .EXAMPLE
        Add-SEPMWindowsExtensionException -PolicyName "Workstations Default Exception Policy" -Extension tmp,tmp2 -ScanType AutoProtect
 
        Add 2 Windows extensions exception, tmp and tmp2 to the "Workstations Default Exception Policy" policy, for AutoProtect scans
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Extension
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Extension,

        # Security Risk type
        [ValidateSet(
            'AllScans',
            'AutoProtect',
            'ScheduledAndOndemand'
        )]
        [string] 
        $ScanType = 'AllScans'
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Gather current extension exception list
        $PolicyExtList = (Get-SEPMExceptionPolicy -PolicyName $PolicyName).configuration.extension_list.extensions

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExtensionList = @()
        $ExceptionParams.RulestateSource = $script:ModuleName
        $ExceptionParams.scancategory = $ScanType

        # Add extension to the current list (as the API update call overwrites the list)
        $ExtensionList += $Extension
        $ExtensionList += $PolicyExtList
        $ExtensionList = $ExtensionList | Sort-Object | Get-Unique # Avoid duplicates
        $ExceptionParams.extensions += $ExtensionList

        # Create the file exception object with CreateExtensionListHashtable
        # Method parameters have to be in the same order as in the method definition
        $ExtensionHashTable = $Policy.ObjBody.CreateExtensionListHashtable(
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.extensions
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddExtensionsList($ExtensionHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Add-SEPMWindowsExtensionException.ps1' 132
#Region '.\Public\Add-SEPMWindowsFileException.ps1' -1

function Add-SEPMWindowsFileException {

    <#
    .SYNOPSIS
        Add a Windows File Exception to a Symantec Endpoint Protection Manager Policy
    .DESCRIPTION
        Add a Windows File Exception to a Symantec Endpoint Protection Manager Policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Path
        Path to add to the exception list
    .PARAMETER PathVariable
        Path variable to use for the path
    .PARAMETER Sonar
        Add the exception to the SONAR exclusions
    .PARAMETER SecurityRiskCategory
        Add the exception to the Security Risk exclusions
        Takes the following values:
            AllScans
            AutoProtect
            ScheduledAndOndemand
    .PARAMETER ApplicationControl
        Add the exception to the Application Control exclusions
    .PARAMETER ExcludeChildProcesses
        Exclude child processes from the Application Control exclusions
        Requires ApplicationControl to be set to true
    .PARAMETER SkipCertificateCheck
        Skip the certificate check when connecting to the SEPM
    .PARAMETER AllScans
        Add the exception to all scan types
        Equivalent to setting Sonar, SecurityRiskCategory and ApplicationControl to true
        If no scan type is provided, default to AllScans
    .EXAMPLE
        Add-SEPMWindowsFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -Sonar
 
        Exclude the file C:\Temp\file1.exe from SONAR scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -SecurityRiskCategory "AllScans"
 
        Exclude the file C:\Temp\file1.exe from all Security Risk scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -ApplicationControl
 
        Exclude the file C:\Temp\file1.exe from Application Control scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -ApplicationControl -ExcludeChildProcesses
 
        Exclude the file C:\Temp\file1.exe from Application Control scans in the policy Default and exclude child processes
    .EXAMPLE
        Add-SEPMWindowsFileException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -AllScans
 
        Exclude the file C:\Temp\file1.exe from all scan types in the policy Default (SONAR / AutoProtect / Scheduled scans / Application Control)
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Sonar
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [switch]
        $Sonar,

        # Scancategory - requires securityrisk to be set to true
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [ValidateSet(
            'AllScans',
            'AutoProtect',
            'ScheduledAndOndemand'
        )]
        [string] 
        $SecurityRiskCategory,

        # Pathvariable
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [ValidateSet(
            '[NONE]', 
            '[COMMON_APPDATA]', 
            '[COMMON_DESKTOPDIRECTORY]', 
            '[COMMON_DOCUMENTS]', 
            '[COMMON_PROGRAMS]', 
            '[COMMON_STARTUP]', 
            '[PROGRAM_FILES]', 
            '[PROGRAM_FILES_COMMON]', 
            '[SYSTEM]', 
            '[SYSTEM_DRIVE]', 
            '[USER_PROFILE]', 
            '[WINDOWS]'
        )]
        [Alias('WindowsPathVariable')]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(ParameterSetName = 'WindowsFileException', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("^[A-Za-z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*[^\\/:*?""<>|\r\n]+\.[^\\/:*?""<>|\r\n]+$")]
        [Alias('WindowsPath')]
        [string] 
        $Path,

        # Applicationcontrol
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [switch]
        $ApplicationControl,

        # AllScans
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [switch]
        $AllScans,

        # Recursive - requires applicationcontrol to be set to true
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [ValidateScript({
                if (-not $ApplicationControl) {
                    throw "-ExcludeChildProcesses exception requires the -ApplicationControl switch to be set."
                }
                return $true
            })]
        [switch]
        $ExcludeChildProcesses
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExceptionParams.path = $path
        $ExceptionParams.pathvariable = $PathVariable
        $ExceptionParams.RulestateSource = $script:ModuleName

        # Parse the optional parameters
        switch ($PSBoundParameters.Keys) {
            "Sonar" {
                $ExceptionParams.sonar = $true
            }
            "DeleteException" {
                $ExceptionParams.deleted = $true
            }
            "SecurityRiskCategory" {
                $ExceptionParams.securityrisk = $true
                $ExceptionParams.scancategory = $SecurityRiskCategory
            }
            "ApplicationControl" {
                $ExceptionParams.applicationcontrol = $true
            }
            "ExcludeChildProcesses" {
                $ExceptionParams.applicationcontrol = $true
                $ExceptionParams.recursive = $true
            }
            "AllScans" {
                $ExceptionParams.securityrisk = $true
                $ExceptionParams.sonar = $true
                $ExceptionParams.applicationcontrol = $true
                $ExceptionParams.scancategory = "AllScans"
            }
        }

        # If no scan type is provided, default to AllScans
        if (-not $ExceptionParams.securityrisk -and -not $ExceptionParams.sonar -and -not $ExceptionParams.applicationcontrol) {
            $ExceptionParams.securityrisk = $true
            $ExceptionParams.sonar = $true
            $ExceptionParams.applicationcontrol = $true
            $ExceptionParams.scancategory = "AllScans"
        }

        # Create the file exception object with CreateFilesHashTable
        # Method parameters have to be in the same order as in the method definition
        $FilesHashTable = $Policy.ObjBody.CreateFilesHashTable(
            $ExceptionParams.sonar,
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.pathvariable,
            $ExceptionParams.path,
            $ExceptionParams.applicationcontrol,
            $ExceptionParams.securityrisk,
            $ExceptionParams.recursive
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddConfigurationFilesExceptions($FilesHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Add-SEPMWindowsFileException.ps1' 235
#Region '.\Public\Add-SEPMWindowsFolderException.ps1' -1

function Add-SEPMWindowsFolderException {

    <#
    .SYNOPSIS
        Add a Windows Folder Exception to a Symantec Endpoint Protection Manager Policy
    .DESCRIPTION
        Add a Windows Folder Exception to a Symantec Endpoint Protection Manager Policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Path
        Path to add to the exception list
        Verify that the path is valid before adding it to the exception list
    .PARAMETER PathVariable
        Path variable to use for the path
    .PARAMETER Sonar
        Add the exception to the SONAR exclusions
    .PARAMETER SecurityRiskCategory
        Add the exception to the Security Risk exclusions
        Takes the following values:
            AllScans
            AutoProtect
            ScheduledAndOndemand
    .PARAMETER ApplicationControl
        Add the exception to the Application Control exclusions
    .PARAMETER ExcludeChildProcesses
        Exclude child processes from the Application Control exclusions
        Requires ApplicationControl to be set to true
    .PARAMETER SkipCertificateCheck
        Skip the certificate check when connecting to the SEPM
    .PARAMETER AllScans
        Add the exception to all scan types
        Equivalent to setting Sonar, SecurityRiskCategory and ApplicationControl to true
        If no scan type is provided, default to AllScans
    .EXAMPLE
        Add-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp" -Sonar
 
        Exclude the folder C:\Temp from SONAR scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp" -SecurityRiskCategory "AllScans"
 
        Exclude the folder C:\Temp from all Security Risk scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp" -ApplicationControl
 
        Exclude the folder C:\Temp from Application Control scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp" -ApplicationControl -ExcludeChildProcesses
 
        Exclude the folder C:\Temp from Application Control scans in the policy Default and exclude child processes
    .EXAMPLE
        Add-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp" -AllScans
 
        Exclude the folder C:\Temp from all scan types in the policy Default (SONAR / AutoProtect / Scheduled scans / Application Control)
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Scancategory - requires securityrisk to be set to true
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [ValidateSet(
            'AllScans',
            'AutoProtect',
            'ScheduledAndOndemand'
        )]
        [ValidateScript({
                if ($ScanType -ne "SecurityRisk") {
                    throw "The -SecurityRiskCategory parameter requires the -ScanType parameter to be set to 'SecurityRisk'."
                }
                return $true
            })]
        [string] 
        $SecurityRiskCategory,

        # Pathvariable
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [ValidateSet(
            '[NONE]', 
            '[COMMON_APPDATA]', 
            '[COMMON_DESKTOPDIRECTORY]', 
            '[COMMON_DOCUMENTS]', 
            '[COMMON_PROGRAMS]', 
            '[COMMON_STARTUP]', 
            '[PROGRAM_FILES]', 
            '[PROGRAM_FILES_COMMON]', 
            '[SYSTEM]', 
            '[SYSTEM_DRIVE]', 
            '[USER_PROFILE]', 
            '[WINDOWS]'
        )]
        [Alias('WindowsPathVariable')]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(ParameterSetName = 'WindowsFolderException', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("^[A-Za-z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*")]
        [Alias('WindowsPath')]
        [string] 
        $Path,

        # Recursive - requires applicationcontrol to be set to true
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [switch]
        $IncludeSubFolders,

        # ScanType
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [ValidateSet(
            'SecurityRisk',
            'SONAR',
            'ApplicationControl',
            'All'
        )]
        [string]
        $ScanType = 'All'
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExceptionParams.directory = $path
        $ExceptionParams.pathvariable = $PathVariable
        $ExceptionParams.RulestateSource = $script:ModuleName

        # Parse the optional parameters
        switch ($PSBoundParameters.Keys) {
            "SecurityRiskCategory" {
                $ExceptionParams.securityrisk = $true
                $ExceptionParams.scancategory = $SecurityRiskCategory
            }
            "IncludeSubFolders" {
                $ExceptionParams.recursive = $true
            }
        }

        # If no scan type is provided, default to AllScans
        if (-not $ExceptionParams.scantype) {
            $ExceptionParams.scantype = "All"
        }

        # Create the file exception object with CreateFilesHashTable
        # Method parameters have to be in the same order as in the method definition
        $DirectoryHashTable = $Policy.ObjBody.CreateDirectoryHashtable(
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.scantype,
            $ExceptionParams.pathvariable,
            $ExceptionParams.directory,
            $ExceptionParams.recursive
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddConfigurationDirectoriesExceptions($DirectoryHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Add-SEPMWindowsFolderException.ps1' 211
#Region '.\Public\Add-SEPMWindowsTamperProtectionException.ps1' -1

function Add-SEPMWindowsTamperProtectionException {

    <#
    .SYNOPSIS
        Add aTamper Protection Exception to a Symantec Endpoint Protection Manager Policy
    .DESCRIPTION
        Add aTamper Protection Exception to a Symantec Endpoint Protection Manager Policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Path
        Path to add to the exception list
    .PARAMETER PathVariable
        Path variable to use for the path
        Following values are allowed:
            [NONE]
            [COMMON_APPDATA]
            [COMMON_DESKTOPDIRECTORY]
            [COMMON_DOCUMENTS]
            [COMMON_PROGRAMS]
            [COMMON_STARTUP]
            [PROGRAM_FILES]
            [PROGRAM_FILES_COMMON]
            [SYSTEM]
            [SYSTEM_DRIVE]
            [USER_PROFILE]
            [WINDOWS]
    .EXAMPLE
        Add-SEPMWindowsTamperProtectionException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe"
 
        Exclude the file C:\Temp\file1.exe from Tamper Protection scans in the policy Default
    .EXAMPLE
        Add-SEPMWindowsTamperProtectionException -PolicyName "Workstations Default Exception Policy" -Path ".gitconfig -PathVariable [USER_PROFILE]
 
        Exclude the file .gitconfig located in the %USERPROFILE% from Tamper Protection scans in the policy Default
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Pathvariable
        [ValidateSet(
            '[NONE]', 
            '[COMMON_APPDATA]', 
            '[COMMON_DESKTOPDIRECTORY]', 
            '[COMMON_DOCUMENTS]', 
            '[COMMON_PROGRAMS]', 
            '[COMMON_STARTUP]', 
            '[PROGRAM_FILES]', 
            '[PROGRAM_FILES_COMMON]', 
            '[SYSTEM]', 
            '[SYSTEM_DRIVE]', 
            '[USER_PROFILE]', 
            '[WINDOWS]'
        )]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("^[A-Za-z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*[^\\/:*?""<>|\r\n]+\.[^\\/:*?""<>|\r\n]+$")]
        [Alias('WindowsPath')]
        [string] 
        $Path
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExceptionParams.path = $path
        $ExceptionParams.pathvariable = $PathVariable
        $ExceptionParams.RulestateSource = $script:ModuleName

        # Create the Tamper Protection exception object with CreateTamperFilesHashtable
        # Method parameters have to be in the same order as in the method definition
        $TamperHashTable = $Policy.ObjBody.CreateTamperFilesHashtable(
            $ExceptionParams.sonar,
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.pathvariable,
            $ExceptionParams.path,
            $ExceptionParams.applicationcontrol,
            $ExceptionParams.securityrisk,
            $ExceptionParams.recursive
        )

        # Add the tamper exception parameters to the body structure
        $Policy.ObjBody.AddTamperFiles($TamperHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Add-SEPMWindowsTamperProtectionException.ps1' 144
#Region '.\Public\Backup-SEPMAuthentication.ps1' -1

function Backup-SEPMAuthentication {
    <#
    .SYNOPSIS
        Exports the user's current authentication file.
 
    .DESCRIPTION
        Exports the user's current authentication file.
 
        This is primarily used for unit testing scenarios.
 
    .PARAMETER Path
        The path to store the user's current authentication file.
 
    .PARAMETER Force
        If specified, will overwrite the contents of any file with the same name at the
        location specified by Path.
 
    .EXAMPLE
        Backup-SEPMAuthentication -Path 'c:\foo\credentials.xml'
 
        Writes the user's current authentication file to c:\foo\credentials.xml.
#>

    [CmdletBinding()]
    param(
        [string] $Path,

        [switch] $Force,

        [switch] $Credentials,

        [switch] $AccessToken
    )

    # Make sure that the path that we're going to be storing the file exists.
    $null = New-Item -Path (Split-Path -Path $Path -Parent) -ItemType Directory -Force

    if ($Credentials) {
        if (Test-Path -Path $script:credentialsFilePath -PathType Leaf) {
            $null = Copy-Item -Path $script:credentialsFilePath -Destination $Path -Force:$Force
        }
    }

    if ($AccessToken) {
        if (Test-Path -Path $script:accessTokenFilePath -PathType Leaf) {
            $null = Copy-Item -Path $script:accessTokenFilePath -Destination $Path -Force:$Force
        }
    }
}
#EndRegion '.\Public\Backup-SEPMAuthentication.ps1' 49
#Region '.\Public\Backup-SEPMConfiguration.ps1' -1

function Backup-SEPMConfiguration {
    <#
    .SYNOPSIS
        Exports the user's current configuration file.
 
    .DESCRIPTION
        Exports the user's current configuration file.
 
        This is primarily used for unit testing scenarios.
 
    .PARAMETER Path
        The path to store the user's current configuration file.
 
    .PARAMETER Force
        If specified, will overwrite the contents of any file with the same name at the
        location specified by Path.
 
    .EXAMPLE
        Backup-SEPMConfiguration -Path 'c:\foo\config.json'
 
        Writes the user's current configuration file to c:\foo\config.json.
#>

    [CmdletBinding()]
    param(
        [string] $Path,

        [switch] $Force
    )

    # Make sure that the path that we're going to be storing the file exists.
    $null = New-Item -Path (Split-Path -Path $Path -Parent) -ItemType Directory -Force

    if (Test-Path -Path $script:configurationFilePath -PathType Leaf) {
        $null = Copy-Item -Path $script:configurationFilePath -Destination $Path -Force:$Force
    } else {
        ConvertTo-Json -InputObject @{} | Set-Content -Path $Path -Force:$Force
    }
}
#EndRegion '.\Public\Backup-SEPMConfiguration.ps1' 39
#Region '.\Public\Clear-SepmAuthentication.ps1' -1

function Clear-SEPMAuthentication {
    <#
    .SYNOPSIS
        Clears out any API token from memory, as well as from local file storage.
 
    .DESCRIPTION
        Clears out any API token from memory, as well as from local file storage.
 
    .EXAMPLE
        Clear-SEPMAuthentication
 
        Clears out any API token from memory, as well as from local file storage.
 
    .NOTES
        This command will not clear your configuration settings.
        Please use Reset-SEPMConfiguration to accomplish that.
#>

    [CmdletBinding()]
    param()

    # Clear out Credential and AccessToken variables from memory
    $script:Credential = $null
    $script:accessToken = $null

    # Remove file that stores the Access Token
    Remove-Item -Path $script:accessTokenFilePath -ErrorAction SilentlyContinue -Force -ErrorVariable ev
    Remove-Item -Path $script:credentialsFilePath -ErrorAction SilentlyContinue -Force -ErrorVariable ev

    if (($null -ne $ev) -and
            ($ev.Count -gt 0) -and
            ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) {
        $message = "Experienced a problem trying to remove the file that persists the Access Token [$script:credentialsFilePath]."
        Write-Warning -Message $message
    }

    $message = "This has not cleared your configuration settings. Call Reset-SEPMConfiguration to accomplish that."
    Write-Verbose -Message $message
}
#EndRegion '.\Public\Clear-SepmAuthentication.ps1' 39
#Region '.\Public\Confirm-SEPMEventInfo.ps1' -1

function Confirm-SEPMEventInfo {
    <# # TODO add examples once finished
    .SYNOPSIS
        Post Acknowledgement For Notification
    .DESCRIPTION
        Acknowledges a specified event for a given event ID.
        A system administrator account is required for this REST API.
    .PARAMETER EventID
        The event ID to acknowledge.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> $SEPMEvents = Confirm-SEPMEventInfo -eventID 30D8A67F0A6606220DEB5989DC3FAC50
#>


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

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/events/acknowledge/$eventID"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        $params = @{
            Method  = 'POST'
            Uri     = $URI
            headers = $headers
        }
        
        $resp = Invoke-ABRestMethod -params $params

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Confirm-SEPMEventInfo.ps1' 59
#Region '.\Public\ConvertTo-FlatObject.ps1' -1

# From https://github.com/EvotecIT/PSSharedGoods/tree/master/Public/Converts
Function ConvertTo-FlatObject {
    <#
    .SYNOPSIS
    Flattends a nested object into a single level object.
 
    .DESCRIPTION
    Flattends a nested object into a single level object.
 
    .PARAMETER Objects
    The object (or objects) to be flatten.
 
    .PARAMETER Separator
    The separator used between the recursive property names
 
    .PARAMETER Base
    The first index name of an embedded array:
    - 1, arrays will be 1 based: <Parent>.1, <Parent>.2, <Parent>.3, …
    - 0, arrays will be 0 based: <Parent>.0, <Parent>.1, <Parent>.2, …
    - "", the first item in an array will be unnamed and than followed with 1: <Parent>, <Parent>.1, <Parent>.2, …
 
    .PARAMETER Depth
    The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop.
 
    .PARAMETER Uncut
    The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop.
 
    .PARAMETER ExcludeProperty
    The propertys to be excluded from the output.
 
    .EXAMPLE
    $Object3 = [PSCustomObject] @{
        "Name" = "Przemyslaw Klys"
        "Age" = "30"
        "Address" = @{
            "Street" = "Kwiatowa"
            "City" = "Warszawa"
 
            "Country" = [ordered] @{
                "Name" = "Poland"
            }
            List = @(
                [PSCustomObject] @{
                    "Name" = "Adam Klys"
                    "Age" = "32"
                }
                [PSCustomObject] @{
                    "Name" = "Justyna Klys"
                    "Age" = "33"
                }
                [PSCustomObject] @{
                    "Name" = "Justyna Klys"
                    "Age" = 30
                }
                [PSCustomObject] @{
                    "Name" = "Justyna Klys"
                    "Age" = $null
                }
            )
        }
        ListTest = @(
            [PSCustomObject] @{
                "Name" = "SÅ‚awa Klys"
                "Age" = "33"
            }
        )
    }
 
    $Object3 | ConvertTo-FlatObject
 
    .NOTES
    Based on https://powersnippets.com/convertto-flatobject/
    #>

    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeLine)][Object[]]$Objects,
        [String]$Separator = ".",
        [ValidateSet("", 0, 1)]$Base = 1,
        [int]$Depth = 5,
        [string[]] $ExcludeProperty,
        [Parameter(DontShow)][String[]]$Path,
        [Parameter(DontShow)][System.Collections.IDictionary] $OutputObject
    )
    Begin {
        $InputObjects = [System.Collections.Generic.List[Object]]::new()
    }
    Process {
        foreach ($O in $Objects) {
            if ($null -ne $O) {
                $InputObjects.Add($O)
            }
        }
    }
    End {
        If ($PSBoundParameters.ContainsKey("OutputObject")) {
            $Object = $InputObjects[0]
            $Iterate = [ordered] @{}
            if ($null -eq $Object) {
                #Write-Verbose -Message "ConvertTo-FlatObject - Object is null"
            } elseif ($Object.GetType().Name -in 'String', 'DateTime', 'TimeSpan', 'Version', 'Enum') {
                $Object = $Object.ToString()
            } elseif ($Depth) {
                $Depth--
                If ($Object -is [System.Collections.IDictionary]) {
                    $Iterate = $Object
                } elseif ($Object -is [Array] -or $Object -is [System.Collections.IEnumerable]) {
                    $i = $Base
                    foreach ($Item in $Object.GetEnumerator()) {
                        $NewObject = [ordered] @{}
                        If ($Item -is [System.Collections.IDictionary]) {
                            foreach ($Key in $Item.Keys) {
                                if ($Key -notin $ExcludeProperty) {
                                    $NewObject[$Key] = $Item[$Key]
                                }
                            }
                        } elseif ($Item -isnot [Array] -and $Item -isnot [System.Collections.IEnumerable]) {
                            foreach ($Prop in $Item.PSObject.Properties) {
                                if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) {
                                    $NewObject["$($Prop.Name)"] = $Item.$($Prop.Name)
                                }
                            }
                        } else {
                            $NewObject = $Item
                        }
                        $Iterate["$i"] = $NewObject
                        $i += 1
                    }
                } else {
                    foreach ($Prop in $Object.PSObject.Properties) {
                        if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) {
                            $Iterate["$($Prop.Name)"] = $Object.$($Prop.Name)
                        }
                    }
                }
            }
            If ($Iterate.Keys.Count) {
                foreach ($Key in $Iterate.Keys) {
                    if ($Key -notin $ExcludeProperty) {
                        ConvertTo-FlatObject -Objects @(, $Iterate["$Key"]) -Separator $Separator -Base $Base -Depth $Depth -Path ($Path + $Key) -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty
                    }
                }
            } else {
                $Property = $Path -Join $Separator
                if ($Property) {
                    # We only care if property is not empty
                    if ($Object -is [System.Collections.IDictionary] -and $Object.Keys.Count -eq 0) {
                        $OutputObject[$Property] = $null
                    } else {
                        $OutputObject[$Property] = $Object
                    }
                }
            }
        } elseif ($InputObjects.Count -gt 0) {
            foreach ($ItemObject in $InputObjects) {
                $OutputObject = [ordered]@{}
                ConvertTo-FlatObject -Objects @(, $ItemObject) -Separator $Separator -Base $Base -Depth $Depth -Path $Path -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty
                [PSCustomObject] $OutputObject
            }
        }
    }
}
#EndRegion '.\Public\ConvertTo-FlatObject.ps1' 162
#Region '.\Public\Export-SEPMExceptionPolicyToExcel.ps1' -1

function Export-SEPMExceptionPolicyToExcel {
    <# TODO update help
    .SYNOPSIS
        A short one-line action-based description, e.g. 'Tests if a function is valid'
    .DESCRIPTION
        A longer description of the function, its purpose, common use cases, etc.
    .NOTES
        Information or caveats about the function e.g. 'This function is not supported in Linux'
    .LINK
        Specify a URI to a help page, this will show when Get-Help -Online is used.
    .EXAMPLE
        Test-MyTestFunction -Verbose
        Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
    #>

    
    
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,
        
        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,
        
        # Path
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
                # Validate the path
                if (-Not (Split-Path $_ -Parent | Test-Path)) {
                    throw "Directory of `$_` does not exist"
                }
                # Validate the file extension
                if (-Not ($_ -match '\.xlsx$')) {
                    throw "File `$_` does not have the .xlsx extension"
                }
                return $true
            })]
        [string]
        $Path
    )

    begin {

    }

    process {
        # Get Exception policy object
        $ExceptionPolicy = Get-SEPMExceptionPolicy -PolicyName $PolicyName -SkipCertificateCheck:$SkipCertificateCheck

        # Verify the PSObject typename is "SEPM.ExceptionPolicy"
        if ($ExceptionPolicy.PSObject.TypeNames[0] -ne "SEPM.ExceptionPolicy") {
            $message = "The policy name provided is not of type Exception Policy or does not exist - Please verify the policy name"
            Write-Error -Message $message
            throw $message
        }

        # Add the categories to the PSObject
        $ExceptionCategory = [PSCustomObject]@{
            Files           = $ExceptionPolicy.configuration.files
            Folders         = $ExceptionPolicy.configuration.directories
            Certificates    = $ExceptionPolicy.configuration.certificates
            Tamper_files    = $ExceptionPolicy.configuration.tamper_files
            Webdomain       = $ExceptionPolicy.configuration.webdomains
            Mac             = $ExceptionPolicy.configuration.mac.files
            Linux           = $ExceptionPolicy.configuration.linux.directories
            Linux_Extension = $ExceptionPolicy.configuration.linux.extension_list
            KnownRisks      = $ExceptionPolicy.configuration.knownrisks
        }

        # Define the properties to export
        $Props = [PSCustomObject]@{
            Files           = @("scancategory", "pathvariable", "path", "SONAR", "applicationcontrol", "securityrisk", "recursive")
            Folders         = @("scancategory", "scantype", "pathvariable", "directory", "recursive")
            Certificates    = @("*")
            Tamper_files    = @("pathvariable", "path")
            Webdomain       = @("domain")
            Mac             = @("pathvariable", "path")
            Linux           = @("scancategory", "pathvariable", "directory", "recursive")
            Linux_Extension = @("*")
            KnownRisks      = @("threat.id", "threat.name", "action")
        }

        # Define Excel export parameters
        $excel_params = @{
            ClearSheet   = $true
            BoldTopRow   = $true
            AutoSize     = $true
            FreezeTopRow = $true
            AutoFilter   = $true
        }

        # Export the data to Excel
        foreach ($category in $ExceptionCategory.PSObject.Properties.Name) {
            # Only export the category if it has data
            if ($ExceptionCategory.$category) {
                # Special case for Extensions. Split in an array of objects for correct formating
                if ($category -eq "Linux_Extension") {
                    $Extensions = @()
                    foreach ($line in $ExceptionCategory.Linux_Extension.extensions) {
                        $obj = New-Object -TypeName PSObject
                        $obj | Add-Member -MemberType NoteProperty -Name Extensions -Value $line
                        $Extensions += $obj
                    }
                    $Extensions | Select-Object -Property $Props.$category | Export-Excel -Path $Path -WorksheetName $category @excel_params
                    continue
                }
                $ExceptionCategory.$category | ConvertTo-FlatObject | Select-Object -Property $Props.$category | Export-Excel -Path $Path -WorksheetName $category @excel_params
            }
        }
    }
}
#EndRegion '.\Public\Export-SEPMExceptionPolicyToExcel.ps1' 119
#Region '.\Public\Get-SEPClientDefVersions.ps1' -1

function Get-SEPClientDefVersions {
    <#
    .SYNOPSIS
        Gets a list of clients for a group by content version.
    .DESCRIPTION
        Gets a list of clients for a group by content version.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPClientDefVersions
 
        version clientsCount
        ------- ------------
        2023-09-04 rev. 002 15
        2023-09-03 rev. 002 4
        2023-09-01 rev. 008 2
        2023-08-31 rev. 021 2
        2023-08-31 rev. 002 1
        2023-08-29 rev. 003 1
 
        Gets a list of clients grouped by content version.
    .EXAMPLE
        PS C:\PSSymantecSEPM> $definitionVersions = Get-SEPClientDefVersions
        PS C:\PSSymantecSEPM> $definitionVersions
 
        version clientsCount
        ------- ------------
        2023-09-04 rev. 002 15
        2023-09-03 rev. 002 4
        2023-09-01 rev. 008 2
        2023-08-31 rev. 021 2
        2023-08-31 rev. 002 1
        2023-08-29 rev. 003 1
 
        PS C:\PSSymantecSEPM> ($definitionVersions | Where-Object version -eq "2023-09-03 rev. 002").GetComputerWithThisDefinition()
 
        computerName ipAddresses GroupName
        ------------ ----------- ---------
        Computer123 {10.0.70.126} My Company\_Americas\Workstations
        Computer124 {10.1.19.127} My Company\_EMEA\Workstations
        Computer125 {10.5.125.128} My Company\_APAC\Workstations
        Computer126 {10.9.38.110} My Company\_LATAM\Workstations
 
        Gets a list of clients grouped by content version
        Then gets the list of computers with a specified content version (2023-09-03 rev. 002), using the GetComputerWithThisDefinition() method.
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/stats/client/content"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.clientDefStatusList | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEP.clientDefStatusList")
        }

        return $resp.clientDefStatusList
    }
}
#EndRegion '.\Public\Get-SEPClientDefVersions.ps1' 90
#Region '.\Public\Get-SEPClientInfectedStatus.ps1' -1

function Get-SEPClientInfectedStatus {
    <#
    .SYNOPSIS
        Gets SEP Clients with Infected or Clean status
    .DESCRIPTION
        Gets SEP Clients with Infected or Clean status
        NOTES : Clean status is just Infected = 0
 
    .INPUTS
        None
    .OUTPUTS
        List of SEP Clients with Infected status
    .PARAMETER Clean
        If specified, returns SEP Clients with Clean status
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        Get-SEPClientInfectedStatus
 
        Gets computer details for all computers in the domain
    .EXAMPLE
        Get-SEPClientInfectedStatus -Clean
 
        Gets computer details for all computers in the domain that are not infected
#>


    [CmdletBinding()]
    param (
        [Parameter()]
        [switch]
        $Clean,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
    }

    process {
        if ($clean) {
            $non_infected = Get-SEPComputers | Where-Object { $_.infected -ne 1 }
            return $non_infected
        } else {
            $infected = Get-SEPComputers | Where-Object { $_.infected -eq 1 }
            return $Infected
        }
    }
}
#EndRegion '.\Public\Get-SEPClientInfectedStatus.ps1' 55
#Region '.\Public\Get-SEPClientStatus.ps1' -1

function Get-SEPClientStatus {
    <#
    .SYNOPSIS
        Gets a list and count of the online and offline clients.
    .DESCRIPTION
        Gets a list and count of the online and offline clients.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        C:\PSSymantecSEPM> Get-SEPClientStatus
 
        lastUpdated clientCountStatsList
        ----------- --------------------
        1693910248728 {@{status=ONLINE; clientsCount=212}, @{status=OFFLINE; clientsCount=48}}
 
        Gets a list and count of the online and offline clients.
#>

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/stats/client/onlinestatus"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.clientCountStatsList | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEP.clientStatusList")
        }

        return $resp.clientCountStatsList
    }
}
#EndRegion '.\Public\Get-SEPClientStatus.ps1' 60
#Region '.\Public\Get-SEPClientVersion.ps1' -1

function Get-SEPClientVersion {
    <#
    .SYNOPSIS
        Gets a list and count of clients by client product version.
    .DESCRIPTION
        Gets a list and count of clients by client product version.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> $SEPversions = Get-SEPClientVersion
        PS C:\PSSymantecSEPM> $SEPversions.clientVersionList
 
        version clientsCount formattedVersion
        ------- ------------ ----------------
        11.0.6000.550 1 11.0.6 (11.0 MR6) build 550
        12.1.2015.2015 1 12.1.2 (12.1 RU2) build 2015
        12.1.6867.6400 1 12.1.6 (12.1 RU6 MP4) build 6867
        12.1.7004.6500 3 12.1.6 (12.1 RU6 MP5) build 7004
        12.1.7454.7000 177 12.1.7 (12.1 RU7) build 7454
        14.0.3752.1000 36 14.0.3 (14.0 RU3 MP7) build 1000
        14.2.1031.0100 21 14.2.1 (14.2 RU1) build 0100
        14.2.3335.1000 3 14.2.3 (14.2 RU3 MP3) build 1000
        14.3.510.0000 12 14.3 (14.3) build 0000
        14.3.558.0000 5 14.3 (14.3) build 0000
 
        Gets a list and count of clients by client product version.
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/stats/client/version"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.clientVersionList | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEP.clientVersionList")
        }

        return $resp.clientVersionList
    }
}
#EndRegion '.\Public\Get-SEPClientVersion.ps1' 71
#Region '.\Public\Get-SEPComputers.ps1' -1

function Get-SEPComputers {
    <#
    .SYNOPSIS
        Gets the information about the computers in a specified domain
    .DESCRIPTION
        Gets the information about the computers in a specified domain. either from computer names or group names
    .PARAMETER ComputerName
        Specifies the name of the computer for which you want to get the information. Supports wildcards
    .PARAMETER GroupName
        Specifies the group full path name for which you want to get the information. Supports wildcards
    .PARAMETER IncludeSubGroups
        Specifies whether to include subgroups when querying by group name
    .EXAMPLE
        Get-SEPComputers
 
        Gets computer details for all computers in the domain
    .EXAMPLE
        "MyComputer1","MyComputer2" | Get-SEPComputers
 
        Gets computer details for the specified computer MyComputer via pipeline
    .EXAMPLE
        Get-SEPComputers -ComputerName "MyComputer*"
 
        Gets computer details for all computer names starting by MyComputer
    .EXAMPLE
        Get-SEPComputers -GroupName "My Company\EMEA\Workstations"
 
        Gets computer details for all computers in the specified group MyGroup
    .EXAMPLE
        Get-SEPComputers -GroupName "My Company\EMEA\Workstations" -IncludeSubGroups
 
        Gets computer details for all computers in the specified group MyGroup and its subgroups
#>

    [CmdletBinding(
        DefaultParameterSetName = 'ComputerName'
    )]
    Param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName'
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'GroupName'
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # switch parameter to include subgroups
        [Parameter(
            ParameterSetName = 'GroupName'
        )]
        [switch]
        $IncludeSubGroups,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {

        # Using computer name API call
        if ($ComputerName) {
            $allResults = @()
            $URI = $script:BaseURLv1 + "/computers"

            # URI query strings
            $QueryStrings = @{
                computerName = $ComputerName
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

            # Invoke the request params
            $params = @{
                Method  = 'GET'
                Uri     = $URI
                headers = $headers
            }

            $allResults = (Invoke-ABRestMethod -params $params).content

            # Filtering
            $allResults = $allResults | Where-Object { $_.computerName -like $ComputerName }
        }

        # Using computer name API call then filtering
        elseif ($GroupName) {
            $allResults = @()
            $URI = $script:BaseURLv1 + "/computers"

            # URI query strings
            $QueryStrings = @{
                sort         = "COMPUTER_NAME"
                pageIndex    = 1
                pageSize     = 100
                computerName = $ComputerName # empty string value to ensure the URI is constructed correctly & query all computers
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
    
            do {
                # Invoke the request params
                $params = @{
                    Method  = 'GET'
                    Uri     = $URI
                    headers = $headers
                }

                $resp = Invoke-ABRestMethod -params $params
                
                # Process the response
                $allResults += $resp.content

                # Increment the page index & update URI
                $QueryStrings.pageIndex++
                $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
            } until ($resp.lastPage -eq $true)

            # Filtering
            if ($IncludeSubGroups) {
                $allResults = $allResults | Where-Object { $_.group.name -like "$GroupName*" }
            } else {
                $allResults = $allResults | Where-Object { $_.group.name -eq $GroupName }
            }
        }

        # No parameters
        else {
            $allResults = @()
            $URI = $script:BaseURLv1 + "/computers"

            # URI query strings
            $QueryStrings = @{
                sort      = "COMPUTER_NAME"
                pageIndex = 1
                pageSize  = 100
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
    
            do {
                # Invoke the request params
                $params = @{
                    Method  = 'GET'
                    Uri     = $URI
                    headers = $headers
                }

                $resp = Invoke-ABRestMethod -params $params

                # Process the response
                $allResults += $resp.content

                # Increment the page index & update URI
                $QueryStrings.pageIndex++
                $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
            } until ($resp.lastPage -eq $true)
        }

        # Add a PSTypeName to the object
        $allresults | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEP.Computer")
        }

        # return the response
        return $allResults
    }
}
#EndRegion '.\Public\Get-SEPComputers.ps1' 198
#Region '.\Public\Get-SEPFileDetails.ps1' -1

function Get-SEPFileDetails {
    <#
    .SYNOPSIS
        Gets the details of a binary file, such as the checksum and the file size
    .DESCRIPTION
        Gets the details of a binary file, such as the checksum and the file size
    .PARAMETER FileID
        The ID of the file to get the details of
        Is a required parameter
        Can be found in the command ID of the response from Send-SEPMCommandGetFile
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPFileDetails -FileID 12345678901234567890123456789
 
        id fileSize checksum
        -- -------- --------
        CD02BC8E0A6606D53533F2428BB86D4E 1071101 4BE0BB3B57044CAD186FB59C2B7A13BB
#>


    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $FileID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/command-queue/file/$FileID/details"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # URI query strings
        $QueryStrings = @{
            file_id = $FileID
        }

        # Construct the URI
        $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPFileDetails.ps1' 68
#Region '.\Public\Get-SEPGUPList.ps1' -1

function Get-SEPGUPList {
    <#
    .SYNOPSIS
        Gets a list of group update providers
    .DESCRIPTION
        Gets a list of SEP clients acting as group update providers
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPGUPList
 
        Gets a list of GUPs clients
    .EXAMPLE
    PS C:\PSSymantecSEPM> Get-SEPGUPList | Select-Object Computername, AgentVersion, IpAddress, port
 
    computerName agentVersion ipAddress port
    ------------ ------------ --------- ----
    Server01 12.1.7454.7000 10.0.0.150 2967
    Server02 14.3.558.0000 10.1.0.150 2967
    Workstation01 12.1.7454.7000 192.168.0.1 2967
    Workstation02 14.3.558.0000 192.168.1.1 2967
 
    Gets a list of GUPs clients with specific properties
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/gup/status"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEP.GUPList")
        }

        return $resp
    }
}
#EndRegion '.\Public\Get-SEPGUPList.ps1' 68
#Region '.\Public\Get-SEPMAccessToken.ps1' -1

function Get-SEPMAccessToken {
    <#
    .SYNOPSIS
        Retrieves the API token for use in the rest of the module.
 
    .DESCRIPTION
        Retrieves the API token for use in the rest of the module.
 
        First will try to use the one that may have been provided as a parameter.
        If not provided, then will try to use the one already cached in memory.
        If still not found, will look to see if there is a file with the API token stored on disk
        Finally, if there is still no available token :
            - check if the SEPM server name is configured
            - check if the credentials are configured or stored on disk
            - query one from the SEPM server
            - store it in memory and on disk
            - return the token
 
    .PARAMETER AccessToken
        If provided, this will be returned instead of using the cached/configured value
 
    .OUTPUTS
        System.String
#>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [PSCustomObject] $AccessToken
    )

    # First will try to use the one that may have been provided as a parameter.
    if (-not [String]::IsNullOrEmpty($AccessToken.token)) {
        if (Test-SEPMAccessToken -Token $AccessToken) {
            $script:accessToken = $AccessToken
            return $AccessToken
        }
    }

    # If not provided, then will try to use the one already cached in memory.
    if (-not [String]::IsNullOrEmpty($script:accessToken)) {
        if (Test-SEPMAccessToken -Token $script:accessToken) {
            return $script:accessToken
        }
    }

    # If still not found, will look to see if there is a file with the API token stored in the disk
    if (Test-Path $script:accessTokenFilePath) {
        $AccessToken = Import-Clixml -Path $script:accessTokenFilePath -ErrorAction Ignore
        if (Test-SEPMAccessToken -Token $AccessToken) {
            $script:accessToken = $AccessToken
            return $script:accessToken
        }
    }
        
    # Finally, if there is still no available token, query one from the SEPM server.
    # Then caches the token in memory and stores it in a file on disk as a SecureString

    # Test if the SEPM server name is configured
    if ($null -eq $script:configuration.ServerAddress) {
        $message = "SEPM Server name not found. Provide server name :"
        Write-Warning -Message $message
        $ServerAddress = Read-Host -Prompt $message
        Set-SepmConfiguration -ServerAddress $ServerAddress
    }

    # Look for credentials stored in the disk
    if (Test-Path $script:credentialsFilePath) {
        $script:Credential = Import-Clixml -Path $script:credentialsFilePath
    }
    if ($null -eq $script:Credential) {
        $message = "Credentials not found. Provide credentials :"
        Write-Warning -Message $message
        Set-SEPMAuthentication -credential (Get-Credential)
    }

    # Test the certificate of the SEPM server
    $URI_Authenticate = $script:BaseURLv1 + '/identity/authenticate'
    Test-SEPMCertificate -URI $URI_Authenticate

    # Construct the request
    $body = @{
        "username" = $script:Credential.UserName
        "password" = ([System.Net.NetworkCredential]::new("", $script:Credential.Password).Password)
        "appName"  = "PSSymantecSEPM PowerShell Module"
        "domain"   = $script:configuration.domain
    }

    $Params = @{
        Method      = 'POST'
        Uri         = $URI_Authenticate
        ContentType = "application/json"
        Body        = ($body | ConvertTo-Json)
    }

    # Invoke the request and SkipCert if needed
    $Response = Invoke-ABRestMethod -params $Params

    # Sort the response
    $CachedToken = [PSCustomObject]@{
        token           = $response.token
        tokenExpiration = (Get-Date).AddSeconds($Response.tokenExpiration)
        SkipCert        = $script:SkipCert
    }

    # Caches the token in memory
    $script:accessToken = $CachedToken

    # Stores it in a file on disk as a SecureString
    if (-not (Test-Path ($Script:accessTokenFilePath | Split-Path))) {
        New-Item -ItemType Directory -Path ($Script:accessTokenFilePath | Split-Path) -Force | Out-Null
    }
    $script:accessToken | Export-Clixml -Path $script:accessTokenFilePath -Force

    # return the token
    return $script:accessToken
}
#EndRegion '.\Public\Get-SEPMAccessToken.ps1' 117
#Region '.\Public\Get-SEPMAdmins.ps1' -1

Function Get-SEPMAdmins {
    <#
    .SYNOPSIS
        Displays a list of admins in the Symantec Database
 
    .DESCRIPTION
        Gets the list of administrators for a particular domain.
 
        The Git repo for this module can be found here: https://github.com/Douda/PSSymantecSEPM
 
    .PARAMETER AdminName
        Displays only a specific user from the Admin List
 
    .PARAMETER SkipCertificateCheck
        Skip certificate check
 
    .EXAMPLE
        Get-SEPMAdmins
     
    .EXAMPLE
    Get-SEPMAdmins -AdminName admin
 
#>

    [CmdletBinding()]
    Param (
        # AdminName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        [Alias("Admin")]
        $AdminName,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/admin-users"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # URI query strings
        $QueryStrings = @{
            domain = $script:configuration.domain
        }

        # Construct the URI
        $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
        
        $resp = Invoke-ABRestMethod -params $params

        
        # Add a PSTypeName to the object
        $resp | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEP.adminList")
        }

        # Process the response
        if ([string]::IsNullOrEmpty($AdminName)) {
            return $resp
        } else {
            $resp = $resp | Where-Object { $_.loginName -eq $AdminName }
            return $resp
        }
    }
}
#EndRegion '.\Public\Get-SEPMAdmins.ps1' 89
#Region '.\Public\Get-SEPMCommandStatus.ps1' -1

function Get-SEPMCommandStatus {
    <#
    .SYNOPSIS
        Get Command Status Details
    .DESCRIPTION
        Gets the details of a command status
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
    PS C:\PSSymantecSEPM> $status = Get-SEPMCommandStatus -Command_ID D17D6DF9877049559910DD7B0306711C
 
        content : {@{beginTime=; lastUpdateTime=; computerName=MyWorkstation01; computerIp=192.168.1.1; domainName=Default; currentLoginUserName=localadmin; stateId=0; subStateId=0; subStateDesc=; binaryFileId=; resultInXML=;
                            computerId=ABCDEF2837CD5C4FD167AD5E2CB31C71; hardwareKey=ABCDEF2837CD5C4FD167AD5E2CB31C71}}
        number : 0
        size : 20
        sort : {@{direction=ASC; property=Begintime; ascending=True}}
        numberOfElements : 1
        firstPage : True
        totalPages : 1
        lastPage : True
        totalElements : 1
 
        PS C:\PSSymantecSEPM> $status.content
 
        beginTime :
        lastUpdateTime :
        computerName : MyWorkstation01
        computerIp : 192.168.1.1
        domainName : Default
        currentLoginUserName : localadmin
        stateId : 0
        subStateId : 0
        subStateDesc :
        binaryFileId :
        resultInXML :
        computerId : ABCDEF2837CD5C4FD167AD5E2CB31C71
        hardwareKey : ABCDEF2837CD5C4FD167AD5E2CB31C71
 
    Gets the status of a command
    .PARAMETER Command_ID
        The ID of the command to get the status of
#>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        [Alias("ID", "CommandID")]
        $Command_ID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/command-queue/$command_id"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        $allResults = @()
        $QueryStrings = @{}

        do {
            # prepare the parameters
            $params = @{
                Method  = 'GET'
                Uri     = $URI
                headers = $headers
            }

            $resp = Invoke-ABRestMethod -params $params

            # Process the response
            $allResults += $resp.content

            # Increment the page index & update URI
            $QueryStrings.pageIndex++
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

        } until ($resp.lastPage -eq $true)

        # Add a PSTypeName to the object
        $allresults | ForEach-Object {
            $_.PSTypeNames.Insert(0, "SEPM.CommandStatus")
        }
    
        return $allResults
    }
}
#EndRegion '.\Public\Get-SEPMCommandStatus.ps1' 107
#Region '.\Public\Get-SEPMDatabaseInfo.ps1' -1

function Get-SEPMDatabaseInfo {
    <#
    .SYNOPSIS
        Gets the database infromation of local site.
    .DESCRIPTION
        Gets the database infromation of local site
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .INPUTS
        None
    .OUTPUTS
        System.Object
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMDatabaseInfo
 
        name : SQLSRV01
        description :
        address : SQLSRV01
        instanceName :
        port : 1433
        type : Microsoft SQL Server
        version : 12.00.5000
        installedBySepm : False
        database : sem5
        dbUser : sem5
        dbPasswords :
        dbTLSRootCertificate :
 
        Gets detailed information on the database of the local site
    #>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/admin/database"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.PSObject.TypeNames.Insert(0, 'SEPM.DatabaseInfo')

        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMDatabaseInfo.ps1' 71
#Region '.\Public\Get-SEPMDomain.ps1' -1

function Get-SEPMDomain {
    <#
    .SYNOPSIS
        Gets a list of all accessible domains
    .DESCRIPTION
        Gets a list of all accessible domains
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMDomain
 
        id : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        name : Default
        description :
        createdTime : 1360247301316
        enable : True
        companyName :
        contactInfo :
        administratorCount : 15
 
        Gets a list of all accessible domains
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/domains"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.PSObject.TypeNames.Insert(0, 'SEPM.DomainInfo')

        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMDomain.ps1' 64
#Region '.\Public\Get-SEPMEventInfo.ps1' -1

function Get-SEPMEventInfo {
    <#
    .SYNOPSIS
        Gets the information about the computers in a specified domain
    .DESCRIPTION
        Gets the information about the computers in a specified domain.
        A system administrator account is required for this REST API.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> $SEPMEvents = Get-SEPMEventInfo
 
        lastUpdated totalUnacknowledgedMessages criticalEventsInfoList
        ----------- --------------------------- ----------------------
        1693911276712 4906 {@{eventId=XXXXXXXXXXXXXXXXXXXXXXXXX; eventDateTime=2023-08-12 19:22:21.0...
 
        PS C:\PSSymantecSEPM> $SEPMEvents.criticalEventsInfoList | Select-Object -First 1
 
        eventId : XXXXXXXXXXXXXXXXXXXXXXXXX
        eventDateTime : 2023-08-12 19:22:21.0
        subject : CRITICAL: OLD SONAR DEFINITIONS
        message : 306 computers found with SONAR definitions older than 7 days.
        acknowledged : 0
 
        Example of an event gathered from the SEPM server.
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/events/critical"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.criticalEventsInfoList | ForEach-Object {
            $_.PSObject.TypeNames.Insert(0, 'SEPM.EventInfo')
        }

        return $resp.criticalEventsInfoList
    }
}
#EndRegion '.\Public\Get-SEPMEventInfo.ps1' 70
#Region '.\Public\Get-SEPMExceptionPolicy.ps1' -1

function Get-SEPMExceptionPolicy {
    <#
    .SYNOPSIS
        Get Exception Policy
    .DESCRIPTION
        Get Exception Policy details
        Note this is a V2 API call, and replies are originally JSON based
    .PARAMETER PolicyName
        The name of the policy to get the details of
        Is a required parameter
    .PARAMETER List
        List a specific exception category
        Valid values are "files", "directories", "webdomains"
        # TODO : add all the other exception types in example
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMExceptionPolicy -PolicyName "Standard Servers - Exception policy"
 
        Name Value
        ---- -----
        sources {}
        configuration {[files, System.Object[]], [non_pe_rules, System.Object[]], [directories, System.Object[]], [webdomains, System.Object[]]…}
        lockedoptions {[knownrisk, True], [extension, True], [file, True], [domain, True]…}
        enabled True
        desc
        name Standard Servers - Exception policy
        lastmodifiedtime 1646398353107
 
        Shows an example of getting the Exception policy details for the policy named "Workstations Exception Policy
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMExceptionPolicy -PolicyName "AB - Testing - API" -List files | Format-Table
 
        SONAR rulestate.enabled rulestate.source scancategory pathvariable path applicationcontrol securityrisk recursive Platform
        ----- ----------------- ---------------- ------------ ------------ ---- ------------------ ------------ --------- --------
        False True PSSymantecSEPM AutoProtect [NONE] C:\Temp\File5.exe False True False Windows
        True True PSSymantecSEPM AutoProtect [NONE] C:\Temp\File.exe True True False Windows
                            True [NONE] /applications/test/SONAR Mac
                            True [NONE] /Applications/test/TestFolder Mac
 
        Gets Exception details for the policy named "Workstations Exception Policy" and listing only the files exceptions
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMExceptionPolicy -PolicyName "AB - Testing - API" -List directories | Format-Table
 
        rulestate.enabled scancategory scantype pathvariable directory recursive Platform
        ----------------- ------------ -------- ------------ --------- --------- --------
                    True AutoProtect SecurityRisk [NONE] C:\Temp\SecurityRiskAP\ False Windows
                    True AllScans SONAR [NONE] C:\Temp\SonarWithSubfolders\ True Windows
                    True AllScans ApplicationControl [NONE] C:\Temp\AppControlException\ True Windows
                    True AllScans All [NONE] C:\Temp\FolderWithSubfoldersAllScans\ True Windows
                    True AllScans [NONE] /home/user1/ExcludedFolderWithSubfolders True Linux
 
        Gets Exception details for the policy named "Workstations Exception Policy" and listing only the directories exceptions
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMExceptionPolicy -PolicyName "AB - Testing - API" -List webdomains | Format-Table
 
        rulestate.enabled rulestate.source domain
        ----------------- ---------------- ------
                    True PSSymantecSEPM HTTPS://test.com
                    True HTTP://test.com
                    True PSSymantecSEPM HTTP://8.8.8.8
             
        Gets Exception details for the policy named "Workstations Exception Policy" and listing only the webdomains exceptions
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMExceptionPolicy -PolicyName "Workstations Exception Policy" -List extensions | Format-Table
 
        rulestate.enabled scancategory extensions.1 extensions.2 Platform
        ----------------- ------------ ------------ ------------ --------
                    True AllScans tmp extension2 Windows
                    True AutoProtect dk.tmp extension2 Linux
#>


    [CmdletBinding()]
    Param (
        # PolicyName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [Alias("Policy_Name")]
        [String]
        $PolicyName,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # List a specific exception category
        [Parameter()]
        [ValidateSet("files", "directories", "webdomains", "extensions", "tamper")]
        [String]
        $List
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        # BaseURL V2
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        # Stores the policy summary for all policies only once
        $policies = Get-SEPMPoliciesSummary
    }

    process {
        # Get Policy ID from policy name
        $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id
        $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype

        if ($policy_type -ne "exceptions") {
            $message = "policy type is not of type EXCEPTIONS or does not exist - Please verify the policy name"
            Write-Error -Message $message
            throw $message
        }

        # Updating URI with policy ID
        $URI = $URI + "/" + $policyID
        
        # prepare the parameters
        $params = @{
            Method          = 'GET'
            Uri             = $URI
            headers         = $headers
            UseBasicParsing = $true
        }
    
        $resp = Invoke-ABRestMethod -params $params
        
        # JSON response to convert to PSObject
        $resp = $resp | ConvertFrom-Json -AsHashtable -Depth 100
        
        # Add a PSTypeName to the object
        $resp.PSObject.TypeNames.Insert(0, 'SEPM.ExceptionPolicy')

        # Access the ScriptProperties of 'SEPM.ExceptionPolicy' to force them to run at least once
        # This is to ensure that the properties are available when the object is returned
        # refer to PSType SEPM.ExceptionPolicy for more details
        $null = $resp.lastModifiedTimeDate

        # If a specific list is requested, return only that list
        switch ($List) {
            "files" {
                $files = @()
                # Add the Windows platform to the files
                foreach ($f in $resp.configuration.files) {
                    $f["Platform"] = "Windows"
                    $files += $f
                }

                # Add the Mac platform to the files
                foreach ($f in $resp.configuration.mac.files) {
                    $f["Platform"] = "Mac"
                    $files += $f
                }

                # TODO : 01/23/2024 - Linux file exception is not a supported exception type in SEPM
                # Add the Linux platform to the files
                # foreach ($f in $resp.configuration.linux.files) {
                # $f["Platform"] = "Linux"
                # $files += $f
                # }

                return $files | ConvertTo-FlatObject
            }
            "directories" {
                $directories = @()
                # Add the Windows platform to the directories
                foreach ($d in $resp.configuration.directories) {
                    $d["Platform"] = "Windows"
                    $directories += $d
                }

                # TODO : 01/23/2024 - Mac directory exception is not a supported exception type in SEPM
                # Add the Mac platform to the directories
                # foreach ($d in $resp.configuration.mac.directories) {
                # $d["Platform"] = "Mac"
                # $directories += $d
                # }

                # Add the Linux platform to the directories
                foreach ($d in $resp.configuration.linux.directories) {
                    $d["Platform"] = "Linux"
                    $directories += $d
                }

                return $directories | ConvertTo-FlatObject
            }
            "webdomains" {
                return $resp.configuration.webdomains | ConvertTo-FlatObject
            }
            "extensions" {
                $extensions = @()
                # Add the Windows platform to the extensions
                foreach ($e in $resp.configuration.extension_list) {
                    $e["Platform"] = "Windows"
                    $extensions += $e
                }

                # Add the Linux platform to the extensions
                foreach ($e in $resp.configuration.linux.extension_list) {
                    $e["Platform"] = "Linux"
                    $extensions += $e
                }

                return $extensions | ConvertTo-FlatObject
            }
            "tamper" {
                return $resp.configuration.tamper_files | ConvertTo-FlatObject
            }
            Default { return $resp }
        }
    }
}
#EndRegion '.\Public\Get-SEPMExceptionPolicy.ps1' 225
#Region '.\Public\Get-SEPMFileFingerprintList.ps1' -1

function Get-SEPMFileFingerprintList {
    <# TODO update help
    .SYNOPSIS
        Get File Finger Print List By Name
    .DESCRIPTION
        Gets the file fingerprint list for a specified Name as a set of hash values
    .PARAMETER FingerprintListName
        The name of the file fingerprint list
    .PARAMETER FingerprintListID
        The ID of the file fingerprint list
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMFileFingerprintList -FingerprintListName "Fingerprint list for workstations"
 
        id : 2A331150CDB44B9A9F1332E27321A1EE
        name : Fingerprint list for workstations
        hashType : MD5
        source : WEBSERVICE
        description :
        data : {01BCE403043C0695EBB04D89C2B3A027, 03F3C0A7A2DD4EE1E81FABDBC557E2E8, 043A1B77C731F053FCA5DCC4AA18838F, 07996DCEEA57D8615B91A48AA7B49EC3…}
        groupIds : {46B9A36B0A66062224C839F606E6B1CE, AD3CD4620A95B05502CBDB658A6F7BE3, 09CC40530A6606221853DEA0AC606451, 96017A1E0A6906231EFEACCBD915B592…}
 
        Gets the file fingerprint list for a specified Name as a set of hash values
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMFileFingerprintList -FingerprintListID 2A331150CDB44B9A9F1332E27321A1EE
 
        id : 2A331150CDB44B9A9F1332E27321A1EE
        name : ASD01P0215
        hashType : MD5
        source : WEBSERVICE
        description :
        data : {01BCE403043C0695EBB04D89C2B3A027, 03F3C0A7A2DD4EE1E81FABDBC557E2E8, 043A1B77C731F053FCA5DCC4AA18838F, 07996DCEEA57D8615B91A48AA7B49EC3…}
        groupIds : {46B9A36B0A66062224C839F606E6B1CE, AD3CD4620A95B05502CBDB658A6F7BE3, 09CC40530A6606221853DEA0AC606451, 96017A1E0A6906231EFEACCBD915B592…}
 
        Gets the file fingerprint list for a specified ID as a set of hash values
#>


    [CmdletBinding()]
    param (
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $FingerprintListName,

        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $FingerprintListID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {

        if ($FingerprintListName) {
            $URI = $script:BaseURLv1 + "/policy-objects/fingerprints"
            # URI query strings
            $QueryStrings = @{
                name = $FingerprintListName
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

            $params = @{
                Method  = 'GET'
                Uri     = $URI
                headers = $headers
            }
    
            $resp = Invoke-ABRestMethod -params $params
        }

        if ($FingerprintListID) {
            $URI = $script:BaseURLv1 + "/policy-objects/fingerprints/$FingerprintListID"
            
            # prepare the parameters
            $params = @{
                Method  = 'GET'
                Uri     = $URI
                headers = $headers
            }
    
            $resp = Invoke-ABRestMethod -params $params
        }
        
        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMFileFingerprintList.ps1' 114
#Region '.\Public\Get-SEPMFirewallPolicy.ps1' -1

function Get-SEPMFirewallPolicy {
    <#
    .SYNOPSIS
        Get Firewall Policy
    .DESCRIPTION
        Get Firewall Policy details
    .PARAMETER PolicyName
        The name of the policy to get the details of
        Is a required parameter
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMFirewallPolicy -PolicyName "Standard Servers - Firewall policy"
 
        sources :
        configuration : @{enforced_rules=System.Object[]; baseline_rules=System.Object[]; ignore_parent_rules=; smart_dhcp=False; smart_dns=False; smart_wins=False; token_ring_traffic=False; netbios_protection=False; reverse_dns=False; port_scan=False;
                            dos=False; antimac_spoofing=False; autoblock=False; autoblock_duration=600; stealth_web=False; antiIP_spoofing=False; hide_os=False; windows_firewall=NO_ACTION; windows_firewall_notification=False; endpoint_notification=; p2p_auth=;
                            mac=}
        enabled : True
        desc : Standard Server Firewall Policy - This policy is for standard servers. It is a strict policy that blocks all traffic except for the services that are explicitly allowed.
        name : Standard Servers - Firewall policy
        lastmodifiedtime : 1692253688318
 
        Shows an example of getting the firewall policy details for the policy named "Standard Servers - Firewall policy"
#>


    [CmdletBinding(DefaultParameterSetName = 'PolicyName')]
    Param (
        # PolicyName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'PolicyName'
        )]
        [Alias("Policy_Name")]
        [String]
        $PolicyName,

        # Policy ID
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'PolicyID'
        )]
        [Alias("Policy_ID")]
        [String]
        $PolicyID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/policies/firewall"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        
    }

    process {

        if ($PolicyName) {
            # Get Policy ID from policy name
            $policies = Get-SEPMPoliciesSummary
            $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id
            $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype

            if ($policy_type -ne "fw") {
                $message = "policy type is not of type FIREWALL or does not exist - Please verify the policy name"
                Write-Error -Message $message
                throw $message
            }
        }

        # Updating URI with policy ID
        $URI = $URI + "/" + $policyID
        
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        try {
            $resp = Invoke-ABRestMethod -params $params
        } catch {
            Write-Warning -Message "Error: $_"
        }

        # Add a PSTypeName to the object
        $resp.PSObject.TypeNames.Insert(0, 'SEPM.FirewallPolicy')
        
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMFirewallPolicy.ps1' 108
#Region '.\Public\Get-SEPMGroups.ps1' -1

function Get-SEPMGroups {
    <#
    .SYNOPSIS
        Gets a group list
    .DESCRIPTION
        Gets a group list
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\GitHub_Projects\PSSymantecSEPM> Get-SEPMGroups | Select-Object -First 1
 
        id : XXXXXXXXXXXXXXXXXXXXXXXXX
        name : My Company
        description :
        fullPathName : My Company
        numberOfPhysicalComputers : 0
        numberOfRegisteredUsers : 0
        createdBy : XXXXXXXXXXXXXXXXXXXXXXXXX
        created : 1360247401336
        lastModified : 1639056401576
        policySerialNumber : 718B-09/04/2023 12:56:58 775
        policyDate : 1693832218775
        customIpsNumber :
        domain : @{id=XXXXXXXXXXXXXXXXXXXXXXXXX; name=Default}
        policyInheritanceEnabled : False
 
        Gets the first group of the list of groups
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/groups"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        # QueryString parameters for pagination
        $QueryStrings = @{
            pageSize  = 25
            pageIndex = 1
        }
    
        # Invoke the request
        do {
            try {
                # Invoke the request params
                $params = @{
                    Method  = 'GET'
                    Uri     = $URI
                    headers = $headers
                }
                
                $resp = Invoke-ABRestMethod -params $params
                
                # Process the response
                $allResults += $resp.content

                # Increment the page index & update URI
                $QueryStrings.pageIndex++
                $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
            } catch {
                Write-Warning -Message "Error: $_"
            }
        } until ($resp.lastPage -eq $true)

        # Add a PSTypeName to the object
        $allResults | ForEach-Object {
            $_.PSObject.TypeNames.Insert(0, 'SEPM.GroupInfo')
        }

        # return the response
        return $allResults
    }
}
#EndRegion '.\Public\Get-SEPMGroups.ps1' 100
#Region '.\Public\Get-SEPMGroupSettings.ps1' -1

function Get-SEPMGroupSettings {
    <#
    .SYNOPSIS
        Gets a group list
    .DESCRIPTION
        Gets a group list
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\GitHub_Projects\PSSymantecSEPM> Get-SEPMGroups | Select-Object -First 1
 
        id : XXXXXXXXXXXXXXXXXXXXXXXXX
        name : My Company
        description :
        fullPathName : My Company
        numberOfPhysicalComputers : 0
        numberOfRegisteredUsers : 0
        createdBy : XXXXXXXXXXXXXXXXXXXXXXXXX
        created : 1360247401336
        lastModified : 1639056401576
        policySerialNumber : 718B-09/04/2023 12:56:58 775
        policyDate : 1693832218775
        customIpsNumber :
        domain : @{id=XXXXXXXXXXXXXXXXXXXXXXXXX; name=Default}
        policyInheritanceEnabled : False
 
        Gets the first group of the list of groups
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        [Parameter(Mandatory = $true)]
        $locationId,

        [Parameter(Mandatory = $true)]
        $groupId
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        # $URI = $script:BaseURLv1 + "/groups"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Location ID
        $URI = $script:BaseURLv1 + "/groups/$groupId/locations/$locationId/settings"

        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        # Invoke the request
        try {
            # Invoke the request params
            $params = @{
                Method  = 'GET'
                Uri     = $URI
                headers = $headers
            }
                
            $resp = Invoke-ABRestMethod -params $params
        } catch {
            Write-Warning -Message "Error: $_"
        }


        # Add a PSTypeName to the object
        # $resp | ForEach-Object {
        # $_.PSObject.TypeNames.Insert(0, 'SEPM.GroupInfo')
        # }

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMGroupSettings.ps1' 95
#Region '.\Public\Get-SEPMIpsPolicy.ps1' -1

function Get-SEPMIpsPolicy {
    # TODO : returned object has empty configuration fields. Could be a bug ?
    # Example
    # PS C:\PSSymantecSEPM> $IPS_example | ConvertTo-Json
    # {
    # "sources": null,
    # "configuration": {},
    # "enabled": true,
    # "desc": "Summary : added IP as excluded host to avoid ServiceNow discovery service conflicts",
    # "name": "Intrusion Prevention policy PRODUCTION",
    # "lastmodifiedtime": 1693559858824
    # }

    <#
    .SYNOPSIS
        Get IPS Policy
    .DESCRIPTION
        Get IPS Policy details
    .PARAMETER PolicyName
        The name of the policy to get the details of
        Is a required parameter
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMIpsPolicy -PolicyName "Intrusion Prevention policy PRODUCTION"
 
        sources :
        configuration :
        enabled : True
        desc : IPS description field
        name : Intrusion Prevention policy PRODUCTION
        lastmodifiedtime : 1693559858824
 
        Shows an example of getting the IPS policy details for the policy named "Intrusion Prevention policy PRODUCTION"
#>


    [CmdletBinding()]
    Param (
        # PolicyName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [Alias("Policy_Name")]
        [String]
        $PolicyName,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
                # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/policies/ips"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        # Stores the policy summary for all policies only once
        $policies = Get-SEPMPoliciesSummary
    }

    process {
        # Get Policy ID from policy name
        $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id
        $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype

        if ($policy_type -ne "ips") {
            $message = "policy type is not of type IPS or does not exist - Please verify the policy name"
            Write-Error -Message $message
            throw $message
        }

        # Updating URI with policy ID
        $URI = $URI + "/" + $policyID
        
        # prepare the parameters
        $params = @{
            Method          = 'GET'
            Uri             = $URI
            headers         = $headers
            UseBasicParsing = $true
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMIpsPolicy.ps1' 99
#Region '.\Public\Get-SEPMLatestDefinition.ps1' -1

function Get-SEPMLatestDefinition {
    <#
    .SYNOPSIS
        Get AV Def Latest Info
    .DESCRIPTION
        Gets the latest revision information for antivirus definitions from Symantec Security Response.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMLatestDefinition
 
        contentName publishedBySymantec publishedBySEPM
        ----------- ------------------- ---------------
        AV_DEFS 9/4/2023 rev. 2 9/4/2023 rev. 2
 
        Gets the latest revision information for antivirus definitions from Symantec Security Response.
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/content/avdef/latest"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.PSObject.TypeNames.Insert(0, 'SEPM.LatestDefinitionInfo')
        
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMLatestDefinition.ps1' 59
#Region '.\Public\Get-SEPMLicense.ps1' -1

function Get-SEPMLicense {
    <#
    .SYNOPSIS
        Get SEP License Info
    .DESCRIPTION
        Get SEP License Info
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .PARAMETER Summary
        Get the summary of the license information
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMLicense
 
        serialNumber : BXXXXXXXXXX
        licenseType : 2
        seats : 10000
        startDate : 1641562400000
        expireDate : 1757393999000
        endDate : 1757393999000
        associatedLicenses :
        productName : Symantec Endpoint Security Complete, Subscription License
        keyNames : {SES, SES_SVR, scs_content, SEP_APP_ISOLATION…}
 
        Gets the license information for the SEPM
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMLicense -Summary
 
        license_type : PAID
        ended : False
        service_end_date : 1757393999000
        service_expiration_date : 1757393999000
        serial_number : BXXXXXXXXXX
        ordered_quantity : 10000
        unexpired_seats : 10000
 
        Gets the summary of the license information for the SEPM
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Summary
        [Parameter()]
        [switch]
        $Summary
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/licenses"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        #If the -Summary switch is used, then we will only return the summary of the license information
        if ($Summary) {
            $URI = $script:BaseURLv1 + "/licenses/summary"
        }

        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        if ($Summary) {
            $resp.PSObject.TypeNames.Insert(0, 'SEPM.LicenseSummaryInfo')
        } else {
            $resp.PSObject.TypeNames.Insert(0, 'SEPM.LicenseInfo')
        }
        
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMLicense.ps1' 93
#Region '.\Public\Get-SEPMLocation.ps1' -1

function Get-SEPMLocation {
    <# TODO update help for Location
    .SYNOPSIS
        Gets a list of locations for a specific group
    .DESCRIPTION
        Gets a list of locations for a specific group
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .PARAMETER GroupID
        Mandatory parameter for the group ID
    .INPUTS
        System.String
    .OUTPUTS
        System.Object with the following properties:
            locationName
            locationId
            groupName
            groupId
            groupFullPathName
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMLocation -GroupID "XXXXXXXX"
 
        name id
        ---- --
        Default 33CE4894AC1485D12E3AAC763CF9A71B
        Location 1 - Internal 0CCB0536AC1485D1233F341B9495C3C5
        Location 2 - VPN F5E857C9AC1485D13095A0D6E1CD5B25
 
        Gets the list of location names and their IDs for the specified group
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMGroups | Get-SEPMLocation | ft
 
        locationName locationId groupName groupId groupFullPathName
        ------------ ---------- --------- ------- -----------------
        Default 60B5C584AC17D44C6CC60471B7292FC4 My Company BDDDF2E6AC17D44C7007E1EA7851E110 My Company
        Default 60B5C584AC17D44C6CC60471B7292FC4 Default Group EA26D2D9AC17D44C532C651C62105B61 My Company\Default Group
        Default 60B5C584AC17D44C6CC60471B7292FC4 group 1 10314F0DAC1485D157E3089CEB47D5BC My Company\group 1
        Default 60B5C584AC17D44C6CC60471B7292FC4 sub group 1 6CB7099BAC1485D118EA465F45C4AE54 My Company\group 1\sub group 1
 
        Gets the list of location names and their IDs for all groups from the list of groups via pipeline
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # GroupID
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $GroupID
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        $allGroupsInfo = Get-SEPMGroups
    }

    process {
        # Get Group info
        $groupInfo = $allGroupsInfo | Where-Object { $_.id -eq $GroupID }

        $URI = $script:BaseURLv1 + "/groups" + "/$GroupID/locations"
        $locationList = @()

        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        # QueryString parameters
        $QueryStrings = @{
            hasName = $true
        }
    
        # Invoke the request
        $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
                
        $resp = Invoke-ABRestMethod -params $params

        # parse response and add group information to the list
        foreach ($location in $resp) {
            $locationList += [PSCustomObject]@{
                locationName      = $location.split(":")[0]
                locationId        = $location.split("/")[-1]
                groupName         = $groupInfo.name
                groupId           = $groupInfo.id
                groupFullPathName = $groupInfo.fullPathName
            }
        }

        # Add a PSTypeName to the object
        $locationList | ForEach-Object {
            $_.PSObject.TypeNames.Insert(0, 'SEPM.GroupLocationInfo')
        }

        # return the response
        return $locationList
    }
}
#EndRegion '.\Public\Get-SEPMLocation.ps1' 125
#Region '.\Public\Get-SEPMLocationXML.ps1' -1

function Get-SEPMLocationXML {
    <# TODO update help for Location
    .SYNOPSIS
        Gets a list of locations for a specific group
    .DESCRIPTION
        Gets a list of locations for a specific group
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .PARAMETER GroupID
        Mandatory parameter for the group ID
    .INPUTS
        System.String
    .OUTPUTS
        System.Object with the following properties:
            locationName
            locationId
            groupName
            groupId
            groupFullPathName
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMLocationXML -GroupID "XXXXXXXX"
 
        name id
        ---- --
        Default 33CE4894AC1485D12E3AAC763CF9A71B
        Location 1 - Internal 0CCB0536AC1485D1233F341B9495C3C5
        Location 2 - VPN F5E857C9AC1485D13095A0D6E1CD5B25
 
        Gets the list of location names and their IDs for the specified group
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMGroups | Get-SEPMLocationXML | ft
 
        locationName locationId groupName groupId groupFullPathName
        ------------ ---------- --------- ------- -----------------
        Default 60B5C584AC17D44C6CC60471B7292FC4 My Company BDDDF2E6AC17D44C7007E1EA7851E110 My Company
        Default 60B5C584AC17D44C6CC60471B7292FC4 Default Group EA26D2D9AC17D44C532C651C62105B61 My Company\Default Group
        Default 60B5C584AC17D44C6CC60471B7292FC4 group 1 10314F0DAC1485D157E3089CEB47D5BC My Company\group 1
        Default 60B5C584AC17D44C6CC60471B7292FC4 sub group 1 6CB7099BAC1485D118EA465F45C4AE54 My Company\group 1\sub group 1
 
        Gets the list of location names and their IDs for all groups from the list of groups via pipeline
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # GroupID
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $GroupID,

        # LocationID
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $LocationID
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        $allGroupsInfo = Get-SEPMGroups
    }

    process {
        # Get Group info
        $groupInfo = $allGroupsInfo | Where-Object { $_.id -eq $GroupID }

        $URI = $script:BaseURLv1 + "/groups/$GroupID/locations/$LocationID/xml"
        # $locationList = @()

        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        # QueryString parameters
        $QueryStrings = @{}
    
        # Invoke the request
        $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
                
        $resp = Invoke-ABRestMethod -params $params

        # # parse response and add group information to the list
        # foreach ($location in $resp) {
        # $locationList += [PSCustomObject]@{
        # locationName = $location.split(":")[0]
        # locationId = $location.split("/")[-1]
        # groupName = $groupInfo.name
        # groupId = $groupInfo.id
        # groupFullPathName = $groupInfo.fullPathName
        # }
        # }

        # # Add a PSTypeName to the object
        # $locationList | ForEach-Object {
        # $_.PSObject.TypeNames.Insert(0, 'SEPM.GroupLocationInfo')
        # }

        # # return the response
        # return $locationList

        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMLocationXML.ps1' 133
#Region '.\Public\Get-SEPMPoliciesSummary.ps1' -1

function Get-SEPMPoliciesSummary {
    <#
    .SYNOPSIS
        Get summary of all or feature specific policies
    .DESCRIPTION
        Get the policy summary for specified policy type.
        Also gets the list of groups to which the policies are assigned.
    .PARAMETER PolicyType
        The policy type for which the summary is to be retrieved.
        The valid values are hid, exceptions, mem, ntr, av, fw, ips, lu, hi, adc, msl, upgrade.
        If not specified, the summary for all policies is retrieved.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMPoliciesSummary
 
        Get policy statistics for all policies and its assigned groups
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMPoliciesSummary -PolicyType fw
 
        Get policy statistics for firewall policies and its assigned groups
    .EXAMPLE
        PS C:\PSSymantecSEPM> $csvPoliciesSummary = Get-SEPMPoliciesSummary | Select-Object -ExcludeProperty sources
        PS C:\PSSymantecSEPM> $csvPoliciesSummary | ConvertTo-FlatObject | Export-Csv C:\temp\test.csv
        PS C:\PSSymantecSEPM> $csvPoliciesSummary[0]
 
        enabled : True
        desc :
        name : Custom AV policy
        lastmodifiedtime : 1720096619241
        id : DFDDEFB3AC1E4AEE7D3F8CC3968XXXXX
        domainid : 8D9FCF73C0A890F810856CF40E9XXXXX
        policytype : av
        subtype :
        assignedtocloudgroups :
        assignedtolocations.1.groupId : 4F492FD6AC15B5D55CA866FE6C3XXXXX
        assignedtolocations.1.defaultLocationId : FE77AFA8AC15B5D5607B79FDE94XXXXX
        assignedtolocations.1.locationIds.1 : FE77AFA8AC15B5D5607B79FDE94XXXXX
        assignedtolocations.1.groupNameFullPath : My Company\Workstations\Tamper\Block and do not log
        assignedtolocations.2.groupId : BF9A0D8CAC15B5D534DB17F34E5XXXXX
        assignedtolocations.2.defaultLocationId : 39F318ABAC15B5D559E1014F7B0XXXXX
        assignedtolocations.2.locationIds.1 : 39F318ABAC15B5D559E1014F7B0XXXXX
        assignedtolocations.2.groupNameFullPath : My Company\Workstations\Tamper\Block and Log
        lastModifiedDate : 04/07/2023 12:36:59
 
        Export the list of all policies, its assigned groups, locations and other details to a CSV file
        Excludes the sources property from the output as it's empty by default
#>


    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateSet(
            'hid', 
            'exceptions', 
            'mem', 
            'ntr', 
            'av', 
            'fw', 
            'ips', 
            'lucontent', 
            'lu', 
            'hi', 
            'adc', 
            'msl', # Currently getting an error when trying to get this policy type
            # {"errorCode":"400","appErrorCode":"","errorMessage":"The policy type argument is invalid."}
            # TODO: Investigate this error
            'upgrade'
        )]
        [string]
        $PolicyType,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/policies/summary"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        # Get the list of groups and IDs to inject into the response
        $groups = Get-SEPMGroups
    }

    process {
        if ($PolicyType) {
            $URI = $script:BaseURLv1 + "/policies/summary" + "/" + $PolicyType
        }

        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        # Invoke the request
        try {
            $resp = Invoke-ABRestMethod -params $params
            # Add group FullPath to the response from their Group ID for ease of use
            # Parsing every response object
            foreach ($policy in $resp.content) {
                # Parsing every location this policy is applied to
                foreach ($location in $policy.assignedtolocations) {
                    # Getting the group name from the group ID, and adding it to the response object
                    $group = $groups | Where-Object { $_.id -match $location.groupid } | Get-Unique
                    $location | Add-Member -NotePropertyName "groupNameFullPath" -NotePropertyValue $group.fullPathName
                }
            }
        } catch {
            Write-Warning -Message "Error: $_"
        }
        

        # Add a PSTypeName to the object
        $resp.content | ForEach-Object {
            $_.PSTypeNames.Insert(0, 'SEPM.PolicySummary')
        }
            
        # return the response
        return $resp.content
    }
}
#EndRegion '.\Public\Get-SEPMPoliciesSummary.ps1' 136
#Region '.\Public\Get-SEPMPolicyXML.ps1' -1

function Get-SEPMPolicyXML {
    <#
    .SYNOPSIS
        Gets policy details in XML format
    .DESCRIPTION
        Gets policy details in XML format.
 
    .PARAMETER PolicyName
        The name of the policy to get the details of
        Is a required parameter
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .OUTPUTS
        XML object of the policy details
        Typename: System.Xml.XmlDocument
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMPolicyXML -PolicyName "Standard Servers - Firewall policy"
 
        xml SchemaContainer
        --- ---------------
        version="1.0" encoding="UTF-8" SchemaContainer
 
        Gets a PowerShell XML object of the policy details
#>


    [CmdletBinding(DefaultParameterSetName = 'PolicyName')]
    Param (
        # PolicyName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'PolicyName'
        )]
        [Alias("Policy_Name")]
        [String]
        $PolicyName,

        # Policy ID
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'PolicyID'
        )]
        [Alias("Policy_ID")]
        [String]
        $PolicyID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/policies/raw"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        
    }

    process {

        if ($PolicyName) {
            # Get Policy ID from policy name
            $policies = Get-SEPMPoliciesSummary
            $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id
            $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype
        }

        # Updating URI with policy ID
        $URI = $URI + "/" + $policy_type + "/" + $policyID
        
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }

        try {
            $resp = Invoke-RestMethod @params -SkipCertificateCheck
        } catch {
            Write-Warning -Message "Error: $_"
        }

        [xml]$xmlContent = $resp.policy_xml
        return $xmlContent
    }
}
#EndRegion '.\Public\Get-SEPMPolicyXML.ps1' 99
#Region '.\Public\Get-SEPMReplicationStatus.ps1' -1

function Get-SEPMReplicationStatus {
    <#
    .SYNOPSIS
        Get Replication Status
    .DESCRIPTION
        Get Replication Status
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMReplicationStatus
 
        replicationStatus
        -----------------
        @{siteName=Site Europe; siteLocation=Paris; replicationPartnerStatusList=System.Object[]; id=XXXXXXXXXXXXXXXXXXXXXXXX}
 
        Get a list of replication status with every remote site
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/replication/status"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # Add a PSTypeName to the object
        $resp.replicationStatus | ForEach-Object {
            $_.PSObject.TypeNames.Insert(0, 'SEPM.ReplicationStatus')
            # Add sub PSTypeName to the object
            foreach ($partner in $_.replicationPartnerStatusList) {
                $partner | ForEach-Object {
                    $_.PSObject.TypeNames.Insert(0, 'SEPM.ReplicationPartnerStatus')
                }
            }
        }

        return $resp.replicationStatus
    }
}
#EndRegion '.\Public\Get-SEPMReplicationStatus.ps1' 67
#Region '.\Public\Get-SEPMThreatStats.ps1' -1

function Get-SEPMThreatStats {
    <#
    .SYNOPSIS
        Gets threat statistics
    .DESCRIPTION
        Gets threat statistics
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMThreatStats
 
        Stats
        -----
        @{lastUpdated=1693912098821; infectedClients=1}
 
        Gets threat statistics
#>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/stats/threat"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp.Stats
    }
}
#EndRegion '.\Public\Get-SEPMThreatStats.ps1' 55
#Region '.\Public\Get-SEPMVersion.ps1' -1

Function Get-SEPMVersion {
    <#
    .SYNOPSIS
        Gets the current version of Symantec Endpoint Protection Manager.
    .DESCRIPTION
        Gets the current version of Symantec Endpoint Protection Manager. This function dot not require authentication.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Get-SEPMVersion
 
        API_SEQUENCE API_VERSION version
        ------------ ----------- -------
        230504014 14.3.7000 14.3.9816.7000
 
        Gets the current version of Symantec Endpoint Protection Manager.
    #>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/version"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMVersion.ps1' 55
#Region '.\Public\Move-SEPClientGroup.ps1' -1

function Move-SEPClientGroup {
    <#
    .SYNOPSIS
        Moves a computer to a different SEPM group
    .DESCRIPTION
        Moves a computer to a different SEPM group
        Gathers the hardwareKey of the computer and the group ID of the destination group
        and sends a PATCH request to the SEPM API
    .PARAMETER ComputerName
        Specifies the name of the computer for which you want to get the information
    .PARAMETER GroupName
        Specifies the group full path name for which you want to get the information
        Full path name is the group name with the parent groups separated by backslash
        "My Company\EMEA\Workstations"
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        Move-SEPClientGroup -ComputerName "MyComputer" -GroupName "My Company\EMEA\Workstations"
 
        Moves the computer MyComputer to the group My Company\EMEA\Workstations
    .EXAMPLE
        "MyComputer1","MyComputer2" | Move-SEPClientGroup -GroupName "My Company\EMEA\Workstations"
 
        Moves the computers MyComputer1 and MyComputer2 to the group My Company\EMEA\Workstations via pipeline
    .EXAMPLE
        Move-SEPClientGroup -ComputerName "MyComputer" -GroupName "My Company\EMEA\Workstations" -SkipCertificateCheck
 
        Moves the computer MyComputer to the group My Company\EMEA\Workstations and skips certificate check
    #>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # ComputerName
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            Mandatory = $true
        )]
        [Alias("Group")]
        [String]
        $GroupName
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/computers"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        # Get all groups from SEPM
        $allGroups = Get-SEPMGroups
    }

    process {
        # Get the computer hardwareID
        $hardwareKey = Get-SEPComputers -ComputerName $ComputerName | Select-Object -ExpandProperty hardwareKey
        if ([string]::IsNullOrEmpty($hardwareKey)) {
            $message = "HardwareKey of computer $ComputerName not found. Please check the computer name and try again."
            Write-Error $message
            return
        }

        # Get the group ID of the destination group
        $groupID = $allGroups | Where-Object { $_.fullPathName -eq $GroupName } | Select-Object -ExpandProperty id
        if ([string]::IsNullOrEmpty($groupID)) {
            $message = "Group $GroupName not found. Please check the group name and try again."
            $message += "Following group format is expected: 'My Company\group\subgroup'"
            Write-Error $message
            return
        }

        # Body structure for the request
        $body = @(
            @{
                "group"       = @{
                    "id" = $groupID   
                }
                "hardwareKey" = $hardwareKey
            }
        ) 

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI
            headers     = $headers
            contenttype = 'application/json'
            body        = $body | ForEach-Object { ConvertTo-Json @( $_ ) } # This way converts to JSON as array
        }
    
        # Invoke the request
        try {
            $resp = Invoke-ABRestMethod -params $params
        } catch {
            Write-Warning -Message "Error: $_"
        }

        $fullResponse = [PSCustomObject]@{
            computerName        = $ComputerName
            computerHardwareKey = $hardwareKey
            targetGroup         = $GroupName
            responseCode        = $resp.responseCode
            responseMessage     = $resp.responseMessage
        }

        # Add a PSTypeName to the object
        $fullResponse.PSObject.TypeNames.Insert(0, 'SEPM.MoveClientGroupResponse')

        # return the response
        return $fullResponse
    }
}
#EndRegion '.\Public\Move-SEPClientGroup.ps1' 134
#Region '.\Public\New-SEPMGroup.ps1' -1

function New-SEPMGroup {
    <#
    .SYNOPSIS
        Creates a new SEPM group
    .DESCRIPTION
        Creates a new SEPM group
        Requires the full path name of the parent group
    .PARAMETER GroupName
        Specifies the name of the group to create
    .PARAMETER ParentGroup
        Specifies the full path name of the parent group
        Full path name is the group name with the parent groups separated by backslash
        "My Company\EMEA\Workstations"
    .PARAMETER EnabledInheritance
        Specifies if the group should inherit the parent group's policies
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        New-SEPMGroup -GroupName "Win7" -ParentGroup "My Company\EMEA\Workstations"
 
        Creates a new group Win7 under the group My Company\EMEA\Workstations
    .EXAMPLE
        New-SEPMGroup -GroupName "Win 10" -ParentGroup "My Company\EMEA\Workstations" -EnabledInheritance
 
        Creates a new group Win 10 under the group My Company\EMEA\Workstations and enables inheritance
    #>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # Parent group
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $ParentGroup,

        # Enabled inheritance
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Inherit")]
        [switch]
        $EnabledInheritance,

        # Group Description
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $Description
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/groups"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        # Get all groups from SEPM
        $allGroups = Get-SEPMGroups
    }

    process {
        # Get the group ID of the destination group
        $ParentGroupID = $allGroups | Where-Object { $_.fullPathName -eq $ParentGroup } | Select-Object -ExpandProperty id
        if ([string]::IsNullOrEmpty($ParentGroupID)) {
            $message = "Group $GroupName not found. Please check the parent group name and try again."
            $message += "Following group format is expected: 'My Company\group\subgroup'"
            Write-Error $message
            return
        }

        # Body structure for the request
        $body = @{
            "inherits"    = $EnabledInheritance.ToBool()
            "name"        = $GroupName
            "description" = $Description
        }

        # prepare the parameters
        $params = @{
            Method      = 'POST'
            Uri         = $URI + "/$ParentGroupID"
            headers     = $headers
            contenttype = 'application/json'
            body        = $body | ConvertTo-Json
        }

        # TODO For testing only - remove this
        # $body | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force
    
        # Invoke the request
        try {
            $resp = Invoke-ABRestMethod -params $params
        } catch {
            Write-Warning -Message "Error: $_"
        }

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\New-SEPMGroup.ps1' 124
#Region '.\Public\Remove-SEPMFileFingerprintList.ps1' -1

function Remove-SEPMFileFingerprintList {
    <#
    .SYNOPSIS
        Deletes a file fingerprint list
    .DESCRIPTION
        Deletes a file fingerprint list, and removes it from a group to which it applies
    .PARAMETER FingerprintListName
        The name of the file fingerprint list
    .PARAMETER FingerprintListID
        The ID of the file fingerprint list
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Remove-SEPMFileFingerprintList -FingerprintListName "Fingerprint list for workstations"
 
        Removes the file fingerprint list with the name "Fingerprint list for workstations"
    .EXAMPLE
        PS C:\PSSymantecSEPM> "Fingerprint list for workstations" | Remove-SEPMFileFingerprintList
 
        Removes the file fingerprint list with the name "Fingerprint list for workstations" via the pipeline
    .EXAMPLE
        PS C:\PSSymantecSEPM> Remove-SEPMFileFingerprintList -FingerprintListID 2A331150CDB44B9A9F1332E27321A1EE
 
        Removes the file fingerprint list with the ID "2A331150CDB44B9A9F1332E27321A1EE"
#>


    [CmdletBinding(
        DefaultParameterSetName = 'Name'
    )]
    param (
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Name'
        )]
        [string]
        $FingerprintListName,

        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ID'
        )]
        [string]
        $FingerprintListID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Get the FingerprintListID if the FingerprintListName is provided
        if ($FingerprintListName) {
            $URI = $script:BaseURLv1 + "/policy-objects/fingerprints"
            $FingerprintListID = Get-SEPMFileFingerprintList -FingerprintListName $FingerprintListName | Select-Object -ExpandProperty id
        }

        $URI = $script:BaseURLv1 + "/policy-objects/fingerprints/$FingerprintListID"

        $params = @{
            Method  = 'DELETE'
            Uri     = $URI
            headers = $headers
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Remove-SEPMFileFingerprintList.ps1' 86
#Region '.\Public\Remove-SEPMGroup.ps1' -1

function Remove-SEPMGroup {
    <# TODO Update the help
    .SYNOPSIS
        Creates a new SEPM group
    .DESCRIPTION
        Creates a new SEPM group
        Requires the full path name of the parent group
    .PARAMETER GroupName
        Specifies the name of the group to create
    .PARAMETER ParentGroup
        Specifies the full path name of the parent group
        Full path name is the group name with the parent groups separated by backslash
        "My Company\EMEA\Workstations"
    .PARAMETER EnabledInheritance
        Specifies if the group should inherit the parent group's policies
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        Remove-SEPMGroup -GroupName "Win7" -ParentGroup "My Company\EMEA\Workstations"
 
        Creates a new group Win7 under the group My Company\EMEA\Workstations
    .EXAMPLE
        Remove-SEPMGroup -GroupName "Win 10" -ParentGroup "My Company\EMEA\Workstations" -EnabledInheritance
 
        Creates a new group Win 10 under the group My Company\EMEA\Workstations and enables inheritance
    #>


    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # Parent group
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $ParentGroup,

        # Enabled inheritance
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Inherit")]
        [switch]
        $EnabledInheritance,

        # Group Description
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [String]
        $Description
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/groups"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
        # Get all groups from SEPM
        $allGroups = Get-SEPMGroups
    }

    process {
        # Get the group ID of the destination group
        $ParentGroupID = $allGroups | Where-Object { $_.fullPathName -eq $ParentGroup } | Select-Object -ExpandProperty id
        if ([string]::IsNullOrEmpty($ParentGroupID)) {
            $message = "Group $GroupName not found. Please check the parent group name and try again."
            $message += "Following group format is expected: 'My Company\group\subgroup'"
            Write-Error $message
            return
        }

        # Body structure for the request
        $body = @{
            "inherits"    = $EnabledInheritance.ToBool()
            "name"        = $GroupName
            "description" = $Description
        }

        # prepare the parameters
        $params = @{
            Method      = 'POST'
            Uri         = $URI + "/$ParentGroupID"
            headers     = $headers
            contenttype = 'application/json'
            body        = $body | ConvertTo-Json
        }

        # TODO For testing only - remove this
        # $body | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force
    
        # Invoke the request
        try {
            $resp = Invoke-ABRestMethod -params $params
        } catch {
            Write-Warning -Message "Error: $_"
        }

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Remove-SEPMGroup.ps1' 125
#Region '.\Public\Remove-SEPMWindowsExtensionException.ps1' -1

function Remove-SEPMWindowsExtensionException {

    <#
    .SYNOPSIS
        Add a Windows extension exception to a SEPM policy
    .DESCRIPTION
        Add a Windows extension exception to a SEPM policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Extension
        Extension to add to the exception list
        Accepts multiple values
    .PARAMETER ScanType
        Type of scan to apply the exception to
        Valid values are:
            AllScans
            AutoProtect
            ScheduledAndOndemand
    .EXAMPLE
        Add-SEPMWindowsExtensionException -PolicyName "Workstations Default Exception Policy" -Extension tmp,tmp2
 
        Add 2 Windows extensions exception, tmp and tmp2 to the "Workstations Default Exception Policy" policy
    .EXAMPLE
        Add-SEPMWindowsExtensionException -PolicyName "Workstations Default Exception Policy" -Extension tmp,tmp2 -ScanType AutoProtect
 
        Add 2 Windows extensions exception, tmp and tmp2 to the "Workstations Default Exception Policy" policy, for AutoProtect scans
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Extension
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Extension,

        # Security Risk type
        [ValidateSet(
            'AllScans',
            'AutoProtect',
            'ScheduledAndOndemand'
        )]
        [string] 
        $ScanType = 'AllScans'
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Gather current extension exception list
        $PolicyExtList = (Get-SEPMExceptionPolicy -PolicyName $PolicyName).configuration.extension_list.extensions

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExtensionList = @()
        $ExceptionParams.RulestateSource = $script:ModuleName
        $ExceptionParams.scancategory = $ScanType

        # Populate extension list
        $ExtensionList += $PolicyExtList

        # Parse the extension list to remove
        foreach ($Ext in $Extension) {
            if ($ExtensionList -notcontains $Ext) {
                throw "Cannot remove Extension $Ext. It is not in the exception list"
            } else {
                $ExtensionList = $ExtensionList | Where-Object { $_ -ne $Ext }
            }
        }
        $ExceptionParams.extensions += $ExtensionList

        # Create the file exception object with CreateExtensionListHashtable
        # Method parameters have to be in the same order as in the method definition
        $ExtensionHashTable = $Policy.ObjBody.CreateExtensionListHashtable(
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.extensions
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddExtensionsList($ExtensionHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Remove-SEPMWindowsExtensionException.ps1' 139
#Region '.\Public\Remove-SEPMWindowsFileException.ps1' -1

function Remove-SEPMWindowsFileException {

    <#
    .SYNOPSIS
        Remove a Windows File Exception from a Symantec Endpoint Protection Manager Policy
    .DESCRIPTION
        Remove a Windows File Exception from a Symantec Endpoint Protection Manager Policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Path
        Path to add to the exception list
    .PARAMETER PathVariable
        Path variable to use for the path
    .PARAMETER SkipCertificateCheck
        Skip the certificate check when connecting to the SEPM
    .EXAMPLE
        Remove-SEPMWindowsFileException -PolicyName "Default" -Path "C:\Temp\file1.exe"
     
        Remove the file C:\Temp\file1.exe from the exception list in the policy Default
    #>

    
    
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Pathvariable
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [ValidateSet(
            '[NONE]', 
            '[COMMON_APPDATA]', 
            '[COMMON_DESKTOPDIRECTORY]', 
            '[COMMON_DOCUMENTS]', 
            '[COMMON_PROGRAMS]', 
            '[COMMON_STARTUP]', 
            '[PROGRAM_FILES]', 
            '[PROGRAM_FILES_COMMON]', 
            '[SYSTEM]', 
            '[SYSTEM_DRIVE]', 
            '[USER_PROFILE]', 
            '[WINDOWS]'
        )]
        [Alias('WindowsPathVariable')]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(ParameterSetName = 'WindowsFileException', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("^[A-Za-z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*[^\\/:*?""<>|\r\n]+\.[^\\/:*?""<>|\r\n]+$")]
        [Alias('WindowsPath')]
        [string] 
        $Path
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExceptionParams.path = $path
        $ExceptionParams.pathvariable = $PathVariable
        $ExceptionParams.RulestateSource = $script:ModuleName
        $ExceptionParams.deleted = $true

        # Create the file exception object with CreateFilesHashTable
        # Method parameters have to be in the same order as in the method definition
        $FilesHashTable = $Policy.ObjBody.CreateFilesHashTable(
            $ExceptionParams.sonar,
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.pathvariable,
            $ExceptionParams.path,
            $ExceptionParams.applicationcontrol,
            $ExceptionParams.securityrisk,
            $ExceptionParams.recursive
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddConfigurationFilesExceptions($FilesHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Remove-SEPMWindowsFileException.ps1' 130
#Region '.\Public\Remove-SEPMWindowsFolderException.ps1' -1

function Remove-SEPMWindowsFolderException {

    <# TODO update help
    .SYNOPSIS
        Add a Windows File Exception to a Symantec Endpoint Protection Manager Policy
    .DESCRIPTION
        Add a Windows File Exception to a Symantec Endpoint Protection Manager Policy
    .PARAMETER PolicyName
        Name of the policy to update
    .PARAMETER Path
        Path to add to the exception list
    .PARAMETER PathVariable
        Path variable to use for the path
    .PARAMETER Sonar
        Add the exception to the SONAR exclusions
    .PARAMETER SecurityRiskCategory
        Add the exception to the Security Risk exclusions
        Takes the following values:
            AllScans
            AutoProtect
            ScheduledAndOndemand
    .PARAMETER ApplicationControl
        Add the exception to the Application Control exclusions
    .PARAMETER ExcludeChildProcesses
        Exclude child processes from the Application Control exclusions
        Requires ApplicationControl to be set to true
    .PARAMETER SkipCertificateCheck
        Skip the certificate check when connecting to the SEPM
    .PARAMETER AllScans
        Add the exception to all scan types
        Equivalent to setting Sonar, SecurityRiskCategory and ApplicationControl to true
        If no scan type is provided, default to AllScans
    .EXAMPLE
        Remove-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -Sonar
 
        Exclude the file C:\Temp\file1.exe from SONAR scans in the policy Default
    .EXAMPLE
        Remove-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -SecurityRiskCategory "AllScans"
 
        Exclude the file C:\Temp\file1.exe from all Security Risk scans in the policy Default
    .EXAMPLE
        Remove-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -ApplicationControl
 
        Exclude the file C:\Temp\file1.exe from Application Control scans in the policy Default
    .EXAMPLE
        Remove-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -ApplicationControl -ExcludeChildProcesses
 
        Exclude the file C:\Temp\file1.exe from Application Control scans in the policy Default and exclude child processes
    .EXAMPLE
        Remove-SEPMWindowsFolderException -PolicyName "Workstations Default Exception Policy" -Path "C:\Temp\file1.exe" -AllScans
 
        Exclude the file C:\Temp\file1.exe from all scan types in the policy Default (SONAR / AutoProtect / Scheduled scans / Application Control)
    #>

    
    

    [CmdletBinding()]
    param (
        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck,

        # Policy Name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [String]
        $PolicyName,

        # Pathvariable
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [ValidateSet(
            '[NONE]', 
            '[COMMON_APPDATA]', 
            '[COMMON_DESKTOPDIRECTORY]', 
            '[COMMON_DOCUMENTS]', 
            '[COMMON_PROGRAMS]', 
            '[COMMON_STARTUP]', 
            '[PROGRAM_FILES]', 
            '[PROGRAM_FILES_COMMON]', 
            '[SYSTEM]', 
            '[SYSTEM_DRIVE]', 
            '[USER_PROFILE]', 
            '[WINDOWS]'
        )]
        [Alias('WindowsPathVariable')]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(ParameterSetName = 'WindowsFolderException', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("^[A-Za-z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*")]
        [Alias('WindowsPath')]
        [string] 
        $Path
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv2 + "/policies/exceptions"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Initialize the policy exception
        $Policy = Initialize-PolicyExceptionStructure -PolicyName $PolicyName

        # Init & populate the mandatory parameters
        $ExceptionParams = @{}
        $ExceptionParams.directory = $path
        $ExceptionParams.pathvariable = $PathVariable
        $ExceptionParams.RulestateSource = $script:ModuleName
        $ExceptionParams.deleted = $true

        # Create the file exception object with CreateFilesHashTable
        # Method parameters have to be in the same order as in the method definition
        $DirectoryHashTable = $Policy.ObjBody.CreateDirectoryHashtable(
            $ExceptionParams.deleted,
            $ExceptionParams.RulestateEnabled,
            $ExceptionParams.RulestateSource,
            $ExceptionParams.scancategory,
            $ExceptionParams.scantype,
            $ExceptionParams.pathvariable,
            $ExceptionParams.directory,
            $ExceptionParams.recursive
        )

        # Add the file exception parameters to the body structure
        $Policy.ObjBody.AddConfigurationDirectoriesExceptions($DirectoryHashTable)

        # Optimize the body structure (remove empty properties)
        $Policy.ObjBody = Optimize-ExceptionPolicyStructure -obj $Policy.ObjBody

        # TODO For testing only - remove this
        # $Policy.ObjBody | ConvertTo-Json -Depth 100 | Out-File .\Data\PolicyStructure.json -Force

        # prepare the parameters
        $params = @{
            Method      = 'PATCH'
            Uri         = $URI + "/" + $Policy.PolicyID
            headers     = $headers
            contenttype = 'application/json'
            Body        = $Policy.ObjBody | ConvertTo-Json -Depth 100
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Remove-SEPMWindowsFolderException.ps1' 163
#Region '.\Public\Reset-SepmConfiguration.ps1' -1

function Reset-SEPMConfiguration {
    <#
    .SYNOPSIS
        Clears out the user's configuration file and configures this session with all default
        configuration values.
 
    .DESCRIPTION
        Clears out the user's configuration file and configures this session with all default
        configuration values.
 
    .EXAMPLE
        Reset-SEPMConfiguration
 
        Deletes the local configuration file and loads in all default configuration values.
 
    .NOTES
        This command will not clear your authentication token.
        Please use Clear-SEPMAuthentication to accomplish that.
#>


    $null = Remove-Item -Path $script:configurationFilePath -Force -ErrorAction SilentlyContinue -ErrorVariable ev

    if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) {
        $message = "Reset was unsuccessful. Experienced a problem trying to remove the file [$script:configurationFilePath]."
        Write-Warning -Message $message
    }

    $message = "This has not cleared your authentication token. Call Clear-SEPMAuthentication to accomplish that."
    Write-Verbose -Message $message
}
#EndRegion '.\Public\Reset-SepmConfiguration.ps1' 31
#Region '.\Public\Restore-SEPMAuthentication.ps1' -1

function Restore-SEPMAuthentication {
    <#
    .SYNOPSIS
        Sets the specified file to be the user's authentication file.
 
    .DESCRIPTION
        Sets the specified file to be the user's authentication file.
 
        This is primarily used for unit testing scenarios.
    .PARAMETER Path
        The path to store the user's current authentication file.
 
    .EXAMPLE
        Restore-SEPMAuthentication -Path 'c:\foo\config.xml'
 
        Makes the contents of c:\foo\config.xml be the user's authentication for the module.
#>

    [CmdletBinding()]
    param(
        [ValidateScript({
                if (Test-Path -Path $_ -PathType Leaf) { $true }
                else { throw "$_ does not exist." } })]
        [string] $Path,

        [Parameter(ParameterSetName = 'AccessToken')]
        [switch] $AccessToken,

        [Parameter(ParameterSetName = 'Credential')]
        [switch] $Credential
    )
    
    if ($AccessToken) {
        # Make sure that the path that we're going to be storing the file exists.
        $null = New-Item -Path (Split-Path -Path $script:accessTokenFilePath -Parent) -ItemType Directory -Force

        $null = Copy-Item -Path $Path -Destination $script:accessTokenFilePath -Force
    }
    
    if ($Credential) {
        # Make sure that the path that we're going to be storing the file exists.
        $null = New-Item -Path (Split-Path -Path $script:credentialsFilePath -Parent) -ItemType Directory -Force

        $null = Copy-Item -Path $Path -Destination $script:credentialsFilePath -Force
    }

    Initialize-SepmConfiguration
}
#EndRegion '.\Public\Restore-SEPMAuthentication.ps1' 48
#Region '.\Public\Restore-SEPMConfiguration.ps1' -1

function Restore-SEPMConfiguration {
    <#
    .SYNOPSIS
        Sets the specified file to be the user's configuration file.
 
    .DESCRIPTION
        Sets the specified file to be the user's configuration file.
 
        This is primarily used for unit testing scenarios.
    .PARAMETER Path
        The path to store the user's current configuration file.
 
    .EXAMPLE
        Restore-SEPMConfiguration -Path 'c:\foo\config.json'
 
        Makes the contents of c:\foo\config.json be the user's configuration for the module.
#>

    [CmdletBinding()]
    param(
        [ValidateScript({
                if (Test-Path -Path $_ -PathType Leaf) { $true }
                else { throw "$_ does not exist." } })]
        [string] $Path
    )

    # Make sure that the path that we're going to be storing the file exists.
    $null = New-Item -Path (Split-Path -Path $script:configurationFilePath -Parent) -ItemType Directory -Force

    $null = Copy-Item -Path $Path -Destination $script:configurationFilePath -Force

    Initialize-SepmConfiguration
}
#EndRegion '.\Public\Restore-SEPMConfiguration.ps1' 33
#Region '.\Public\Send-SEPMCommandActiveScan.ps1' -1

function Send-SEPMCommandActiveScan {
    <#
.SYNOPSIS
    Send an active scan command to SEP endpoints
.DESCRIPTION
    Send an active scan command to SEP endpoints
    This will scan the specified computer(s) or group(s) for threats
.PARAMETER ComputerName
    The name of the computer to send the command to
    Cannot be used with GroupName
.PARAMETER GroupName
    The name of the group to send the command to
    Cannot be used with ComputerName
    Does not include subgroups
.PARAMETER SkipCertificateCheck
    Skip certificate check
.EXAMPLE
    Send-SEPMCommandActiveScan -ComputerName "Computer1"
  
    Sends an active scan command to Computer1
.EXAMPLE
    "Computer1", "Computer2" | Send-SEPMCommandActiveScan
  
    Sends an active scan command to Computer1 and Computer2
#>


    [CmdletBinding()]
    param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName'
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'GroupName'
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }
    
    process {
        # Init
        $URI = $script:BaseURLv1 + "/command-queue/activescan"

        if ($ComputerName) {
            # Get computer ID(s) from computer name(s)
            $ComputerIDList = @()
            foreach ($C in $ComputerName) {
                $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
                $ComputerIDList += $ComputerID
            }

            # URI query strings
            $QueryStrings = @{
                computer_ids = $ComputerIDList
            }

            # Construct the URI
            $builder = New-Object System.UriBuilder($URI)
            $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
            foreach ($param in $QueryStrings.GetEnumerator()) {
                $query[$param.Key] = $param.Value
            }
            $builder.Query = $query.ToString()
            $URI = $builder.ToString()
        }

        # If group name is specified
        elseif ($GroupName) {
            # Get group ID(s) from group name(s)
            $GroupIDList = @()
            foreach ($G in $GroupName) {
                $GroupID = Get-SEPMGroups | Where-Object { $_.fullPathName -eq $G } | Select-Object -ExpandProperty id -First 1
                $GroupIDList += $GroupID
            }

            # URI query strings
            $QueryStrings = @{
                group_ids = $GroupIDList
            }

            # Construct the URI
            $builder = New-Object System.UriBuilder($URI)
            $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
            foreach ($param in $QueryStrings.GetEnumerator()) {
                $query[$param.Key] = $param.Value
            }
            $builder.Query = $query.ToString()
            $URI = $builder.ToString()
        }

        # Invoke the request params
        $params = @{
            Method      = 'POST'
            Uri         = $URI
            headers     = $headers
            Body        = $body | ConvertTo-Json
            ContentType = 'application/json'
        }
        
        $resp = Invoke-ABRestMethod -params $params

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Send-SEPMCommandActiveScan.ps1' 136
#Region '.\Public\Send-SEPMCommandClearIronCache.ps1' -1

function Send-SEPMCommandClearIronCache {
    <#
    .SYNOPSIS
        Send a clear IRON cache command to SEP endpoints
    .DESCRIPTION
        Send a clear IRON cache command to SEP endpoints
        This will remove cached information about a specific hash from the SEP client
    .PARAMETER ComputerName
        The name of the computer to send the command to
        Cannot be used with GroupName
    .PARAMETER GroupName
        The name of the group to send the command to
        Cannot be used with ComputerName
        Does not include subgroups
    .PARAMETER SHA256
        SHA256 hash of the suspicious file.
        Cannot be used with MD5 or SHA1
    .PARAMETER MD5
        MD5 hash of the suspicious file.
        Cannot be used with SHA256 or SHA1
    .PARAMETER SHA1
        SHA1 hash of the suspicious file.
        Cannot be used with SHA256 or MD5
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        Send-SEPMCommandClearIronCache -ComputerName "Computer1"
 
        Sends a clean IRON cache command Computer1
    .EXAMPLE
        "Computer1", "Computer2" | Send-SEPMCommandClearIronCache
 
        Sends a clean IRON cache command Computer1 and Computer2
    .EXAMPLE
        Send-SEPMCommandClearIronCache -GroupName "My Company\EMEA\Workstations\Site1"
 
        Sends a clean IRON cache command all computers in "My Company\EMEA\Workstations\Site1"
        Does not include subgroups
    #>

    
    [CmdletBinding()]
    param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName'
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'GroupName'
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # SHA256 hash of the suspicious file.
        [Parameter(ParameterSetName = 'SHA256Hash')]
        [Parameter(ParameterSetName = 'ComputerName')]
        [ValidateScript({
                if ($_.Length -ne 64) { throw "SHA256 hash must be 64 characters long" }
                return $true
            })]
        [string]
        $SHA256,

        # MD5 hash of the suspicious file.
        [Parameter(ParameterSetName = 'MD5Hash')]
        [Parameter(ParameterSetName = 'ComputerName')]
        [ValidateScript({
                if ($_.Length -ne 32) { throw "MD5 hash must be 32 characters long" }
                return $true
            })]
        [string]
        $MD5,

        # SHA1 hash of the suspicious file.
        [Parameter(ParameterSetName = 'SHA1Hash')]
        [Parameter(ParameterSetName = 'ComputerName')]
        [ValidateScript({
                if ($_.Length -ne 40) { throw "SHA1 hash must be 40 characters long" }
                return $true
            })]
        [string]
        $SHA1,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }
    
    process {
        # Init
        $URI = $script:BaseURLv1 + "/command-queue/ironcache"

        # Build body and add correct hash to body
        $body = @{
            data = @()
        }
        $hashParameter = $PSCmdlet.MyInvocation.BoundParameters.Keys | Where-Object { $_ -in @('SHA256', 'MD5', 'SHA1') }
        switch ($hashParameter) {
            'SHA256' {
                $body.Add("hashType", "sha256")
                $body.data += $SHA256
            }
            'MD5' {
                $body.Add("hashType", "md5")
                $body.data += $MD5
            }
            'SHA1' {
                $body.Add("hashType", "sha1")
                $body.data += $SHA1
            }
        }

        if ($ComputerName) {
            # Get computer ID(s) from computer name(s)
            $ComputerIDList = @()
            foreach ($C in $ComputerName) {
                $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
                $ComputerIDList += $ComputerID
            }

            # URI query strings
            $QueryStrings = @{
                computer_ids = $ComputerIDList
            }

            # Construct the URI
            $builder = New-Object System.UriBuilder($URI)
            $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
            foreach ($param in $QueryStrings.GetEnumerator()) {
                $query[$param.Key] = $param.Value
            }
            $builder.Query = $query.ToString()
            $URI = $builder.ToString()
        }

        # If group name is specified
        elseif ($GroupName) {
            # Get group ID from group name
            $GroupID = Get-SEPMGroups | Where-Object { $_.fullPathName -eq $GroupName } | Select-Object -ExpandProperty id -First 1

            # URI query strings
            $QueryStrings = @{
                group_ids = $GroupID
            }

            # Construct the URI
            $builder = New-Object System.UriBuilder($URI)
            $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
            foreach ($param in $QueryStrings.GetEnumerator()) {
                $query[$param.Key] = $param.Value
            }
            $builder.Query = $query.ToString()
            $URI = $builder.ToString()
        }

        # Invoke the request params
        $params = @{
            Method      = 'POST'
            Uri         = $URI
            headers     = $headers
            Body        = $body | ConvertTo-Json
            ContentType = 'application/json'
        }
        
        $resp = Invoke-ABRestMethod -params $params

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Send-SEPMCommandClearIronCache.ps1' 196
#Region '.\Public\Send-SEPMCommandFullScan.ps1' -1

function Send-SEPMCommandFullScan {
    <#
.SYNOPSIS
    Send a full scan command to SEP endpoints
.DESCRIPTION
    Send a full scan command to SEP endpoints
    This will scan the specified computer(s) or group(s) for threats
.PARAMETER ComputerName
    The name of the computer to send the command to
    Cannot be used with GroupName
.PARAMETER GroupName
    The name of the group to send the command to
    Cannot be used with ComputerName
    Does not include subgroups
.PARAMETER SkipCertificateCheck
    Skip certificate check
.EXAMPLE
    Send-SEPMCommandFullScan -ComputerName "Computer1"
  
    Sends an active scan command to Computer1
.EXAMPLE
    "Computer1", "Computer2" | Send-SEPMCommandFullScan
  
    Sends a full scan command to Computer1 and Computer2
#>


    [CmdletBinding()]
    param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName'
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'GroupName'
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }
    
    process {
        # Init
        $URI = $script:BaseURLv1 + "/command-queue/fullscan"

        if ($ComputerName) {
            # Get computer ID(s) from computer name(s)
            $ComputerIDList = @()
            foreach ($C in $ComputerName) {
                $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
                $ComputerIDList += $ComputerID
            }

            # URI query strings
            $QueryStrings = @{
                computer_ids = $ComputerIDList
            }

            # Construct the URI
            $builder = New-Object System.UriBuilder($URI)
            $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
            foreach ($param in $QueryStrings.GetEnumerator()) {
                $query[$param.Key] = $param.Value
            }
            $builder.Query = $query.ToString()
            $URI = $builder.ToString()
        }

        # If group name is specified
        elseif ($GroupName) {
            # Get group ID(s) from group name(s)
            $GroupIDList = @()
            foreach ($G in $GroupName) {
                $GroupID = Get-SEPMGroups | Where-Object { $_.fullPathName -eq $G } | Select-Object -ExpandProperty id -First 1
                $GroupIDList += $GroupID
            }

            # URI query strings
            $QueryStrings = @{
                group_ids = $GroupIDList
            }

            # Construct the URI
            $builder = New-Object System.UriBuilder($URI)
            $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
            foreach ($param in $QueryStrings.GetEnumerator()) {
                $query[$param.Key] = $param.Value
            }
            $builder.Query = $query.ToString()
            $URI = $builder.ToString()
        }

        # Invoke the request params
        $params = @{
            Method      = 'POST'
            Uri         = $URI
            headers     = $headers
            Body        = $body | ConvertTo-Json
            ContentType = 'application/json'
        }
        
        $resp = Invoke-ABRestMethod -params $params

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Send-SEPMCommandFullScan.ps1' 136
#Region '.\Public\Send-SEPMCommandGetFile.ps1' -1

function Send-SEPMCommandGetFile {
    <#
    .SYNOPSIS
        Sends a commands to request a suspicious file be uploaded back to Symantec Endpoint Protection Manager
    .DESCRIPTION
        Sends a commands to request a suspicious file be uploaded back to Symantec Endpoint Protection Manager
    .PARAMETER ComputerName
        The list of computers on which to search for the suspicious file.
    .PARAMETER SHA256
        SHA256 hash of the suspicious file.
    .PARAMETER MD5
        MD5 hash of the suspicious file.
    .PARAMETER SHA1
        SHA1 hash of the suspicious file.
    .PARAMETER Source
        The source to search for the suspicious file
        Possible values are: FILESYSTEM (default), QUARANTINE, or BOTH. 12.1.x clients only use FILESYSTEM.
    .PARAMETER FilePath
        The file path of the suspicious file.
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Send-SEPMCommandGetFile -ComputerName MyWorkstation01 -SHA256 1234567890123456789012345678901234567890123456789012345678901234 -FilePath C:\Temp\malware.exe -Source BOTH
 
        Sends a command to request the following file C:\Temp\malware.exe be uploaded from MyWorkstation01 to Symantec Endpoint Protection Manager.
        Requests includes both the file system and quarantine locations to be looked at.
    #>

    
    
    [CmdletBinding()]
    param (
        # The list of computers on which to search for the suspicious file.
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # SHA256 hash of the suspicious file.
        [Parameter(ParameterSetName = 'SHA256Hash')]
        [ValidateScript({
                if ($_.Length -ne 64) { throw "SHA256 hash must be 64 characters long" }
                return $true
            })]
        [string]
        $SHA256,

        # MD5 hash of the suspicious file.
        [Parameter(ParameterSetName = 'MD5Hash')]
        [ValidateScript({
                if ($_.Length -ne 32) { throw "MD5 hash must be 32 characters long" }
                return $true
            })]
        [string]
        $MD5,

        # SHA1 hash of the suspicious file.
        [Parameter(ParameterSetName = 'SHA1Hash')]
        [ValidateScript({
                if ($_.Length -ne 40) { throw "SHA1 hash must be 40 characters long" }
                return $true
            })]
        [string]
        $SHA1,

        # The source to search for the suspicious file
        [Parameter()]
        [ValidateSet('FILESYSTEM ', 'QUARANTINE', 'BOTH')]
        [string]
        $Source,

        # The file path of the suspicious file.
        [Parameter()]
        [ValidateScript({
                if ($_ -notmatch '^.+\\[^\\]+\.[^\\]+$') { 
                    throw "The string must be a file path ending with a file name and an extension" 
                }
                return $true
            })]
        [Alias("Path")]
        [string]
        $FilePath,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }
    
    process {
        # Get computer ID(s) from computer name(s)
        $ComputerIDList = @()
        foreach ($C in $ComputerName) {
            $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
            $ComputerIDList += $ComputerID
        }

        $URI = $script:BaseURLv1 + "/command-queue/files"

        # URI query strings
        $QueryStrings = @{
            computer_ids = $ComputerIDList
            file_path    = $FilePath
            source       = $Source
        }

        # Add correct hash to query strings
        if ($SHA256) {
            $QueryStrings.Add("sha256", $SHA256)
        } elseif ($MD5) {
            $QueryStrings.Add("md5", $MD5)
        } elseif ($SHA1) {
            $QueryStrings.Add("sha1", $SHA1)
        }

        # Construct the URI
        $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

        # prepare the parameters
        $params = @{
            Method  = 'POST'
            Uri     = $URI
            headers = $headers
        }

        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Send-SEPMCommandGetFile.ps1' 147
#Region '.\Public\Send-SEPMCommandQuarantine.ps1' -1

function Send-SEPMCommandQuarantine {
    <#
    .SYNOPSIS
        Send a quarantine/unquarantine command to SEP endpoints
    .DESCRIPTION
        Send a quarantine/unquarantine command to SEP endpoints
    .PARAMETER ComputerName
        The name of the computer to send the command to
        Cannot be used with GroupName
    .PARAMETER GroupName
        The name of the group to send the command to
        Cannot be used with ComputerName
        Does not include subgroups
    .PARAMETER Unquarantine
        Switch parameter to unquarantine the SEP client
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        Send-SEPMCommandQuarantine -ComputerName "Computer1"
        Sends a command to quarantine Computer1
    .EXAMPLE
        "Computer1", "Computer2" | Send-SEPMCommandQuarantine
        Sends a command to quarantine Computer1 and Computer2
    .EXAMPLE
        Send-SEPMCommandQuarantine -GroupName "My Company\EMEA\Workstations\Site1"
        Sends a command to quarantine all computers in "My Company\EMEA\Workstations\Site1"
        Does not include subgroups
    #>

    
    
    [CmdletBinding()]
    param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName'
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'GroupName'
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # Unquarantine
        [Parameter()]
        [switch]
        $Unquarantine,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }
    
    process {
        if ($ComputerName) {
            # Get computer ID(s) from computer name(s)
            $ComputerIDList = @()
            foreach ($C in $ComputerName) {
                $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
                $ComputerIDList += $ComputerID
            }

            $URI = $script:BaseURLv1 + "/command-queue/quarantine"

            # URI query strings
            $QueryStrings = @{
                computer_ids = $ComputerIDList
            }

            # Add unquarantine if specified
            if ($Unquarantine) {
                $QueryStrings['undo'] = $true
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

            # prepare the parameters
            $params = @{
                Method  = 'POST'
                Uri     = $URI
                headers = $headers
            }
    
            $resp = Invoke-ABRestMethod -params $params
            return $resp
        }

        # If group name is specified
        elseif ($GroupName) {
            # Get group ID from group name
            $GroupID = Get-SEPMGroups | Where-Object { $_.fullPathName -eq $GroupName } | Select-Object -ExpandProperty id -First 1
            $URI = $script:BaseURLv1 + "/command-queue/quarantine"

            # URI query strings
            $QueryStrings = @{
                group_ids = $GroupID
            }

            # Add unquarantine if specified
            if ($Unquarantine) {
                $QueryStrings['undo'] = $true
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

            # prepare the parameters
            $params = @{
                Method  = 'POST'
                Uri     = $URI
                headers = $headers
            }
            
            $resp = Invoke-ABRestMethod -params $params
            return $resp
        }
    }
}
#EndRegion '.\Public\Send-SEPMCommandQuarantine.ps1' 144
#Region '.\Public\Set-SepmAuthentication.ps1' -1

function Set-SEPMAuthentication {
    <#
    .SYNOPSIS
        Allows the user to configure the SEPM Authentication
 
    .DESCRIPTION
        Allows the user to configure the SEPM Authentication
 
        Username and password will be securely stored on the machine for use in all future PowerShell sessions.
 
    .PARAMETER Credentials
        A PSCredential object containing the username and password to use for authentication
 
    .EXAMPLE
        Set-SEPMAuthentication Credentials (Get-Credential)
 
        Prompts the user for username and password, saves them to disk and in the PS Session
 
    .EXAMPLE
        $Credentials = Get-Credential
        Set-SEPMAuthentication -Credential $cred
#>

    [CmdletBinding()]
    param(
        [PSCredential] $Credentials
    )

    # If no credentials are provided, prompt the user for them
    if ($null -eq $Credentials) {
        $Credentials = Get-Credential
    }

    # If the user provides a username and password, verify if password is not null or empty
    if ([String]::IsNullOrWhiteSpace($Credentials.GetNetworkCredential().Password)) {
        $message = "Password not provided. Provide correct credentials and try again."
        Write-Error -Message $message
        throw $message
    }

    # Setting script scope variable so that credentials can be used in other functions
    $script:Credential = $Credentials

    # Test if the credential path exists
    if (-not (Test-Path -Path $script:credentialsFilePath)) {
        New-Item -Path $script:credentialsFilePath -ItemType File -Force | Out-Null
    }

    # Saving credentials to disk
    $Credentials | Export-Clixml -Path $script:credentialsFilePath -Force
        
}
#EndRegion '.\Public\Set-SepmAuthentication.ps1' 52
#Region '.\Public\Set-SepmConfiguration.ps1' -1

function Set-SepmConfiguration {
    <#
    .SYNOPSIS
        Change the value of a configuration property for the PSSymantecSEPM module
 
    .DESCRIPTION
        Change the value of a configuration property for the PSSymantecSEPM module
        A single call to this method can set any number or combination of properties.
 
    .PARAMETER ServerAddress
        The hostname of the SEPM instance to communicate with.
    .PARAMETER Port
        The port number of the SEPM instance to communicate with.
    .EXAMPLE
        Set-SepmConfiguration -ServerAddress "MySEPMServer"
 
        Set the SEPM server address to "MySEPMServer"
    .EXAMPLE
        Set-SepmConfiguration -ServerAddress "MySEPMServer" -Port 8446
 
        Set the SEPM server address to "MySEPMServer" and the port to 8446
 
 
#>

    [CmdletBinding(
        PositionalBinding = $false
    )]
    param(
        [string] $ServerAddress,

        [int] $Port
    )

    # Load in the persisted configuration object
    $persistedConfig = Read-SepmConfiguration -Path $script:configurationFilePath

    # Update the configuration object with any values that were provided as parameters
    $properties = Get-Member -InputObject $script:configuration -MemberType NoteProperty | Select-Object -ExpandProperty Name

    # $PSBoundParameters is a hashtable of all the parameters that were passed to this function
    # We can use this to determine which properties were passed in and update the configuration object
    # Allows to easily add new properties by adding new parameters without having to update the below loop
    foreach ($name in $properties) {
        if ($PSBoundParameters.ContainsKey($name)) {
            $value = $PSBoundParameters.$name
            if ($value -is [switch]) { $value = $value.ToBool() }
            $script:configuration.$name = $value
            Add-Member -InputObject $persistedConfig -Name $name -Value $value -MemberType NoteProperty -Force
        }
    }

    # Persist the configuration object to disk
    Save-SepmConfiguration -Configuration $persistedConfig -Path $script:configurationFilePath

    # Re-initialize the configuration object
    Initialize-SepmConfiguration
}
#EndRegion '.\Public\Set-SepmConfiguration.ps1' 58
#Region '.\Public\Start-SEPMReplication.ps1' -1

function Start-SEPMReplication {
    <# TODO update help
    .SYNOPSIS
        Initiates replication with a remote site
    .DESCRIPTION
        Initiates replication with a remote site
    .PARAMETER partnerSiteName
        The name of the remote site to replicate with
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Start-SEPMReplication -partnerSiteName "Remote site Americas"
 
        code
        ----
        0
 
        Initiates replication with the remote site Americas. Response code 0 indicates success.
#>


    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $partnerSiteName,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck

        # TODO known bug with SEPM API, these parameters are returning invalid option if not set to false
        # [switch]
        # $logs,

        # [switch]
        # $ContentAndPackages
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $URI = $script:BaseURLv1 + "/replication/replicatenow"
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # URI query strings
        $QueryStrings = @{
            partnerSiteName = $partnerSiteName
            logs            = $logs
            content         = $ContentAndPackages
        }

        # Construct the URI
        $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

        # prepare the parameters
        $params = @{
            Method  = 'POST'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Start-SEPMReplication.ps1' 78
#Region '.\Public\Start-SEPScan.ps1' -1

function Start-SEPScan {
    <#
    .SYNOPSIS
        Sends Active Scan command to the specified computer(s) or group(s)
    .DESCRIPTION
        Sends a command from SEPM to SEP endpoints to request an active scan on the endpoint(s)
    .PARAMETER ComputerName
        Specifies the name of the computer for which you want to send the command.
        Accepts pipeline input by Value and ByPropertyName
    .PARAMETER GroupName
        Specifies the group full path name for which you want to send the command
    .PARAMETER ActiveScan
        Specifies the type of scan to send to the endpoint(s)
        Valid values are ActiveScan and FullScan
        By default, the ActiveScan switch is used
    .PARAMETER FullScan
        Specifies the type of scan to send to the endpoint(s)
        Valid values are ActiveScan and FullScan
        By default, the ActiveScan switch is used
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        PS C:\PSSymantecSEPM> Start-SEPScan -ComputerName MyComputer01 -ActiveScan
 
        Sends an active scan command to the specified computer MyComputer01
    .EXAMPLE
        "MyComputer1","MyComputer2" | Start-SEPScan
 
        Sends an active scan command to the specified computers MyComputer1 & MyComputer2 via pipeline
        By default, the ActiveScan switch is used
    .EXAMPLE
        Start-SEPScan -GroupName "My Company\EMEA\Workstations" -fullscan
 
        Sends a fullscan command to all endpoints part of the group "My Company\EMEA\Workstations"
#>

    [CmdletBinding(
        DefaultParameterSetName = 'ComputerNameActiveScan'
    )]
    Param (
        # ComputerName
        [Parameter(
            ParameterSetName = 'ComputerNameActiveScan',
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Parameter(
            ParameterSetName = 'ComputerNameFullScan',
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ParameterSetName = 'GroupNameActiveScan',
            ValueFromPipelineByPropertyName = $true
        )]
        [Parameter(
            ParameterSetName = 'GroupNameFullScan',
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # ActiveScan
        [Parameter(ParameterSetName = 'ComputerNameActiveScan')]
        [Parameter(ParameterSetName = 'GroupNameActiveScan')]
        [switch]
        $ActiveScan,

        # FullScan
        [Parameter(ParameterSetName = 'ComputerNameFullScan')]
        [Parameter(ParameterSetName = 'GroupNameFullScan')]
        [switch]
        $FullScan,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # If specific computer name(s) are specified
        if ($ComputerName) {
            # Get computer ID(s) from computer name(s)
            $ComputerIDList = @()
            foreach ($C in $ComputerName) {
                $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
                $ComputerIDList += $ComputerID
            }

            if ($ActiveScan) {
                $URI = $script:BaseURLv1 + "/command-queue/activescan"
            }
            if ($FullScan) {
                $URI = $script:BaseURLv1 + "/command-queue/fullscan"
            }

            # URI query strings
            $QueryStrings = @{
                computer_ids = $ComputerIDList
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings

            # prepare the parameters
            $params = @{
                Method  = 'POST'
                Uri     = $URI
                headers = $headers
            }
    
            $resp = Invoke-ABRestMethod -params $params
            return $resp
        }

        # If by groupname
        elseif ($GroupName) {
            #######################################
            # 1. finds all computers in the group #
            #######################################
            $allComputers = @()
            $URI = $script:BaseURLv1 + "/computers"

            # URI query strings
            $QueryStrings = @{
                sort         = "COMPUTER_NAME"
                pageIndex    = 1
                pageSize     = 100
                computerName = $ComputerName # empty string value to ensure the URI is constructed correctly & query all computers
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
    
            # Get computer list
            do {
                try {
                    # prepare the parameters
                    $params = @{
                        Method  = 'GET'
                        Uri     = $URI
                        headers = $headers
                    }
                    
                    $resp = Invoke-ABRestMethod -params $params
                
                    # Process the response
                    $allComputers += $resp.content

                    # Increment the page index & update URI
                    $QueryStrings.pageIndex++
                    $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
                } catch {
                    Write-Warning -Message "Error: $_"
                }
            } until ($resp.lastPage -eq $true)

            # filter list by group name
            $allComputers = $allComputers | Where-Object { $_.group.name -eq $GroupName }
            
            #################################################
            # 2. send command to all computers in the group #
            #################################################

            if ($ActiveScan) {
                $URI = $script:BaseURLv1 + "/command-queue/activescan"
            }
            if ($FullScan) {
                $URI = $script:BaseURLv1 + "/command-queue/fullscan"
            }

            $AllResp = @()
            
            foreach ($id in $allComputers.uniqueId) {
                # URI query strings
                $QueryStrings = @{
                    computer_ids = $id
                }
    
                # Construct the URI
                $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
        
                # prepare the parameters
                $params = @{
                    Method  = 'POST'
                    Uri     = $URI
                    headers = $headers
                }

                # Send command to each computers in the group
                $resp = Invoke-ABRestMethod -params $params
                $AllResp += $resp
            }
            
            # return the response
            return $AllResp
        }
    }
}
#EndRegion '.\Public\Start-SEPScan.ps1' 221
#Region '.\Public\Update-SEPClientDefinitions.ps1' -1

function Update-SEPClientDefinitions {
    <#
    .SYNOPSIS
        Sends a command from SEPM to SEP endpoints to update content
    .DESCRIPTION
        Sends a command from SEPM to SEP endpoints to update content
    .PARAMETER ComputerName
        The name of the computer to send the command to
        cannot be used with GroupName
    .PARAMETER GroupName
        The name of the group to send the command to
        cannot be used with ComputerName
    .PARAMETER IncludeSubGroups
        Specifies whether to include subgroups when querying by group name
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        Update-SEPClientDefinitions -ComputerName "Computer1"
        Sends a command to update content to Computer1
    .EXAMPLE
        "Computer1", "Computer2" | Update-SEPClientDefinitions
        Sends a command to update content to Computer1 and Computer2
    .EXAMPLE
        Update-SEPClientDefinitions -GroupName "My Company\EMEA\Workstations"
        Sends a command to update content to all computers in "My Company\EMEA\Workstations"
    .EXAMPLE
        Update-SEPClientDefinitions -GroupName "My Company\EMEA\Workstations" -IncludeSubGroups
        Sends a command to update content to all computers in "My Company\EMEA\Workstations" and all subgroups
    #>

    
    
    [CmdletBinding()]
    param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ComputerName'
        )]
        [Alias("Hostname", "DeviceName", "Device", "Computer")]
        [String]
        $ComputerName,

        # group name
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'GroupName'
        )]
        [Alias("Group")]
        [String]
        $GroupName,

        # switch parameter to include subgroups
        [Parameter(
            ParameterSetName = 'GroupName'
        )]
        [switch]
        $IncludeSubGroups,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )
    
    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }
    
    process {
        if ($ComputerName) {
            # Get computer ID(s) from computer name(s)
            $ComputerIDList = @()
            foreach ($C in $ComputerName) {
                $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId
                $ComputerIDList += $ComputerID
            }

            $URI = $script:BaseURLv1 + "/command-queue/updatecontent"

            # URI query strings
            $QueryStrings = @{
                computer_ids = $ComputerIDList
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
    
            # prepare the parameters
            $params = @{
                Method  = 'POST'
                Uri     = $URI
                headers = $headers
            }

            $resp = Invoke-ABRestMethod -params $params
            return $resp
        }

        # If by groupname
        elseif ($GroupName) {
            #######################################
            # 1. finds all computers in the group #
            #######################################
            $allComputers = @()
            $URI = $script:BaseURLv1 + "/computers"

            # URI query strings
            $QueryStrings = @{
                sort         = "COMPUTER_NAME"
                pageIndex    = 1
                pageSize     = 100
                computerName = $ComputerName # empty string value to ensure the URI is constructed correctly & query all computers
            }

            # Construct the URI
            $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
    
            # Get computer list
            do {
                try {
                    # prepare the parameters
                    $params = @{
                        Method  = 'GET'
                        Uri     = $URI
                        headers = $headers
                    }

                    $resp = Invoke-ABRestMethod -params $params
                
                    # Process the response
                    $allComputers += $resp.content

                    # Increment the page index & update URI
                    $QueryStrings.pageIndex++
                    $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
                } catch {
                    Write-Warning -Message "Error: $_"
                }
            } until ($resp.lastPage -eq $true)

            # filter list by group name
            # if IncludeSubGroups is specified, then get all computers from subgroups
            if ($IncludeSubGroups) {
                # get all subgroups
                $allComputers = $allComputers | Where-Object { $_.group.name -like "$GroupName*" }
            } else {
                $allComputers = $allComputers | Where-Object { $_.group.name -eq $GroupName }
            }
            
            #################################################
            # 2. send command to all computers in the group #
            #################################################

            $URI = $script:BaseURLv1 + "/command-queue/updatecontent"
            $AllResp = @()
            
            # Send command to each computers in the group individually
            foreach ($id in $allComputers.uniqueId) {
                # URI query strings
                $QueryStrings = @{
                    computer_ids = $id
                }
    
                # Construct the URI
                $URI = Build-SEPMQueryURI -BaseURI $URI -QueryStrings $QueryStrings
        
                # prepare the parameters
                $params = @{
                    Method  = 'POST'
                    Uri     = $URI
                    headers = $headers
                }
                $resp = Invoke-ABRestMethod -params $params
                $AllResp += $resp
            }
            
            # return the response
            return $AllResp
        }
    }
}
#EndRegion '.\Public\Update-SEPClientDefinitions.ps1' 194
#Region '.\Public\Update-SEPMFileFingerprintList.ps1' -1

function Update-SEPMFileFingerprintList {
    <#
    .SYNOPSIS
        Updates an existing fingerprint list
    .DESCRIPTION
        Updates an existing fingerprint list
        When updating the list, overwrite the entire list with the new list
    .PARAMETER FingerprintListName
        The name of the file fingerprint list
        Cannot be used with FingerprintListID parameter
    .PARAMETER FingerprintListID
        The ID of the file fingerprint list
        Cannot be used with FingerprintListName parameter
    .PARAMETER name
        The name of the fingerprint list that will appear on SEPM
    .PARAMETER domainId
        The domain id of the domain to add the fingerprint list to
        Only takes the domain id. Can be found using Get-SEPMDomain
    .PARAMETER HashType
        The type of hash to use for the fingerprint list
        Valid values are SHA256 and MD5
    .PARAMETER description
        The description of the fingerprint list
    .PARAMETER hashlist
        The hash list to add to the fingerprint list
        Can be generated using Get-FileHash or takes a string array of hashes
    .PARAMETER SkipCertificateCheck
        Skip certificate check
    .EXAMPLE
        $domainId = Get-SEPMDomain | Where-Object { $_.name -eq "Default" } | Select-Object -ExpandProperty id
        $hashlist = ls -file C:\Users\$env:USERNAME\Downloads\*.exe | Get-FileHash -algorithm SHA256
        Update-SEPMFileFingerprintList -FingerprintListName "Downloaded .exe files" -name "Workstations downloaded files" -domainId $DomainId -HashType "SHA256" -description "Contains the list of .exe files downloaded with a specific workstations" -hashlist $hashlist.hash
 
        Gets the domain id for the default domain
        Create a hash list of all the files in the downloads folder of the currently logged in user
        Updates the fingerprint list "Downloaded .exe files" with the new hash list
        The fingerprint list needs to be existing before it can be updated
#>

    [CmdletBinding(
        DefaultParameterSetName = 'Name'
    )]
    param (
        [Parameter()]
        [string]$name,

        [Parameter()]
        [string]$domainId,

        [Parameter()]
        [ValidateSet('SHA256', 'MD5')]
        [string]$HashType,

        [Parameter()]
        [string]$description,

        [Parameter(
            ValueFromPipeline = $true
        )]
        $hashlist,

        [Parameter(
            ParameterSetName = 'Name'
        )]
        [string]
        $FingerprintListName,

        [Parameter(
            ParameterSetName = 'ID'
        )]
        [string]
        $FingerprintListID,

        # Skip certificate check
        [Parameter()]
        [switch]
        $SkipCertificateCheck
    )

    begin {
        # initialize the configuration
        $test_token = Test-SEPMAccessToken
        if (-not $test_token) {
            Get-SEPMAccessToken | Out-Null
        }
        if ($SkipCertificateCheck) {
            $script:SkipCert = $true
        }
        $headers = @{
            "Authorization" = "Bearer " + $script:accessToken.token
            "Content"       = 'application/json'
        }
    }

    process {
        # Get the FingerprintListID if the FingerprintListName is provided
        if ($FingerprintListName) {
            $URI = $script:BaseURLv1 + "/policy-objects/fingerprints"
            $FingerprintListID = Get-SEPMFileFingerprintList -FingerprintListName $FingerprintListName | Select-Object -ExpandProperty id
        }

        $URI = $script:BaseURLv1 + "/policy-objects/fingerprints/$FingerprintListID"

        # Construct the body & required fields
        $body = @{
            name        = $name
            domainId    = $domainId
            hashType    = $HashType
            description = $description
            data        = $hashlist
        }

        $params = @{
            Method      = 'POST'
            Uri         = $URI
            headers     = $headers
            Body        = $body | ConvertTo-Json
            ContentType = 'application/json'
        }
    
        $resp = Invoke-ABRestMethod -params $params

        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Update-SEPMFileFingerprintList.ps1' 126
#Region '.\Public\zz_Initialize-SepmConfiguration.ps1' -1

####################################
# Init script for the whole module #
####################################

## This is the initialization script for the module. It is invoked at the end of the module's
## prefix file as "zz_" to load this module at last. This is done to ensure that all other functions are first loaded
## This function should be private but will stay Public for the moment as it needs to be the last function to be loaded in the module
## TODO make this function private

# Update the data types when loading the module
Update-TypeData -PrependPath (Join-Path -Path $PSScriptRoot -ChildPath 'PSSymantecSEPM.Types.ps1xml')

# The credentials used to authenticate to the SEPM server.
[PSCredential]   $script:Credential = $null
[PSCustomObject] $script:accessToken = $null

# SEPM Server configuration
[string] $script:ServerAddress = $null
[string] $script:BaseURLv1 = $null
[string] $script:BaseURLv2 = $null
[bool] $script:SkipCert = $false # Needed for self-signed certificates

# Module name
[string] $script:ModuleName = "PSSymantecSEPM"

# The location of the file that we'll store any settings that can/should roam with the user.
[string] $script:configurationFilePath = [System.IO.Path]::Combine(
    [System.Environment]::GetFolderPath('ApplicationData'),
    'PSSymantecSEPM',
    'config.json')

# The location of the file that we'll store credentials
[string] $script:credentialsFilePath = [System.IO.Path]::Combine(
    [System.Environment]::GetFolderPath('ApplicationData'),
    'PSSymantecSEPM',
    'creds.xml')

# The location of the file that we'll store the Access Token SecureString
# which cannot/should not roam with the user.
[string] $script:accessTokenFilePath = [System.IO.Path]::Combine(
    [System.Environment]::GetFolderPath('LocalApplicationData'),
    'PSSymantecSEPM',
    'accessToken.xml')

# The session-cached copy of the module's configuration properties
[PSCustomObject] $script:configuration = $null

function Initialize-SepmConfiguration {
    <#
    .SYNOPSIS
        Populates the configuration of the module for this session, loading in any values
        that may have been saved to disk.
 
    .DESCRIPTION
        Populates the configuration of the module for this session, loading in any values
        that may have been saved to disk.
 
    .NOTES
        Internal helper method. This is actually invoked at the END of this file.
    #>

    [CmdletBinding()]
    param()

    # Load in the configuration from disk
    $script:configuration = Import-SepmConfiguration -Path $script:configurationFilePath
    if ($script:configuration.ServerAddress -and $script:configuration.port) {
        $script:BaseURLv1 = "https://" + $script:configuration.ServerAddress + ":" + $script:configuration.port + "/sepm/api/v1"
        $script:BaseURLv2 = "https://" + $script:configuration.ServerAddress + ":" + $script:configuration.port + "/sepm/api/v2"
    } else {
        # If no configuration was loaded from disk, or no server address was specified, reset the configuration
        Reset-SEPMConfiguration
    }

    # Load in the credentials from disk
    if (Test-Path $script:credentialsFilePath) {
        try {
            $script:Credential = Import-Clixml -Path $script:credentialsFilePath
        } catch {
            Write-Verbose "No credentials found from '$script:credentialsFilePath': $_"
        }
    }

    # Load in the access token from disk
    if (Test-Path $script:accessTokenFilePath) {
        try {
            $script:accessToken = Import-Clixml -Path $script:accessTokenFilePath
        } catch {
            Write-Verbose "Failed to import access token from '$script:accessTokenFilePath': $_"
        }
    }
    
}

# Invoke the initialization method to populate the configuration
Initialize-SepmConfiguration
#EndRegion '.\Public\zz_Initialize-SepmConfiguration.ps1' 96