PSSymantecSEPM.psm1

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

<#
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]@{
                deleted      = $null
                rulestate    = [object]@{
                    enabled = $null
                    source  = $null
                }
                scancategory = $null
                extensions   = [System.Collections.Generic.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 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] CreateLinuxDirectoriesHashtable(
        [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] AddLinuxDirectories(
        [hashtable] $linux_directories # Use CreateLinuxDirectoriesHashtable 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
        [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 }

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


    # 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' 1194
#Region '.\Private\Build-SEPMQueryURI.ps1' 0
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' 0
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\Invoke-ABRestMethod.ps1' 0
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' 0
function Optimize-ExceptionPolicyStructure {

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

    process {
        # convert the object to a PSCustomObject (trick to convert custom class to PSCustomObject)
        $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' 92
#Region '.\Private\Read-SepmConfiguration.ps1' 0
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' 0
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' 0
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' 0
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' 0
    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' 0
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' 0
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' 0
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

        # If no error, then the certificate is valid
        $script:SkipCert = $false
    } catch {
        # Get SEPM server name from URI
        $uriObject = New-Object System.Uri($URI)
        $domain = $uriObject.Host

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

        # Prompt for user input to continue
        # TODO addd a remove option for user interaction with -skipcertificationcheck
        # $Response = Read-Host -Prompt 'Press enter to ignore this and continue without SSL/TLS secure channel for this session'
        # if ($Response -eq "") {
        if ($PSVersionTable.PSVersion.Major -lt 6) {
            Skip-Cert
        }
        $script:SkipCert = $true
        # }
    }
}
#EndRegion '.\Private\Test-SEPMCertificate.ps1' 57
#Region '.\Public\Add-SEPMFileFingerprintList.ps1' 0
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 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\Backup-SEPMAuthentication.ps1' 0
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' 0
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' 0
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' 0
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' 0
# 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\Get-SEPClientDefVersions.ps1' 0
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.
#>


    [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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPClientDefVersions.ps1' 60
#Region '.\Public\Get-SEPClientInfectedStatus.ps1' 0
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' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPClientStatus.ps1' 54
#Region '.\Public\Get-SEPClientVersion.ps1' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPClientVersion.ps1' 65
#Region '.\Public\Get-SEPComputers.ps1' 0
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 = @{
                sort         = "COMPUTER_NAME"
                pageIndex    = 1
                pageSize     = 100
                computerName = $ComputerName
            }

            # 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
        }

        # 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 }
            }

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

            # return the response
            return $allResults
        }

        # 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' 223
#Region '.\Public\Get-SEPFileDetails.ps1' 0
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' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPGUPList.ps1' 62
#Region '.\Public\Get-SEPMAccessToken.ps1' 0
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' 0
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

        # Process the response
        if ([string]::IsNullOrEmpty($AdminName)) {
            return $resp
        } else {
            $resp = $resp | Where-Object { $_.loginName -eq $AdminName }
            return $resp
        }
    }
}
#EndRegion '.\Public\Get-SEPMAdmins.ps1' 83
#Region '.\Public\Get-SEPMCommandStatus.ps1' 0
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 {
        # prepare the parameters
        $params = @{
            Method  = 'GET'
            Uri     = $URI
            headers = $headers
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMCommandStatus.ps1' 88
#Region '.\Public\Get-SEPMDatabaseInfo.ps1' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMDatabaseInfo.ps1' 67
#Region '.\Public\Get-SEPMDomain.ps1' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMDomain.ps1' 60
#Region '.\Public\Get-SEPMEventInfo.ps1' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMEventInfo.ps1' 64
#Region '.\Public\Get-SEPMExceptionPolicy.ps1' 0
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 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 "Standard Servers - Exception policy"
#>


    [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
        }
        # 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
        
        # return the response
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMExceptionPolicy.ps1' 97
#Region '.\Public\Get-SEPMFileFingerprintList.ps1' 0
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' 0
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()]
    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/firewall"
        $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 "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
        }
    
        $resp = Invoke-ABRestMethod -params $params
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMFirewallPolicy.ps1' 88
#Region '.\Public\Get-SEPMGroups.ps1' 0
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)

        # return the response
        return $allResults
    }
}
#EndRegion '.\Public\Get-SEPMGroups.ps1' 95
#Region '.\Public\Get-SEPMIpsPolicy.ps1' 0
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' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMLatestDefinition.ps1' 55
#Region '.\Public\Get-SEPMPoliciesSummary.ps1' 0
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
 
        content : {@{sources=System.Object[]; enabled=True; desc=Created automatically during product ...}}
        size : 136
        number : 0
        sort :
        numberOfElements : 136
        totalElements : 136
        totalPages : 1
        lastPage : True
        firstPage : True
 
        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
#>


    [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 (-not $PolicyType) {
            # Invoke the request
            try {
                # prepare the parameters
                $params = @{
                    Method  = 'GET'
                    Uri     = $URI
                    headers = $headers
                }

                $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: $_"
            }

            # return the response
            return $resp.content
        }

        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: $_"
            }
            # return the response
            return $resp.content
        }
    }
}
#EndRegion '.\Public\Get-SEPMPoliciesSummary.ps1' 143
#Region '.\Public\Get-SEPMReplicationStatus.ps1' 0
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
        return $resp
    }
}
#EndRegion '.\Public\Get-SEPMReplicationStatus.ps1' 55
#Region '.\Public\Get-SEPMThreatStats.ps1' 0
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
    }
}
#EndRegion '.\Public\Get-SEPMThreatStats.ps1' 55
#Region '.\Public\Get-SEPMVersion.ps1' 0
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\Remove-SEPMFileFingerprintList.ps1' 0
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\Reset-SepmConfiguration.ps1' 0
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' 0
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' 0
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-SEPMCommandClearIronCache.ps1' 0
function Send-SEPMCommandClearIronCache {
    <# # TODO update help
    .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 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 command to quarantine Computer1
    .EXAMPLE
        "Computer1", "Computer2" | Send-SEPMCommandClearIronCache
        Sends a command to quarantine Computer1 and Computer2
    .EXAMPLE
        Send-SEPMCommandClearIronCache -GroupName "My Company\EMEA\Workstations\Site1"
        Sends a command to quarantine all computers in "My Company\EMEA\Workstations\Site1"
        Does not include subgroups
    #>

    
    #TODO finish function
    [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 {
        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/ironcache"

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

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

            # 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
        }

        # 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/ironcache"

            # 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
                ContentType = 'application/json'
            }
            
            $resp = Invoke-ABRestMethod -params $params

            # return the response
            return $resp
        }
    }
}
#EndRegion '.\Public\Send-SEPMCommandClearIronCache.ps1' 212
#Region '.\Public\Send-SEPMCommandGetFile.ps1' 0
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' 0
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' 0
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

    # Saving credentials to disk
    $Credentials | Export-Clixml -Path $script:credentialsFilePath -Force
        
}
#EndRegion '.\Public\Set-SepmAuthentication.ps1' 47
#Region '.\Public\Set-SepmConfiguration.ps1' 0
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' 0
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' 0
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' 0
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-SEPMExceptionPolicy.ps1' 0
function Update-SEPMExceptionPolicy {
    <#
    .SYNOPSIS
        Update a Symantec Endpoint Protection Manager Exception Policy
    .DESCRIPTION
        Update a Symantec Endpoint Protection Manager Exception Policy
    .PARAMETER PolicyName
        The name of the policy to update
    .PARAMETER Description
        The description of the policy
    .PARAMETER EnablePolicy
        Enable the policy
    .PARAMETER DisablePolicy
        Disable the policy
    .PARAMETER WindowsFileException
        Add a Windows File Exception to the policy
    .PARAMETER WindowsFolderException
        Add a Windows Folder Exception to the policy
    .PARAMETER Sonar
        Add the SONAR type of scan to the exception
    .PARAMETER DeleteException
        Delete the exception
    .PARAMETER RulestateEnabled
        Enable the rule
    .PARAMETER RulestateDisabled
        Disable the rule
    .PARAMETER RulestateSource
        Source of the rule
        Default is the module name : PSSymantecSEPM
    .PARAMETER SecurityRiskCategory
        The type of security risk scan to add to the exception
        Valid values are :
            AllScans
            Auto-Protect
            ScheduledAndOndemand
    .PARAMETER PathVariable
        The path variable to use for the exception
        Valid values are :
            [NONE]
            [COMMON_APPDATA]
            [COMMON_DESKTOPDIRECTORY]
            [COMMON_DOCUMENTS]
            [COMMON_PROGRAMS]
            [COMMON_STARTUP]
            [PROGRAM_FILES]
            [PROGRAM_FILES_COMMON]
            [SYSTEM]
            [SYSTEM_DRIVE]
            [USER_PROFILE]
            [WINDOWS]
    .PARAMETER Path
        The path to add to the exception
    .PARAMETER ApplicationControl
        Add the Application Control type of scan to the exception
    .PARAMETER AllScans
        Add all types of scans to the exception
        Based on the exception type
    .PARAMETER ExcludeChildProcesses
        Exclude child processes from the exception
        Specific to Application Control type of scan
        Requires ApplicationControl to be explicitly set to true
    .PARAMETER Recursive
        Add the recursive option to the exception
    .EXAMPLE
        $params = @{
            PolicyName = "Workstations Exception Policy"
            WindowsFileException = $true
            AllScans = $true
            PathVariable = "[COMMON_DESKTOPDIRECTORY]"
            Path = "InternalApplication.exe"
        }
        Update-SEPMExceptionPolicy @params
        (Get-SEPMExceptionPolicy -PolicyName "Workstations Exception Policy").configuration.directories | Where-Object { $_.directory -match "InternalApplication.exe" }
 
        Using splatting, excludes the InternalApplication.exe file located in the Desktop directory from all types of scans
        Get-SEPMExceptionPolicy command verifies that the exception has been added to the policy
    .EXAMPLE
        Update-SEPMExceptionPolicy -PolicyName "Workstations Exception Policy" -Description "Default Workstations policy" -WindowsFileException -AllScans -PathVariable "[COMMON_DESKTOPDIRECTORY]" -Path "InternalApplication.exe"
 
        Same example without splatting, excludes the InternalApplication.exe file located in the Desktop directory from all types of scans
    .EXAMPLE
        $params = @{
            PolicyName = "Workstations Exception Policy"
            WindowsFileException = $true
            Sonar = $true
            Path = "C:\MyCorp\InternalApplication.exe"
        }
        Update-SEPMExceptionPolicy @params
 
        Using splatting, excludes the InternalApplication.exe file located in the C:\MyCorp directory from the SONAR type of scan
    .EXAMPLE
    $params = @{
        PolicyName = "Workstations Exception Policy"
        Path = "C:\MyCorp\InternalApplication.exe"
        DeleteException = $true
    }
    Update-SEPMExceptionPolicy @params
 
    Using splatting, deletes the exception for the InternalApplication.exe file located in the C:\MyCorp directory
    #>

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

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

        # Description
        [Parameter()]
        # [Alias('PolicyDescription')]
        [String]
        $PolicyDescription,

        # Enabled Policy
        [Parameter()]
        [switch]
        $EnablePolicy,

        # Disable Policy
        [Parameter()]
        [switch]
        $DisablePolicy,

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

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

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

        # deleted
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [switch]
        $DeleteException,

        # Looks like this is not used in SEPM
        # RulestateEnabled
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [Alias('EnableRule')]
        [switch]$RulestateEnabled,

        # RulestateDisabled
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [Alias('DisableRuleState')]
        [switch]$RulestateDisabled,

        [Parameter(ParameterSetName = 'WindowsFileException')]
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [string] 
        $RulestateSource = "PSSymantecSEPM",

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

        # Pathvariable
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [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]'
        )]
        [string] 
        $PathVariable = "[NONE]",

        # Path
        [Parameter(ParameterSetName = 'WindowsFileException')]
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [string] 
        $Path,

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

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

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

        # Recursive
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [switch]
        $Recursive,

        # ScanType
        [Parameter(ParameterSetName = 'WindowsFolderException')]
        [ValidateSet(
            'SecurityRisk',
            'SONAR',
            'ApplicationControl',
            'All'
        )]
        [string]
        $ScanType
    )
    
    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'
        }
        $policies = Get-SEPMPoliciesSummary
    }

    process {
        # Get Policy ID from policy name
        $PolicyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id
        
        # Update URI with Policy ID
        $URI = $URI + "/" + $PolicyID

        # Get 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

        # Init
        $ExceptionParams = @{}

        # Common parameters
        # Adding default values if not explicitely provided provided
        # As $PSBoundParameters.Keys doesn't contain default parameters values
        if ($pathvariable -eq "[NONE]") {
            $ExceptionParams.pathvariable = "[NONE]"
        }
        if ($RulestateSource -eq "PSSymantecSEPM") {
            $ExceptionParams.RulestateSource = "PSSymantecSEPM"
        }

        # Exception types are split in groups via switches
        # -WindowsFileException / -WindowsFolderException / etc...
        # Each switch contains the parameters specific to the exception type
        # The parameters are parsed and added to the $ExceptionParams hashtable

        # WindowsFileException
        if ($WindowsFileException) {
            # Parse the parameters provided
            switch ($PSBoundParameters.Keys) {
                "Sonar" {
                    $ExceptionParams.sonar = $true
                }
                "DeleteException" {
                    $ExceptionParams.deleted = $true
                }
                # Looks like this is not used in SEPM
                # TODO verify this RulestateEnabled / RulestateDisabled
                "RulestateEnabled" {
                    $ExceptionParams.RulestateEnabled = $true
                }
                "RulestateDisabled" {
                    $ExceptionParams.RulestateEnabled = $false
                }
                "RulestateSource" {
                    $ExceptionParams.RulestateSource = $RulestateSource
                }
                "SecurityRiskCategory" {
                    $ExceptionParams.securityrisk = $true
                    $ExceptionParams.scancategory = $SecurityRiskCategory
                }
                "PathVariable" {
                    $ExceptionParams.pathvariable = $pathvariable
                }
                "Path" {
                    $ExceptionParams.path = $path
                }
                "ApplicationControl" {
                    $ExceptionParams.applicationcontrol = $true
                }
                "ExcludeChildProcesses" {
                    $ExceptionParams.applicationcontrol = $true
                    $ExceptionParams.recursive = $true
                }
                "AllScans" {
                    $ExceptionParams.securityrisk = $true
                    $ExceptionParams.sonar = $true
                    $ExceptionParams.applicationcontrol = $true
                }
            }

            # Create the file exception object with CreateFilesHashTable
            # Method parameters have to be in the same order as in the method definition
            $FilesHashTable = $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
            $ObjBody.AddConfigurationFilesExceptions($FilesHashTable)
        }

        # WindowsFolderException
        if ($WindowsFolderException) {
            switch ($PSBoundParameters.Keys) {
                "DeleteException" {
                    $ExceptionParams.deleted = $true
                }
                "RulestateEnabled" {
                    $ExceptionParams.RulestateEnabled = $true
                }
                "RulestateSource" {
                    $ExceptionParams.RulestateSource = $RulestateSource
                }
                "SecurityRiskCategory" {
                    # SecurityRiskCategory can only be used if the ScanType parameter is 'SecurityRisk' or 'All'
                    if ($ScanType -eq 'SecurityRisk' -or $ScanType -eq 'All') {
                        $ExceptionParams.scancategory = $SecurityRiskCategory
                    } else {
                        throw "The SecurityRiskCategory parameter can only be used if the ScanType parameter is 'SecurityRisk' or 'All'"
                    }
                }
                "ScanType" {
                    $ExceptionParams.scantype = $ScanType
                }
                "PathVariable" {
                    $ExceptionParams.pathvariable = $pathvariable
                }
                "Path" {
                    $ExceptionParams.directory = $path
                }
                "Recursive" {
                    $ExceptionParams.recursive = $true
                }
                "AllScans" {
                    $ExceptionParams.scancategory = "AllScans"
                    $ExceptionParams.scantype = "All"
                }
            }

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

            # Add the folder exception parameters to the body structure
            $ObjBody.AddConfigurationDirectoriesExceptions($DirectoryHashTable)
        }

        # Verify if updates to the policy are needed
        switch ($psboundparameters.Keys) {
            "EnablePolicy" {
                $ObjBody.enabled = $true
            }
            "DisablePolicy" {
                $ObjBody.enabled = $false
            }
            "Description" {
                $ObjBody.desc = $PolicyDescription
            }
        }

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

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

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

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

        return $resp
    }
}
#EndRegion '.\Public\Update-SEPMExceptionPolicy.ps1' 447
#Region '.\Public\Update-SEPMFileFingerprintList.ps1' 0
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' 0
####################################
# 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

# 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

# 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' 90