PSSymantecCloud.psm1

#Region '.\Classes\Exceptions-Policy.ps1' 0
# https://stackoverflow.com/a/74901407/2552996
class Extensions {
    [Collections.Generic.List[string]] $names = [Collections.Generic.List[string]]::new()
    [bool] $scheduled
    [Collections.Generic.List[string]] $features = [Collections.Generic.List[string]]::new()
}

class UpdateAllowlist {
    [object] $add
    [object] $remove
    UpdateAllowlist() {
        $AllowListStructureAdd = [AllowListStructure]::new()
        $AllowListStructureRemove = [AllowListStructure]::new()
        $this.add = $AllowListStructureAdd
        $this.remove = $AllowListStructureRemove
    }
}

class AllowListStructure {
    [object] $Applications
    [object] $Certificates
    [object] $webdomains
    [object] $ips_hosts
    [Extensions] $Extensions
    [object] $windows
    [object] $linux
    [object] $mac
    # Setting up the PSCustomObject structure from the JSON example : https://pastebin.com/FaKYpgw3
    AllowListStructure() {
        $this.applications = [System.Collections.Generic.List[object]]::new()
        $this.Certificates = [System.Collections.Generic.List[object]]::new()
        $this.webdomains = [System.Collections.Generic.List[object]]::new()
        $this.ips_hosts = [System.Collections.Generic.List[object]]::new()
        # Extensions obj be hashtable. Converting to JSON will not be incorrect format (list instead of k/v pair)
        $this.extensions = [Extensions]::new()
        $this.windows = [PSCustomObject]@{
            files       = [System.Collections.Generic.List[object]]::new()
            directories = [System.Collections.Generic.List[object]]::new()
        }
        $this.Linux = [PSCustomObject]@{
            files       = [System.Collections.Generic.List[object]]::new()
            directories = [System.Collections.Generic.List[object]]::new()
        }
        $this.mac = [PSCustomObject]@{
            files       = [System.Collections.Generic.List[object]]::new()
            directories = [System.Collections.Generic.List[object]]::new()
        }
    }

    # method to add APPLICATIONS tab to the main obj
    [void] AddProcessFile(
        [string] $sha2,
        [string] $name
    ) {
        $this.applications.Add([pscustomobject]@{
                processfile = [pscustomobject]@{
                    sha2 = $sha2
                    name = $name
                }
            })
    }

    # Method to add CERTIFICATES tab to the main obj
    [void] AddCertificates(
        [string] $signature_issuer,
        [string] $signature_company_name,
        # [string] $signature_fingerprint,
        [string] $algorithm,
        [string] $value
    ) {
        # $this.certificates.Add()
        $this.certificates.Add([pscustomobject]@{
                signature_issuer       = $signature_issuer
                signature_company_name = $signature_company_name
                signature_fingerprint  = [pscustomobject]@{
                    algorithm = $algorithm
                    value     = $value
                }
            })
    }

    # Method to add WEBDOMAINS to the main obj
    [void] AddWebDomains(
        [string] $domain
    ) {
        $this.webdomains.add([PSCustomObject]@{
                domain = $domain
            })
    }

    # Method to add IPv4 addresses IPS_HOSTS to the main obj
    [void] AddIpsHostsIpv4Address(
        [string] $ip
    ) {
        $this.ips_hosts.add([PSCustomObject]@{
                ip = $ip
            })
    }

    # Method to add IPv4 subnet IPS_HOSTS to the main obj
    [void] AddIpsHostsIpv4Subnet(
        [string] $ip,
        [string] $mask
    ) {
        $this.ips_hosts.add([pscustomobject]@{
                ipv4_subnet = [pscustomobject]@{
                    ip   = $ip
                    mask = $mask
                }
            })
    }

    # method to add IPv6 subnet IPS_HOSTS to the main obj
    [void] AddIpsHostsIpv6Subnet(
        [string] $ipv6_subnet
    ) {
        $this.ips_hosts.add([pscustomobject]@{
                ipv6_subnet = $ipv6_subnet
            })
    }

    #method to add ip ranges to the main obj
    [void] AddIpsRange(
        [string] $ip_start,
        [string] $ip_end
    ) {
        $this.ips_hosts.add([pscustomobject]@{
                ip_range = [pscustomobject]@{
                    ip_start = $ip_start
                    ip_end   = $ip_end
                }
            })
    }

    # Method to add EXTENSIONS tab to the main obj
    [void] AddExtensions([Extensions] $Extension) {
        $this.Extensions = $Extension
    }

    # Method to add Windows FILES excel tab to obj
    [void] AddWindowsFiles(
        [string] $pathvariable,
        [string] $path,
        [bool] $scheduled,
        [array] $features
    ) {
        $this.windows.files.add([pscustomobject]@{
                pathvariable = $pathvariable
                path         = $path
                scheduled    = $scheduled
                features     = $features
            })
    }

    # Method to add Linux FILES excel tab to obj
    [void] AddLinuxFiles(
        [string] $pathvariable,
        [string] $path,
        [bool] $scheduled,
        [array] $features
    ) {
        $this.linux.files.add([pscustomobject]@{
                pathvariable = $pathvariable
                path         = $path
                scheduled    = $scheduled
                features     = $features
            })
    }

    # Method to add Mac FILES excel tab to obj
    [void] AddMacFiles(
        [string] $pathvariable,
        [string] $path,
        [bool] $scheduled,
        [array] $features
    ) {
        $this.mac.files.add([pscustomobject]@{
                pathvariable = $pathvariable
                path         = $path
                scheduled    = $scheduled
                features     = $features
            })
    }

    # Method to add Windows DIRECTORIES excel tab to obj
    [void] AddWindowsDirectories(
        [string] $pathvariable,
        [string] $directory,
        [bool] $recursive,
        [bool] $scheduled,
        [array] $features
    ) {
        $this.windows.directories.add([pscustomobject]@{
                pathvariable = $pathvariable
                directory    = $directory
                recursive    = $recursive
                scheduled    = $scheduled
                features     = $features
            })
    }

    # Method to add Linux DIRECTORIES excel tab to obj
    [void] AddLinuxDirectories(
        [string] $pathvariable,
        [string] $directory,
        [bool] $recursive,
        [bool] $scheduled,
        [array] $features
    ) {
        $this.linux.directories.add([pscustomobject]@{
                pathvariable = $pathvariable
                directory    = $directory
                recursive    = $recursive
                scheduled    = $scheduled
                features     = $features
            })
    }

    # Method to add Mac DIRECTORIES excel tab to obj
    [void] AddMacDirectories(
        [string] $pathvariable,
        [string] $directory,
        [bool] $recursive,
        [bool] $scheduled,
        [array] $features
    ) {
        $this.mac.directories.add([pscustomobject]@{
                pathvariable = $pathvariable
                directory    = $directory
                recursive    = $recursive
                scheduled    = $scheduled
                features     = $features
            })
    }
}

# TODO - Verify the structure is the expected one when converting to JSON
class DenyListStructure {
    [object] $blacklistrules
    [object] $nonperules
    denylistStructure() {
        $this.blacklistrules = [System.Collections.Generic.List[object]]::new()
        $this.nonperules = [System.Collections.Generic.List[object]]::new()
    }

    # Method to add blacklist rules to the main obj (called "Executable files" in the cloud policy)
    [void] AddBlacklistRules(
        [string] $sha2,
        [string] $name
    ) {
        $this.blacklistrules.Add([pscustomobject]@{
                processfile = [pscustomobject]@{
                    sha2 = $sha2
                    name = $name
                }
            })
    }

    # Method to add nonpe rules to the main obj (called "Non-executable files" in the cloud policy)
    [void] AddNonPeRules(
        [string] $file_name,
        [string] $file_sha2,
        [int] $file_size,
        [string] $file_directory,
        [string] $actor_directory,
        [string] $actor_sha2,
        [string] $actor_md5
    ) {
        $this.nonperules.Add([pscustomobject]@{
                file = [pscustomobject]@{
                    name      = $file_name
                    sha2      = $file_sha2
                    size      = $file_size
                    directory = $file_directory
                }
            })
        # Add actor directory if it is not empty
        if ($actor_directory -ne "") {
            $this.nonperules.file.actor = [pscustomobject]@{
                directory = $actor_directory
            }
        }
        # Add actor sha2 if it is not empty
        if ($actor_sha2 -ne "") {
            $this.nonperules.file.actor = [pscustomobject]@{
                sha2 = $actor_sha2
            }
        }
        # Add actor md5 if it is not empty
        if ($actor_md5 -ne "") {
            $this.nonperules.file.actor = [pscustomobject]@{
                md5 = $actor_md5
            }
        }
    }
}
class UpdateDenylist {
    [object] $add
    [object] $remove
    UpdateDenylist() {
        $DenyListStructureAdd = [DenyListStructure]::new()
        $DenyListStructureRemove = [DenyListStructure]::new()
        $this.add = $DenyListStructureAdd
        $this.remove = $DenyListStructureRemove
    }

}
#EndRegion '.\Classes\Exceptions-Policy.ps1' 308
#Region '.\Private\Build-QueryURI.ps1' 0
function Build-QueryURI {
    <#
    .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://Server01:8446/sepm/api/v1/computers"
        $QueryStrings = @{
            sort = "COMPUTER_NAME"
            pageIndex = 1
            pageSize = 100
        }
        $URI = Build-QueryURI -BaseURI $BaseURI -QueryStrings $QueryStrings
 
        This example will :
        - Construct a URI from the base URI "https://Server01:8446/sepm/api/v1/computers"
        - With the query strings "sort=COMPUTER_NAME&pageIndex=1&pageSize=100"
        resulting in "https://Server01:8446/sepm/api/v1/computers?sort=COMPUTER_NAME&pageIndex=1&pageSize=100"
    #>



    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()
    $URL = $builder.Uri.AbsoluteUri
    return $URL
}
#EndRegion '.\Private\Build-QueryURI.ps1' 44
#Region '.\Private\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 '.\Private\ConvertTo-FlatObject.ps1' 162
#Region '.\Private\Get-ExcelAllowListObject.ps1' 0
function Get-ExcelAllowListObject {
    <#
    .SYNOPSIS
        Imports excel allow list report from its file path as a PSObject
    .DESCRIPTION
        Imports excel allow list report as a PSObject.
        Same structure that Get-SepCloudPolicyDetails uses to compare Excel allow list and SEP Cloud allow list policy
    .EXAMPLE
        Get-ExcelAllowListObject -Path "WorkstationsAllowListPolicy.xlsx"
        Imports the excel file and returns a structured PSObject
    .INPUTS
        Excel path of allow list policy previously generated from Export-SepCloudAllowListPolicyToExcel CmdLet
    .OUTPUTS
        Custom PSObject
    #>


    param (
        # excel path
        [Parameter(
            ValueFromPipeline
        )]
        [string[]]
        [Alias('Excel', 'Path')]
        $excel_path
    )

    process {

        # List all excel tabs
        $AllSheets = Get-ExcelSheetInfo $excel_path
        $SheetsInfo = @{}
        # Import all Excel info in $SheetsInfo hashtable
        $AllSheets | ForEach-Object { $SheetsInfo[$_.Name] = Import-Excel $_.Path -WorksheetName $_.Name }

        # Get Object from ExceptionStructure Class
        $obj_policy_excel = [AllowListStructure]::new()

        ###############################
        # Populates $obj_policy_excel #
        ###############################

        # Add Applications
        foreach ($line in $SheetsInfo['Applications']) {
            $obj_policy_excel.AddProcessFile(
                $line.sha2,
                $line.Name
            )
        }

        # Add Files
        foreach ($line in $SheetsInfo['Files']) {
            # Parse "features.X" properties to gather the feature_names in an array
            [array]$feature_names = @()
            [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature
            $i = 0
            foreach ($feat in $nb_features) {
                if ($null -ne $line.($nb_features[$i])) {
                    $feature_names += $line.($nb_features[$i])
                }
                $i++
            }
            # Use AddWindowsFiles
            $obj_policy_excel.AddWindowsFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $feature_names
            )
        }

        # Add Directories
        foreach ($line in $SheetsInfo['Directories']) {
            # Parse "features.X" properties to gather the feature names in an array
            [array]$feature_names = @()
            [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature
            $i = 0
            foreach ($feat in $nb_features) {
                if ($null -ne $line.($nb_features[$i])) {
                    $feature_names += $line.($nb_features[$i])
                }
                $i++
            }
            # Use AddWindowsDirectories
            $obj_policy_excel.AddWindowsDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $feature_names
            )
        }

        # Add Extensions
        # no loop required, whole array needed
        $obj_policy_excel.AddExtensions(@{
                names     = $sheetsInfo['Extensions'].extensions
                scheduled = $true
                features  = 'AUTO_PROTECT'
            }
        )

        # Add WebDomains
        foreach ($line in $SheetsInfo['Webdomains']) {
            $obj_policy_excel.AddWebDomains(
                $line.domain
            )
        }

        # Add IPS Hosts
        foreach ($line in $SheetsInfo['Ips_Hosts']) {
            $obj_policy_excel.AddIpsHostsIpv4Address(
                $line.ip
            )
        }

        # Add IPS Subnet v4
        foreach ($line in $SheetsInfo['Ips_Hosts_subnet_v4']) {
            $obj_policy_excel.AddIpsHostsIpv4Subnet(
                $line.ip,
                $line.mask
            )
        }

        # Add IPS Subnet v6
        foreach ($line in $SheetsInfo['Ips_Hosts_subnet_v6']) {
            $obj_policy_excel.AddIpsHostsIpv6Subnet(
                $line.ipv6_subnet
            )
        }

        # Add IPs ranges (includes both IPv4 & v6)
        foreach ($line in $SheetsInfo['Ips_range']) {
            $obj_policy_excel.AddIpsRange(
                $line.ip_start,
                $line.ip_end
            )
        }

        # Add Certificates
        foreach ($line in $SheetsInfo['Certificates']) {
            $obj_policy_excel.AddCertificates(
                $line.signature_issuer,
                $line.signature_company_name,
                $line."signature_fingerprint.algorithm",
                $line."signature_fingerprint.value"
            )
        }

        # Add Linux Files
        foreach ($line in $SheetsInfo['Linux Files']) {
            # Parse "features.X" properties to gather the feature_names in an array
            [array]$feature_names = @()
            [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature
            $i = 0
            foreach ($feat in $nb_features) {
                if ($null -ne $line.($nb_features[$i])) {
                    $feature_names += $line.($nb_features[$i])
                }
                $i++
            }
            # Use AddLinuxFiles
            $obj_policy_excel.AddLinuxFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $feature_names
            )
        }

        # Add Mac Files
        foreach ($line in $SheetsInfo['Mac Files']) {
            # Parse "features.X" properties to gather the feature_names in an array
            [array]$feature_names = @()
            [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature
            $i = 0
            foreach ($feat in $nb_features) {
                if ($null -ne $line.($nb_features[$i])) {
                    $feature_names += $line.($nb_features[$i])
                }
                $i++
            }
            # Use AddMacFiles
            $obj_policy_excel.AddMacFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $feature_names
            )
        }

        # Add Linux Directories
        foreach ($line in $SheetsInfo['Linux Directories']) {
            # Parse "features.X" properties to gather the feature names in an array
            [array]$feature_names = @()
            [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature
            $i = 0
            foreach ($feat in $nb_features) {
                if ($null -ne $line.($nb_features[$i])) {
                    $feature_names += $line.($nb_features[$i])
                }
                $i++
            }
            # Use AddLinuxDirectories
            $obj_policy_excel.AddLinuxDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $feature_names
            )
        }

        # Add Mac Directories
        foreach ($line in $SheetsInfo['Mac Directories']) {
            # Parse "features.X" properties to gather the feature names in an array
            [array]$feature_names = @()
            [array]$nb_features = $line.PSObject.properties.name | Select-String -Pattern feature
            $i = 0
            foreach ($feat in $nb_features) {
                if ($null -ne $line.($nb_features[$i])) {
                    $feature_names += $line.($nb_features[$i])
                }
                $i++
            }
            # Use AddMacDirectories
            $obj_policy_excel.AddMacDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $feature_names
            )
        }

        return $obj_policy_excel
    }
}
#EndRegion '.\Private\Get-ExcelAllowListObject.ps1' 238
#Region '.\Private\Get-SEPCloudAPIData.ps1' 0
function Get-SEPCloudAPIData {
    [CmdletBinding()]
    param (
        $endpoint
    )

    process {
        $api = @{
            'Example'                                  = @{
                '1.0' = @{
                    Description = 'Details about the API endpoint'
                    URI         = 'The URI expressed as /api/v#/endpoint'
                    Method      = 'Method to use against the endpoint'
                    Body        = 'Parameters to use in the body'
                    Query       = 'Parameters to use in the URI query'
                    Result      = 'If the result content is stored in a higher level key, express it here to be unwrapped in the return'
                    Success     = 'The expected HTTP status code for a successful call'
                    Function    = 'The PowerShell function to call to process the result'
                    ObjectTName = 'The name of the PSType object to return'
                }
            }
            'Block-SEPCloudFile'                       = @{
                '1.0' = @{
                    Description = 'Quarantine files on the device(s)'
                    URI         = '/v1/commands/files/contain'
                    Method      = 'Post'
                    Body        = @{
                        device_ids = 'device_ids'
                        hash       = 'hash'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Block-SEPCloudFile'
                    ObjectTName = 'SEPCloud.block-file'
                }
            }
            'Connect-SEPCloud'                         = @{
                '1.0' = @{
                    Description = 'Generate new bearer token from the from the oAuth credential'
                    URI         = '/v1/oauth2/tokens'
                    Method      = 'Post'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Filter      = ''
                    Success     = '200'
                }
            }
            'Get-SEPCloudComponentType'                = @{
                '1.0' = @{
                    Description = 'lets you retrieve policy component host-groups, network-adapters(adapter), network-services(Connection), network IPS details'
                    URI         = '/v1/policies/components'
                    Method      = 'Get'
                    Body        = ''
                    Query       = @{
                        'offset' = 'offset'
                        'limit'  = 'limit'
                    }
                    Result      = 'data'
                    Success     = ''
                    Function    = 'Get-SEPCloudComponentType'
                    ObjectTName = 'SEPCloud.policyComponentType' # generic PSObject but there could be up to 4 different subtypes
                    # host-group-response
                    # network-services
                    # network-adapter
                    # network_ips_response
                }
            }
            'Get-SEPCloudDevice'                       = @{
                '1.0' = @{
                    Description = 'retrieve the list of devices'
                    URI         = '/v1/devices'
                    Method      = 'Get'
                    Body        = ''
                    Query       = @{
                        offset               = 'offset'
                        client_version       = 'client_version'
                        device_group         = 'device_group' # ID of the parent device group
                        device_status        = 'device_status'
                        device_status_reason = 'device_status_reason'
                        device_type          = 'device_type'
                        edr_enabled          = 'edr_enabled'
                        ipv4_address         = 'ipv4_address'
                        include_details      = 'include_details' # flag to include product and feature details in response. Possible values: true/false
                        is_cloud             = 'is_cloud'
                        is_online            = 'is_online'
                        is_virtual           = 'is_virtual'
                        mac_address          = 'mac_address'
                        name                 = 'name'
                        os                   = 'os' # Possible values: windows, Linux, iOS, Mac, Android
                        os_version           = 'os_version'
                    }
                    Result      = 'devices'
                    Success     = ''
                    Function    = 'Get-SEPCloudDevice'
                    ObjectTName = 'SEPCloud.Device'
                }
            }
            'Get-SEPCloudDeviceDetails'                = @{
                '1.0' = @{
                    Description = 'Details about the SEP client'
                    URI         = '/v1/devices'
                    Method      = 'Get'
                    Body        = ''
                    Query       = @{
                        device_id = 'device_id'
                    }
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudDeviceDetails'
                    ObjectTName = 'SEPCloud.device-details '
                }
            }
            'Get-SEPCloudEDRDumpsList'                 = @{
                '1.0' = @{
                    Description = 'get the list of endpoint search commands'
                    URI         = '/v1/commands/endpoint-search'
                    Method      = 'Post'
                    Body        = @{
                        query = 'query'
                        next  = 'next'
                        limit = 'limit'
                    }
                    Query       = ''
                    Result      = 'commands'
                    Success     = ''
                    Function    = 'Get-SEPCloudEDRDumpsList'
                    ObjectTName = 'SEPCloud.commandsResponse'
                }
            }
            'Get-SEPCloudEvents'                       = @{
                '1.0' = @{
                    Description = 'retrieve up to ten thousand events'
                    URI         = '/v1/event-search'
                    Method      = 'Post'
                    Body        = @{
                        feature_name = 'feature_name'
                        product      = 'product'
                        query        = 'query'
                        start_date   = 'start_date'
                        end_date     = 'end_date'
                        next         = 'next'
                        limit        = 'limit'
                    }
                    Query       = ''
                    Result      = 'events'
                    Success     = ''
                    Function    = 'Get-SEPCloudEvents'
                    ObjectTName = 'SEPCloud.Event'
                }
            }
            'Get-SEPCloudFileHashDetails'              = @{
                '1.0' = @{
                    Description = 'retrieve all details of a file based off its hash'
                    URI         = '/v1/threat-intel/protection/file/{id}'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudFileHashDetails'
                    ObjectTName = 'SEPCloud.files-hash'
                }
            }
            'Get-SEPCloudGroup'                        = @{
                '1.0' = @{
                    Description = 'retrieve a list of device groups'
                    URI         = '/v1/device-groups'
                    Method      = 'Get'
                    body        = ''
                    Query       = @{
                        offset = 'offset'
                    }
                    Result      = 'device_groups'
                    Success     = '200'
                    Function    = 'Get-SEPCloudGroupTest'
                    ObjectTName = 'SEPCloud.Device-Group' # root object is 'SEPCloud.Device-Group-List' but children objects only are exposed as 'SEPCloud.Device-Group'
                }
            }
            'Get-SEPCloudGroupPolicies'                = @{
                '1.0' = @{
                    Description = 'retrieve a list of policies that are targeted on a device group'
                    URI         = '/v1/device-groups/{id}/policies'
                    Method      = 'Get'
                    body        = ''
                    Query       = @{
                        group_id = 'group_id'
                    }
                    Result      = 'policies'
                    Success     = '200'
                    Function    = 'Get-SEPCloudGroupPolicies'
                    ObjectTName = 'SEPCloud.targeted-policy'
                }
            }
            'Get-SepCloudIncidentDetails'              = @{
                '1.0' = @{
                    Description = 'retrieve details for a specific incident'
                    URI         = '/v1/incidents'
                    Method      = 'Get'
                    body        = ''
                    Query       = @{
                        incident_id = 'incident_id'
                    }
                    Result      = 'incident'
                    Success     = '200'
                    Function    = 'Get-SepCloudIncidentDetails'
                    ObjectTName = 'SEPCloud.incident-details'
                }
            }
            'Get-SEPCloudPolicesSummary'               = @{
                '1.0' = @{
                    Description = 'retrieve a list of your policies (without details)'
                    URI         = '/v1/policies'
                    Method      = 'Get'
                    Body        = ''
                    Query       = @{
                        limit  = 'limit'
                        offset = 'offset'
                        name   = 'name'
                        type   = 'type'
                    }
                    Result      = 'policies'
                    Success     = ''
                    Function    = 'Get-SEPCloudPolicesSummary'
                    ObjectTName = 'SEPCloud.policy'
                }
            }
            'Get-SepCloudTargetRules'                  = @{
                '1.0' = @{
                    Description = 'retrieve a list of target rules'
                    URI         = '/v1/policies/target-rules'
                    Method      = 'Get'
                    Body        = ''
                    Query       = @{
                        limit  = 'limit'
                        offset = 'offset'
                    }
                    Result      = 'target_rules'
                    Success     = ''
                    Function    = 'Get-SepCloudTargetRules'
                    ObjectTName = 'SEPCloud.target-rule'
                }
            }
            'Get-SEPCloudThreatIntelCveProtection'     = @{
                '1.0' = @{
                    Description = 'returns information whether a given CVE has been blocked by any Symantec technologies'
                    URI         = '/v1/threat-intel/protection/cve'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelCveProtection'
                    ObjectTName = 'SEPCloud.cve-protection'
                }
            }
            'Get-SEPCloudThreatIntelFileInsight'       = @{
                '1.0' = @{
                    Description = 'returns file insight enrichments for given file sha256'
                    URI         = '/v1/threat-intel/insight/file'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelFileInsight'
                    ObjectTName = 'SEPCloud.file-insight'
                }
            }
            'Get-SEPCloudThreatIntelNetworkInsight'    = @{
                '1.0' = @{
                    Description = 'returns network enrichments for given URL, domain or IP'
                    URI         = '/v1/threat-intel/insight/network'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelNetworkInsight'
                    ObjectTName = 'SEPCloud.network-insight'
                }
            }
            'Get-SEPCloudThreatIntelFileProtection'    = @{
                '1.0' = @{
                    Description = 'returns information whether a given file has been blocked by any Symantec technologies'
                    URI         = '/v1/threat-intel/protection/file'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelFileProtection'
                    ObjectTName = 'SEPCloud.file-protection'
                }
            }
            'Get-SEPCloudThreatIntelFileRelated'       = @{
                '1.0' = @{
                    Description = 'returns information whether a given file has been blocked by any Symantec technologies'
                    URI         = '/v1/threat-intel/related/file/'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelFileRelated'
                    ObjectTName = 'SEPCloud.file-related'
                }
            }
            'Get-SEPCloudThreatIntelNetworkProtection' = @{
                '1.0' = @{
                    Description = 'returns information whether a given file has been blocked by any Symantec technologies'
                    URI         = '/v1/threat-intel/protection/network'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelNetworkProtection'
                    ObjectTName = 'SEPCloud.network-protection'
                }
            }
            'Get-SEPCloudThreatIntelFileProcessChain'  = @{
                '1.0' = @{
                    Description = 'returns topK process lineage enrichment for the provided file sha256'
                    URI         = '/v1/threat-intel/processchain/file'
                    Method      = 'Get'
                    Body        = ''
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Get-SEPCloudThreatIntelFileProcessChain'
                    ObjectTName = 'SEPCloud.file-processchain'
                }
            }
            'Move-SEPCloudDevice'                      = @{
                '1.0' = @{
                    Description = 'lets you transfer multiple devices from one device group to another device group'
                    URI         = '/v1/device-groups/{id}/devices'
                    Method      = 'Put'
                    Body        = @{
                        device_uids = 'device_uids'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Move-SEPCloudDevice'
                    ObjectTName = 'SEPCloud.Bulk-Device-List'
                }
            }
            'New-SEPCloudEDRFullDump'                  = @{
                '1.0' = @{
                    Description = 'Send the full dump command on the device'
                    URI         = '/v1/commands/endpoint-search/fulldump'
                    Method      = 'Post'
                    Body        = @{
                        device_id   = 'device_id'
                        description = 'description'
                        from_date   = 'from_date'
                        to_date     = 'to_date'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'New-SEPCloudEDRFullDump'
                    ObjectTName = 'SEPCloud.DumpCommandResponse'
                }
            }
            'Remove-SEPCloudPolicy'                    = @{
                '1.0' = @{
                    Description = 'Removes a SEP Cloud policy from a device group'
                    URI         = '/v1/policies/{id}/versions/{id}/device-groups'
                    Method      = 'Delete'
                    Body        = @{
                        target_rules     = 'target_rules'
                        device_group_ids = 'device_group_ids'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Remove-SEPCloudPolicy'
                    ObjectTName = 'SEPCloud.remove-policy'
                }
            }
            'Set-SEPCloudPolicy'                       = @{
                '1.0' = @{
                    Description = 'apply a policy to device groups'
                    URI         = '/v1/policies/{id}/versions/{id}/device-groups'
                    Method      = 'Post'
                    Body        = @{
                        target_rules     = 'target_rules'
                        device_group_ids = 'device_group_ids'
                        override         = 'override'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = '204'
                    Function    = 'Set-SEPCloudPolicy'
                    ObjectTName = 'SEPCloud.apply-policy'
                }
            }
            'Start-SEPCloudDefinitionUpdate'           = @{
                '1.0' = @{
                    Description = 'Update security definitions of devices'
                    URI         = '/v1/commands/update_content'
                    Method      = 'Post'
                    Body        = @{
                        device_ids   = 'device_ids'
                        org_unit_ids = 'org_unit_ids'
                        is_recursive = 'is_recursive'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Start-SEPCloudDefinitionUpdate'
                    ObjectTName = 'SEPCloud.DeviceCommandResponse'
                }
            }
            'Start-SEPCloudFullScan'                   = @{
                '1.0' = @{
                    Description = 'initiate a full scan on devices managed'
                    URI         = '/v1/commands/scans/full'
                    Method      = 'Post'
                    Body        = @{
                        device_ids   = 'device_ids'
                        org_unit_ids = 'org_unit_ids'
                        is_recursive = 'is_recursive'
                    }
                    Query       = ''
                    Result      = ''
                    Success     = ''
                    Function    = 'Start-SEPCloudFullScan'
                    ObjectTName = 'SEPCloud.DeviceCommandResponse'
                }
            }
        }

        # Use the latest version of the API endpoint
        $version = $api.$endpoint.Keys | Sort-Object | Select-Object -Last 1

        if ($null -eq $version) {
            $ErrorSplat = @{
                Message      = "No matching endpoint found for $Endpoint that corresponds to the current cluster version."
                ErrorAction  = 'Stop'
                TargetObject = $api.$endpoint.keys -join ','
                Category     = 'ObjectNotFound'
            }
            Write-Error @ErrorSplat
        } else {
            Write-Verbose -Message "Selected $version API Data for $endpoint"
            return $api.$endpoint.$version
        }
    }
}
#EndRegion '.\Private\Get-SEPCloudAPIData.ps1' 455
#Region '.\Private\Get-SEPCloudGroupFullPath.ps1' 0
function Get-SEPCloudGroupFullPath {
    <#
    .SYNOPSIS
        Recursively builds a chain of group names from a group to the root.
    .DESCRIPTION
        Recursively builds a chain of group names from a group to the root.
    .EXAMPLE
        Get-SEPCloudGroupFullPath -CurrentGroup $Group -AllGroups $Groups
    #>



    param (
        [PSCustomObject]$CurrentGroup,
        [Array]$AllGroups,
        [String]$Chain = ""
    )

    # If $allGroups is provided, build the chain from the list of groups.
    if ($AllGroups) {
        # If the current group is root (no parent_id), prepend its name to the chain.
        if (-not $CurrentGroup.parent_id) {
            if ($Chain -eq "") {
                return $CurrentGroup.name # If chain is empty, it's the root group.
            } else {
                return $CurrentGroup.name + "\" + $Chain # Prepend root name to chain.
            }
        } else {
            # Find the parent group.
            $ParentGroup = $AllGroups | Where-Object { $_.id -eq $CurrentGroup.parent_id }
            if ($ParentGroup) {
                # If there's a parent, prepend the parent's name to the chain and recurse.
                $NewChain = if ($Chain -eq "") { $CurrentGroup.name } else { $CurrentGroup.name + "\" + $Chain }
                return Get-SEPCloudGroupFullPath -CurrentGroup $ParentGroup -AllGroups $AllGroups -Chain $NewChain
            } else {
                # If no parent found (which shouldn't happen), return the current chain.
                return $Chain
            }
        }
    }
    # If the allGroups parameter was not provided, recursively build the path by querying the API group by group ID.
    else {
        # If the current group is root (no parent_id), prepend its name to the chain.
        if (-not $CurrentGroup.parent_id) {
            if ($Chain -eq "") {
                return $CurrentGroup.name # If chain is empty, it's the root group.
            } else {
                return $CurrentGroup.name + "\" + $Chain # Prepend root name to chain.
            }
        } else {
            # Find the parent group.
            $parentGroupId = $CurrentGroup.parent_id
            if ($parentGroupId) {
                # If there's a parent
                $parentGroup = Get-SEPCloudGroup -GroupID $parentGroupId

                # prepend the parent's name to the chain and recurse.
                $NewChain = if ($Chain -eq "") { $CurrentGroup.name } else { $CurrentGroup.name + "\" + $Chain }
                return Get-SEPCloudGroupFullPath -CurrentGroup $parentGroup -Chain $NewChain
            } else {
                # If no parent found (which shouldn't happen), return the current chain.
                return $Chain
            }
        }
    }



}
#EndRegion '.\Private\Get-SEPCloudGroupFullPath.ps1' 69
#Region '.\Private\Invoke-SEPCloudWebRequest.ps1' 0
function Invoke-SEPCloudWebRequest {
    <#
    .SYNOPSIS
        Gather WebRequest from a URL or redirect URL.
    .DESCRIPTION
        Gather WebRequest from a URL or redirect URL. Preserves the Authorization header upon redirect.
        This function is a wrapper around the System.Net.WebRequest class.
    .PARAMETER Uri
        URL to gather WebRequest from.
    .PARAMETER Method
        HTTP method to use.
    .PARAMETER Headers
        Headers to include in the request.
        Must be a hashtable as per example below.
    .OUTPUTS
        JSON object
    .EXAMPLE
        $params = @{
            Method = 'GET'
            Uri = "https://example.com/v1/endpoint"
            Headers = @{
                Host = "https://example.com/v1/endpoint"
                Accept = "application/json"
                Authorization = "Bearer xxxxxxxx"
            }
            queryParameters = @{
                "ComputerName" = "MyComputer01"
            }
        }
        Invoke-SEPCloudWebRequest @params
 
        This example will :
        - Send a GET request to https://example.com/v1/endpoint
        - With the Authorization header set to "Bearer xxxxxxxx"
        - With the query parameter ComputerName set to "MyComputer01" (https://example.com/v1/endpoint?ComputerName=MyComputer01)
    .EXAMPLE
        $params = @{
            Method = 'POST'
            Uri = "https://example.com/v1/endpoint"
            Headers = @{
                Host = "https://example.com/v1/endpoint"
                Accept = "application/json"
                Authorization = "Bearer xxxxxxxx"
            }
            body = @{
                "ComputerName" = "MyComputer01"
            }
        }
        Invoke-SEPCloudWebRequest @params
 
        This example will :
        - Send a POST request to https://example.com/v1/endpoint
        - With the Authorization header set to "Bearer xxxxxxxx"
        - With the body set to JSON format {"ComputerName":"MyComputer01"}
    #>



    [CmdletBinding()]
    param (
        # URL
        [Parameter(Mandatory = $true)]
        [string]$uri,

        # Method
        [Parameter(Mandatory = $true)]
        [string]$method,

        # List of headers
        [Parameter(Mandatory = $true)]
        [hashtable]$headers,

        # Query parameters
        [hashtable]$queryStrings = @{},

        # Body
        [hashtable]$body = @{}
    )

    process {
        # Add query parameters
        if ($queryStrings.Count -gt 0) {
            # Construct the URI
            $uri = Build-QueryURI -BaseURI $uri -QueryStrings $queryStrings
        }

        # Initial request
        $initialRequest = [System.Net.WebRequest]::CreateHttp($uri);
        $initialRequest.Method = $method
        $initialRequest.AllowAutoRedirect = $false

        # Add body
        if ($body.Count -gt 0) {
            $initialRequest.ContentType = "application/json"
            $json = $body | ConvertTo-Json -Depth 100
            $bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
            $initialRequest.ContentLength = $bytes.Length
            $initialRequestStream = $initialRequest.GetRequestStream()
            $initialRequestStream.Write($bytes, 0, $bytes.Length)
            $initialRequestStream.Close()
        }

        # Add headers
        foreach ($header in $Headers.GetEnumerator()) {
            $initialRequest.Headers.Add($header.Key, $header.Value)
        }

        # Send the initial request
        try {
            $inititalResponse = $initialRequest.GetResponse();
            Write-Verbose -Message "URI = $($inititalResponse.GetResponseHeader("Location"))"
            Write-Verbose -Message "Status code : $($inititalResponse.StatusCode)"
        } catch {
            throw $_
        }

        # IF HTTP status code is linked to redirected URL
        if ($inititalResponse.StatusCode.value__ -in (301, 302, 303, 307, 308)) {
            Write-Verbose -Message "URI to redirect : $($inititalResponse.GetResponseHeader("Location"))"
            # Create new request with the redirect URL
            $redirectUrl = $inititalResponse.GetResponseHeader("Location")
            $newRequest = [System.Net.WebRequest]::CreateHttp($redirectUrl);
            $newRequest.Method = $Method
            $newRequest.AllowAutoRedirect = $false

            # Add query parameters
            if ($queryStrings.Count -gt 0) {
                # Construct the URI
                $uri = Build-QueryURI -BaseURI $uri -QueryStrings $queryStrings
            }

            # Add body
            if ($body.Count -gt 0) {
                $newRequest.ContentType = "application/json"
                $json = $body | ConvertTo-Json -Depth 100
                $bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
                $newRequest.ContentLength = $bytes.Length
                $newRequestStream = $newRequest.GetRequestStream()
                $newRequestStream.Write($bytes, 0, $bytes.Length)
                $newRequestStream.Close()
            }

            # Add headers
            # TODO verify why when adding all the headers and not just the Authorization header, the request fails wih 400
            # Reuse all headers from the initial request (including Authorization header)
            foreach ($header in $Headers.GetEnumerator()) {
                $newRequest.Headers.Add($header.Key, $header.Value)
            }
            # $newRequest.Headers.Add("Authorization", $Headers.Authorization)
            # $newRequest.Headers.Add("Accept", $Headers.Accept)
            # $newRequest.Headers.Add("Host", $Headers.Host)

            # Send the new request
            try {
                $newResponse = $newRequest.GetResponse()
                Write-Verbose -Message "Status Code : $($newResponse.StatusCode.value__)"
            } catch {
                Write-Error "Error in Invoke-SEPCloudWebRequest: $($_.Exception.InnerException.Message)"
            }

            # Parse the response
            $stream = $newResponse.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($stream)
            $content = $reader.ReadToEnd()
        } else {
            # Get the response from the initial request
            $stream = $inititalResponse.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($stream)
            $content = $reader.ReadToEnd()
        }

        return $content | ConvertFrom-Json -Depth 100
    }
}
#EndRegion '.\Private\Invoke-SEPCloudWebRequest.ps1' 174
#Region '.\Private\Merge-SEPCloudAllowList.ps1' 0
function Merge-SepCloudAllowList {
    <#
    .SYNOPSIS
        Merges 2 SEP Cloud allow list policy to a single PSObject
    .DESCRIPTION
        Returns a custom PSObject ready to be converted in json as HTTP Body for Update-SepCloudAllowlistPolicy CmdLet
        Excel file takes precedence in case of conflicts. It is the main "source of truth".
        Logic goes as below
        - If SEP exception present in both excel & policy : no changes
        - If SEP exception present only in Excel : add exception
        - If SEP exception present only in policy (so not in Excel) : remove exception
    .NOTES
        Excel file takes precedence in case of conflicts
    .INPUTS
        - SEP cloud allow list policy PSObject
        - Excel report file path (previously generated from Export-SepCloudAllowListPolicyToExcel CmdLet)
    .OUTPUTS
        - Custom PSObject
    .EXAMPLE
        Merge-SepCloudAllowList -Policy_Name "My Allow List Policy For Servers" -Excel ".\Data\Centralized_exceptions_for_servers.xlsx" | Update-SepCloudAllowlistPolicy
    #>

    [CmdletBinding()]
    param (
        # Policy version
        [Parameter(
        )]
        [string]
        [Alias("Version")]
        $Policy_Version,

        # Exact policy name
        [Parameter(
            Mandatory
        )]
        [string]
        [Alias("PolicyName")]
        $Policy_Name,

        # excel path
        [Parameter(
            Mandatory
        )]
        [string]
        [Alias('Excel', 'Path')]
        $excel_path
    )

    # Get policy details to compare with Excel file
    # Use specific version or by default latest version
    switch ($PSBoundParameters.Keys) {
        'Policy_Version' {
            $obj_policy = Get-SepCloudPolicyDetails -Policy_Name $Policy_Name -Policy_Version $Policy_Version
        }
        Default {}
    }

    if ($null -eq $PSBoundParameters['Policy_Version']) {
        $obj_policy = Get-SepCloudPolicyDetails -Policy_Name $Policy_Name
    }

    # Import excel report as a structured object with
    $obj_policy_excel = Get-ExcelAllowListObject -Path $excel_path

    # Initialize structured obj that will be later converted
    # to HTTP JSON Body with "add" and "remove" hive
    $obj_body = [UpdateAllowlist]::new()

    ###########################
    # Comparison starts here #
    ###########################

    # "Applications" tab
    # Parsing excel object first
    $policy_sha2 = $obj_policy.features.configuration.applications.processfile
    $excel_sha2 = $obj_policy_excel.Applications.processfile
    # Parsing first excel object
    foreach ($line in $excel_sha2) {
        # if sha2 appears in both lists
        if ($policy_sha2.sha2.contains($line.sha2)) {
            # No changes needed
            continue
        } else {
            # if sha2 only in excel list, set the sha to the "add" hive
            $obj_body.add.AddProcessFile(
                $line.sha2,
                $line.name
            )
        }
    }
    # Parsing then policy object
    foreach ($line in $policy_sha2) {
        # if sha2 appears only in policy (so not in Excel)
        if (-not $excel_sha2.sha2.contains($line.sha2)) {
            # set the sha to the "remove" hive
            $obj_body.remove.AddProcessFile(
                $line.sha2,
                $line.name
            )
        }
    }

    # "Files" tab
    # Parsing excel object first
    $policy_files = $obj_policy.features.configuration.windows.files
    $excel_files = $obj_policy_excel.windows.files
    foreach ($line in $excel_files) {
        # If file appears in both lists
        if ($policy_files.path.contains($line.Path)) {
            # No changes needed
            continue
        } else {
            # if file only in excel list, set the file to the "add" hive
            $obj_body.add.AddWindowsFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $line.features
            )
        }
    }
    # Parsing then policy object
    foreach ($line in $policy_files) {
        # if file appears only in policy (so not in Excel)
        if (-not $excel_files.path.contains($line.path)) {
            # set the file to the "remove" hive
            $obj_body.remove.AddWindowsFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $line.features
            )
        }
    }

    # "Directories" tab
    # Parsing excel object first
    $policy_directories = $obj_policy.features.configuration.windows.directories
    $excel_directories = $obj_policy_excel.windows.directories
    foreach ($line in $excel_directories) {
        # If directory appears in both lists
        if ($policy_directories.directory.contains($line.directory)) {
            # No changes needed
            continue
        } else {
            # if directory only in excel list, set the directory to the "add" hive
            $obj_body.add.AddWindowsDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $line.features
            )
        }
    }
    # parsing then policy object
    foreach ($line in $policy_directories) {
        # if directory appears only in policy (so not in Excel)
        if (-not $excel_directories.directory.contains($line.directory)) {
            # set the directory to the "remove" hive
            $obj_body.remove.AddWindowsDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $line.features
            )
        }
    }

    # "Certificates" tab
    # Parsing excel object first
    # TODO confirm this is the right way to compare certificates
    $policy_certs = $obj_policy.features.configuration.certificates
    $excel_certs = $obj_policy_excel.certificates
    foreach ($line in $excel_certs) {
        # If certs appears in both lists
        if ($policy_certs.signature_fingerprint.value.contains($line.signature_fingerprint.value)) {
            # No changes needed
            continue
        } else {
            # if cert only in excel list, set the cert to the "add" hive
            $obj_body.add.AddCertificates(
                $line.signature_issuer,
                $line.signature_company_name,
                $line.signature_fingerprint.algorithm,
                $line.signature_fingerprint.value
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_certs) {
        # if cert appears only in policy (so not in Excel)
        if (-not $excel_certs.signature_fingerprint.value.contains($line.signature_fingerprint.value)) {
            # set the cert to the "remove" hive
            $obj_body.remove.AddCertificates(
                $line.signature_issuer,
                $line.signature_company_name,
                $line.signature_fingerprint.algorithm,
                $line.signature_fingerprint.value
            )
        }
    }

    # "Webdomains" tab
    # Parsing excel object first
    $policy_webdomains = $obj_policy.features.configuration.webdomains
    $excel_webdomains = $obj_policy_excel.webdomains
    foreach ($line in $excel_webdomains) {
        # If webdomain appears in both lists
        if ($policy_webdomains.domain.contains($line.domain)) {
            # No changes needed
            continue
        } else {
            # if webdomain only in excel list, set the webdomain to the "add" hive
            $obj_body.add.AddWebDomains(
                $line.domain
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_webdomains) {
        # if webdomain appears only in policy (so not in Excel)
        if (-not $excel_webdomains.domain.contains($line.domain)) {
            # set the webdomain to the "remove" hive
            $obj_body.remove.AddWebDomains(
                $line.domain
            )
        }
    }

    # "Ips_hosts" tab
    # Parsing excel object first
    $policy_ips_hosts = $obj_policy.features.configuration.ips_hosts
    $excel_ips_hosts = $obj_policy_excel.ips_hosts
    foreach ($line in $excel_ips_hosts) {
        # If Ips_hosts appears in both lists
        if ($policy_ips_hosts.ip.contains($line.ip)) {
            # No changes needed
            continue
        } else {
            # if Ips_hosts only in excel list, set the Ips_hosts to the "add" hive
            $obj_body.add.AddIpsHostsIpv4Address(
                $line.ip
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_ips_hosts) {
        # if Ips_hosts appears only in policy (so not in Excel)
        if (-not $excel_ips_hosts.ip.contains($line.ip)) {
            # set the Ips_hosts to the "remove" hive
            $obj_body.remove.AddIpsHostsIpv4Address(
                $line.ip
            )
        }
    }

    # "Ips_Hosts_subnet_v6" tab
    # Parsing excel object first
    $policy_ips_hosts_subnet_v6 = $obj_policy.features.configuration.ips_hosts.ipv6_subnet | Where-Object { $_ }
    $excel_ips_hosts_subnet_v6 = $obj_policy_excel.ips_hosts.ipv6_subnet | Where-Object { $_ }
    foreach ($line in $excel_ips_hosts_subnet_v6) {
        # if subnet appears in both lists
        if ($policy_ips_hosts_subnet_v6.contains($line)) {
            # no changes
            continue
        } else {
            # if subnet only in excel list, set the subnet to the "add" hive
            $obj_body.add.AddIpsHostsIpv6Subnet(
                $line
            )
        }
        # }
    }

    # parsing then policy object
    foreach ($line in $policy_ips_hosts_subnet_v6) {
        # if subnet appears only in policy (so not in Excel)
        if (-not $excel_ips_hosts_subnet_v6.contains($line)) {
            # set the subnet to the "remove" hive
            $obj_body.remove.AddIpsHostsIpv6Subnet(
                $line
            )
        }
    }

    # "ip ranges" tab
    # Parsing excel object first
    $policy_ip_range = $obj_policy.features.configuration.ips_hosts.ip_range | Where-Object { $_ }
    $excel_ip_range = $obj_policy_excel.ips_hosts.ip_range | Where-Object { $_ }
    foreach ($line in $excel_ip_range) {
        # If ip_start appears in both lists
        if ($policy_ip_range.ip_start.contains($line.ip_start)) {
            # find the index of the ip_start in the policy list
            $policy_index = $policy_ip_range.ip_start.IndexOf($line.ip_start)
            # use index to find the corresponding ip_end
            $policy_ip_end = $policy_ip_range.ip_end[$policy_index]
            # if policy_ip_end is the same as in excel list, no changes needed
            if ($policy_ip_end -eq $line.ip_end) {
                continue
            } else {
                # if policy_ip_end is different, remove the ip_start & ip_end from policy and ...
                $obj_body.remove.AddIpsRange(
                    $policy_ip_range.ip_start[$policy_index],
                    $policy_ip_range.ip_end[$policy_index]
                )
                # ... set the ip range from excel to the "add" hive
                $obj_body.add.AddIpsRange(
                    $line.ip_start,
                    $line.ip_end
                )
            }
        }
        # if ip_start appears only in excel list
        else {
            # set the ip range to the "add" hive
            $obj_body.add.AddIpsRange(
                $line.ip_start,
                $line.ip_end
            )
        }
    }

    # then parsing policy object
    foreach ($line in $policy_ip_range) {
        # if ip_start appears only in policy (so not in Excel)
        if (-not $excel_ip_range.ip_start.contains($line.ip_start)) {
            # set the ip range to the "remove" hive
            $obj_body.remove.AddIpsRange(
                $line.ip_start,
                $line.ip_end
            )
        }
    }

    # "Ips_Hosts_subnet_v4" tab
    # Parsing excel object first
    $policy_ips_hosts_subnet_v4 = $obj_policy.features.configuration.ips_hosts.ipv4_subnet | Where-Object { $_ }
    $excel_ips_hosts_subnet_v4 = $obj_policy_excel.ips_hosts.ipv4_subnet | Where-Object { $_ }
    foreach ($line in $excel_ips_hosts_subnet_v4) {
        # If ip appears in both lists
        if ($policy_ips_hosts_subnet_v4.ip.contains($line.ip)) {
            # find the index of the ip in the policy list
            $policy_index = $policy_ips_hosts_subnet_v4.ip.IndexOf($line.ip)
            # use index to find the corresponding mask
            $policy_mask = $policy_ips_hosts_subnet_v4.mask[$policy_index]
            # if policy_mask is the same as in excel list, no changes needed
            if ($policy_mask -eq $line.mask) {
                continue
            } else {
                # if policy_mask is different, remove the ip and mask from policy and ...
                $obj_body.remove.AddIpsHostsIpv4Subnet(
                    $policy_ips_hosts_subnet_v4.ip[$policy_index],
                    $policy_ips_hosts_subnet_v4.mask[$policy_index]
                )
                # ... set the ip from excel to the "add" hive
                $obj_body.add.AddIpsHostsIpv4Subnet(
                    $line.ip,
                    $line.mask
                )
            }
        }
    }

    # then parsing policy object
    foreach ($line in $policy_ips_hosts_subnet_v4) {
        # if ip appears only in policy (so not in Excel)
        if (-not $excel_ips_hosts_subnet_v4.ip.contains($line.ip)) {
            # set the ip to the "remove" hive
            $obj_body.remove.AddIpsHostsIpv4Subnet(
                $line.ip,
                $line.mask
            )
        }
    }

    # "Extensions" tab
    # Parsing excel object first
    $policy_extensions = $obj_policy.features.configuration.extensions
    $excel_extensions = $obj_policy_excel.extensions
    $extensions_list_to_add = @()
    foreach ($line in $excel_extensions.names) {
        # If extension appears in both lists
        if ($policy_extensions.names.contains($line)) {
            # No changes needed
            continue
        } else {
            # if extension only in excel list, set the extension to the "add" hive
            # Adding it to $extensions_list_to_add
            $extensions_list_to_add += $line
        }
    }
    # If extensions to add not empty
    if ($null -ne $extensions_list_to_add) {
        [PSCustomObject]$ext = @{
            Names     = $extensions_list_to_add
            scheduled = $true
            features  = 'AUTO_PROTECT'
        }
        $obj_body.add.AddExtensions(
            $ext
        )
    }

    # Parsing then policy object
    $extensions_list_to_remove = @()
    foreach ($line in $policy_extensions.names) {
        # if extension appears only in policy (so not in Excel)
        # Adding it to the $extensions_list_to_remove
        if (-not $excel_extensions.names.contains($line)) {
            $extensions_list_to_remove += $line
        }
    }
    # If extensions to remove not empty
    if ($null -ne $extensions_list_to_remove) {
        # set the extension to the "remove" hive
        [PSCustomObject]$ext = @{
            Names     = $extensions_list_to_remove
            scheduled = $true
            features  = 'AUTO_PROTECT'
        }
        $obj_body.remove.AddExtensions(
            $ext
        )
    }

    # "Linux Files" tab
    # Parsing excel object first
    $policy_linux_files = $obj_policy.features.configuration.linux.files
    $excel_linux_files = $obj_policy_excel.linux.files
    foreach ($line in $excel_linux_files) {
        # If file appears in both lists
        if ($policy_linux_files.contains($line.Path)) {
            # No changes needed
            continue
        } else {
            # if file only in excel list, set the file to the "add" hive
            $obj_body.add.AddLinuxFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $line.features
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_linux_files) {
        # if file appears only in policy (so not in Excel)
        if (-not $excel_linux_files.path.contains($line.path)) {
            # set the file to the "remove" hive
            $obj_body.remove.AddLinuxFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $line.features
            )
        }
    }

    # "Linux Directories" tab
    # Parsing excel object first
    $policy_linux_directories = $obj_policy.features.configuration.linux.directories
    $excel_linux_directories = $obj_policy_excel.linux.directories
    foreach ($line in $excel_linux_directories) {
        # If directory appears in both lists
        if ($policy_linux_directories.contains($line.directory)) {
            # No changes needed
            continue
        } else {
            # if directory only in excel list, set the directory to the "add" hive
            $obj_body.add.AddLinuxDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $line.features
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_linux_directories) {
        # if directory appears only in policy (so not in Excel)
        if (-not $excel_linux_directories.directory.contains($line.directory)) {
            # set the directory to the "remove" hive
            $obj_body.remove.AddLinuxDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $line.features
            )
        }
    }

    # "Mac Files" tab
    # Parsing excel object first
    $policy_mac_files = $obj_policy.features.configuration.mac.files
    $excel_mac_files = $obj_policy_excel.mac.files
    foreach ($line in $excel_mac_files) {
        # If file appears in both lists
        if ($policy_mac_files.contains($line.Path)) {
            # No changes needed
            continue
        } else {
            # if file only in excel list, set the file to the "add" hive
            $obj_body.add.AddMacFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $line.features
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_mac_files) {
        # if file appears only in policy (so not in Excel)
        if (-not $excel_mac_files.path.contains($line.path)) {
            # set the file to the "remove" hive
            $obj_body.remove.AddMacFiles(
                $line.pathvariable,
                $line.path,
                $line.scheduled,
                $line.features
            )
        }
    }

    # "Mac Directories" tab
    # Parsing excel object first
    $policy_mac_directories = $obj_policy.features.configuration.mac.directories
    $excel_mac_directories = $obj_policy_excel.mac.directories
    foreach ($line in $excel_mac_directories) {
        # If directory appears in both lists
        if ($policy_mac_directories.contains($line.directory)) {
            # No changes needed
            continue
        } else {
            # if directory only in excel list, set the directory to the "add" hive
            $obj_body.add.AddMacDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $line.features
            )
        }
    }

    # Parsing then policy object
    foreach ($line in $policy_mac_directories) {
        # if directory appears only in policy (so not in Excel)
        if (-not $excel_mac_directories.directory.contains($line.directory)) {
            # set the directory to the "remove" hive
            $obj_body.remove.AddMacDirectories(
                $line.pathvariable,
                $line.directory,
                $line.recursive,
                $line.scheduled,
                $line.features
            )
        }
    }


    return $obj_body
}
#EndRegion '.\Private\Merge-SEPCloudAllowList.ps1' 573
#Region '.\Private\New-BodyString.ps1' 0
function New-BodyString($bodykeys, $parameters) {
    <#
    .SYNOPSIS
    Function to create the body payload for an API request
 
    .DESCRIPTION
    This function compares the defined body parameters within Get-SEPCloudAPIData with any parameters set within the invocation process.
    If matches are found, a properly formatted and valid body payload is created and returned.
 
    .PARAMETER bodykeys
    All of the body options available to the endpoint
 
    .PARAMETER parameters
    All of the parameter options available within the parent function
  #>


    # If sending a GET request, no body is needed
    if ($resources.Method -eq 'Get') {
        return $null
    }

    # Look at the list of parameters that were set by the invocation process
    # This is how we know which params were actually set by the call, versus defaulting to some zero, null, or false value

    # Now that custom params are added, let's inventory all invoked params

    Write-Verbose -Message 'Build the body parameters'
    $bodystring = @{ }
    # Walk through all of the available body options presented by the endpoint
    # Note: Keys are used to search in case the value changes in the future across different API versions
    foreach ($body in $bodykeys) {
        Write-Verbose "Adding $body..."

        # Array Object
        if ($resources.Body.$body.GetType().BaseType.Name -eq 'Array') {
            $bodyarray = $resources.Body.$body.Keys
            $arraystring = @{ }
            foreach ($arrayitem in $bodyarray) {
                # Walk through all of the parameters defined in the function
                # Both the parameter name and parameter alias are used to match against a body option
                # It is suggested to make the parameter name "human friendly" and set an alias corresponding to the body option name
                foreach ($param in $parameters) {
                    # If the parameter name or alias matches the body option name, build a body string

                    if ($param.Name -eq $arrayitem -or $param.Aliases -eq $arrayitem) {
                        # Switch variable types
                        if ((Get-Variable -Name $param.Name).Value.GetType().Name -eq 'SwitchParameter') {
                            $arraystring.Add($arrayitem, (Get-Variable -Name $param.Name).Value.IsPresent)
                        }
                        # All other variable types
                        elseif ($null -ne (Get-Variable -Name $param.Name).Value) {
                            $arraystring.Add($arrayitem, (Get-Variable -Name $param.Name).Value)
                        }
                    }
                }
            }
            $bodystring.Add($body, @($arraystring))
        }

        # Non-Array Object
        else {
            # Walk through all of the parameters defined in the function
            # Both the parameter name and parameter alias are used to match against a body option
            # It is suggested to make the parameter name "human friendly" and set an alias corresponding to the body option name
            foreach ($param in $parameters) {
                # If the parameter name or alias matches the body option name, build a body string
                if (($param.Name -eq $body -or $param.Aliases -eq $body)) {
                    if ((Get-Variable -Name $param.Name).Value) {
                        # Switch variable types
                        if ((Get-Variable -Name $param.Name).Value.GetType().Name -eq 'SwitchParameter') {
                            $bodystring.Add($body, (Get-Variable -Name $param.Name).Value.IsPresent)
                        }
                        # All other variable types
                        elseif ($null -ne (Get-Variable -Name $param.Name).Value -and (Get-Variable -Name $param.Name).Value.Length -gt 0) {
                            $bodystring.Add($body, (Get-Variable -Name $param.Name).Value)
                        }
                    }
                }
            }
        }
    }

    # Store the results into a JSON string
    if (0 -ne $bodystring.count) {
        # $bodystring = ConvertTo-Json -InputObject $bodystring
        Write-Verbose -Message "Body = $(ConvertTo-Json -InputObject $bodystring)"
    } else {
        Write-Verbose -Message 'No body for this request'
    }
    return $bodystring
}
#EndRegion '.\Private\New-BodyString.ps1' 92
#Region '.\Private\New-QueryString.ps1' 0
function New-QueryString($query, $uri) {
    <#
    .SYNOPSIS
    Adds query parameters to a URI
 
    .DESCRIPTION
    This function compares the defined query parameters within SEPCloudAPIData with any parameters set within the invocation process.
    If matches are found, a properly formatted and valid query string is created and appended to a returned URI
 
    .PARAMETER query
    An array of query values that are added based on which $objects have been passed by the user
 
    .PARAMETER uri
    The entire URI without any query values added
 
    #>


    # TODO: It seems like there's a more elegant way to do this logic, but this code is stable and functional.
    foreach ($_ in $query) {
        # The query begins with a "?" character, which is appended to the $uri after determining that at least one $params was collected
        if ($_ -eq $query[0]) {
            $uri += '?' + $_
        }
        # Subsequent queries are separated by a "&" character
        else {
            $uri += '&' + $_
        }
    }
    return $uri
}
#EndRegion '.\Private\New-QueryString.ps1' 31
#Region '.\Private\New-URIQuery.ps1' 0
function New-URIQuery($queryKeys, $parameters, $uri) {

    <#
    .SYNOPSIS
        Builds a URI with query parameters for an uri
    .DESCRIPTION
        Builds a URI with query parameters for an uri.
        This function takes a list of keys and values, and constructs an URI with the query parameters.
    .PARAMETER queryKeys
        The query keys as defined in Get-SEPCloudAPIData
    .PARAMETER parameters
        The set of parameters passed as query values
    .PARAMETER uri
        The base URI to build from
 
    #>




    # Construct the uri
    $builder = New-Object System.UriBuilder($uri)
    $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)

    Write-Verbose -Message "Build the query parameters for $(if ($queryKeys){$queryKeys -join ','}else{'<null>'})"
    # Walk through all of the available query options
    foreach ($queryKey in $queryKeys) {
        # Walk through all of the parameters defined in the function
        # Both the parameter name and parameter alias are used to match against a query option
        # This will allow for easier readability of the code
        foreach ($param in $parameters) {
            # If the parameter name matches the query option name, build a query string
            if ($param.ContainsKey($queryKey)) {
                if ($null -ne $param.Values) {
                    $query.Add($queryKey, $param[$queryKey])
                }
            }
        }
    }

    $builder.Query = $query.ToString()
    $uri = $builder.Uri.AbsoluteUri
    Write-Verbose -Message "URI = $uri"
    return $uri
}
#EndRegion '.\Private\New-URIQuery.ps1' 45
#Region '.\Private\New-URIString.ps1' 0
function New-URIString {

    <#
    .SYNOPSIS
        Builds a valid URI
    .DESCRIPTION
        Builds a valid URI based off of the constructs defined in the Get-SEPCLoudAPIData resources for the cmdlet.
        Inserts any object IDs into the URI if {id} is specified within the constructs.
    .PARAMETER baseURL
        The base URL for the API
    .PARAMETER id
        The ID of an object to be inserted into the URI
        Accepts an array of IDs from 0 to 2 strings
    .PARAMETER endpoint
        The endpoint to be inserted into the URI
        Optionally at the end of the base URL if no {id} is specified
    .EXAMPLE
        New-URIString -baseURL "192.168.3.11" -id 56789 -endpoint "/v1/device-groups"
        Returns "https://192.168.3.11/v1/device-groups/56789"
    .EXAMPLE
        New-URIString -baseURL "192.168.3.11" -id 56789 -endpoint "/v1/device-groups/{id}/devices"
        Returns "https://192.168.3.11/v1/device-groups/56789/devices"
    #>




    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $baseURL = $script:SEPCloudConnection.BaseURL,

        [Parameter()]
        [ValidateCount(0, 2)]
        [array]
        $id,

        [Parameter(
            Mandatory = $true
        )]
        [string]
        $endpoint
    )

    Write-Verbose -Message 'Build the URI'
    $uri = ('https://' + $baseUrl + $endpoint)

    # If we find {id} in the path, replace it with the $id value
    if ($endpoint -match '{id}') {
        # regex to replace the {id} with the next value in the array
        $idx = @(0)
        $uri = [regex]::Replace($uri, '{id}|$', {
                # if there is a "next value" in the list
                if ($next = $id[$idx[0]++]) {
                    # if matching EOL
                    if (-not $args[0].Value) {
                        return '/' + $next
                    } else { $next }
                }
            })
    }

    # Otherwise, only add the $id value at the end if it exists (for single object retrieval)
    else {
        # If $id has 2 elements can't append both ids to URI
        if ($id.Count -gt 1) {
            $message = "2 ids provided : '$id'"
            $message += "endpoint $endpoint allows only one id :"
            Write-Error -Message $message -ErrorAction Stop
        }
        if ($id.Count -eq 1) {
            $uri += "/$id"
        }
    }

    Write-Verbose -Message "URI = $uri"
    return $uri
}

# live test
# $BaseURL = "api.my.test.com"
# $endpoint = "/v1/{id}/device-groups"
# $id = @("123456", "789012")

# New-URIString -endpoint $endpoint -id $id -baseURL $BaseURL
#EndRegion '.\Private\New-URIString.ps1' 86
#Region '.\Private\New-UserAgentString.ps1' 0
function New-UserAgentString {
    <#
        .SYNOPSIS
        Helper function, creates a user agent string
 
        .DESCRIPTION
        Function that generates a user agent string containing the module name, version and OS / platform information
 
        .NOTES
        Written by Jaap Brasser for community usage
        Twitter: @jaap_brasser
        GitHub: jaapbrasser
 
        .EXAMPLE
        New-UserAgentString
        PSSymantecCloud-0.0--7.4.2--platform--Win32NT--platform_version--Microsoft.Windows.10.0.22631
 
        Will generate a new user agent string containing the module name, version and OS / platform information
 
 
        New-UserAgentString -UserAgentHash @{platform_integration='Poshbot.Rubrik'}
 
        Will generate a new user agent string containing the module name, version and OS / platform information with the additional information specified in UserAgentHash
    #>

    param(
        [hashtable] $UserAgentHash
    )

    process {
        $OS, $OSVersion = if ($psversiontable.PSVersion.Major -lt 6) {
            'Win32NT'
            try {
                Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop | ForEach-Object {
                    ($_.Name -Split '\|')[0], $_.BuildNumber -join ''
                }
            } catch {}
        } else {
            $psversiontable.platform
            if ($psversiontable.os.Length -gt 64) {
                $psversiontable.os.Substring(0, 64) -replace ':', '.'
                $psversiontable.os.Substring(0, 64) -replace ' ', '.'
            } else {
                $psversiontable.os.Trim() -replace ' ', '.'
            }
        }

        $PlatformDetails = "platform--$OS--platform_version--$OSVersion"

        $ModuleVersion = try {
            if (-not [string]::IsNullOrWhiteSpace($MyInvocation.MyCommand.ScriptBlock.Module.PrivateData.PSData.Prerelease)) {
                $MyInvocation.MyCommand.ScriptBlock.Module.Version.ToString(),
                $MyInvocation.MyCommand.ScriptBlock.Module.PrivateData.PSData.Prerelease.ToString() -join '.'

            } else {
                $MyInvocation.MyCommand.ScriptBlock.Module.Version.ToString()
            }
        } catch {

        }

        $UserAgent = "$script:ModuleName-{0}--{1}--{2}" -f
        $ModuleVersion,
        $psversiontable.psversion.tostring(),
        $PlatformDetails

        if ($UserAgentHash) {
            $UserAgentHash.keys | ForEach-Object -Begin {
                [string]$StringBuilder = ''
            } -Process {
                $StringBuilder += "--$_--$($UserAgentHash[$_])"
            } -End {
                $UserAgent += $StringBuilder
            }
        }

        return $UserAgent
    }
}
#EndRegion '.\Private\New-UserAgentString.ps1' 79
#Region '.\Private\Optimize-SEPCloudAllowListPolicyObject.ps1' 0
function Optimize-SepCloudAllowListPolicyObject {
    <#
    .SYNOPSIS
        Removes empty properties from the SEP Cloud Allow List Policy Object
    .DESCRIPTION
        Removes empty properties from the SEP Cloud Allow List Policy Object. This is required to avoid errors when creating a new policy.
    .EXAMPLE
        $AllowListPolicyOptimized = $AllowListPolicy | Optimize-SepCloudAllowListPolicyObject
    #>


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

    process {
        # Listing all properties of the object
        $AllProperties = $obj | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
        foreach ($property in $AllProperties) {
            switch ($property) {
                "add" {
                    # recursively call the function to dig deeper
                    $obj.add = Optimize-SepCloudAllowListPolicyObject $obj.$property
                    # Verify if the add object has no properties
                    if (($obj.add | Get-Member -MemberType NoteProperty).count -eq 0) {
                        # Remove the "add" property from the object
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "remove" {
                    # recursively call the function to dig deeper
                    $obj.remove = Optimize-SepCloudAllowListPolicyObject $obj.$property
                    # Verify if the remove object has no properties
                    if (($obj.remove | Get-Member -MemberType NoteProperty).count -eq 0) {
                        # Remove the "remove" property from the object
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "applications" {
                    if ($obj.$property.processfile.count -eq 0) {
                        # Remove Applications property
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "webdomains" {
                    if ($obj.$property.count -eq 0) {
                        # Remove webdomains property
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "certificates" {
                    if ($obj.$property.count -eq 0) {
                        # Remove certificates property
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "ips_hosts" {
                    if ($obj.$property.count -eq 0) {
                        # Remove ips_hosts property
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "extensions" {
                    if ($obj.$property.names.count -eq 0) {
                        # Remove extensions property
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "linux" {
                    # Checking linux files
                    if ($obj.$property.files.count -eq 0) {
                        # Remove linux files property only
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "files"
                    }
                    # Checking linux folders
                    if ($obj.$property.directories.count -eq 0) {
                        # Remove linux folders property only
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "directories"
                    }
                    # If both files and folders are empty, remove the property
                    if ($obj.$property.files.count -eq 0 -and $obj.$property.directories.count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "windows" {
                    # Checking windows files
                    if ($obj.$property.files.count -eq 0) {
                        # Remove windows files property only
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "files"
                    }
                    # Checking windows folders
                    if ($obj.$property.directories.count -eq 0) {
                        # Remove windows folders property only
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "directories"
                    }
                    # If both files and folders are empty, remove the property
                    if ($obj.$property.files.count -eq 0 -and $obj.$property.directories.count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                "mac" {
                    # Checking mac files
                    if ($obj.$property.files.count -eq 0) {
                        # Remove mac files property only
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "files"
                    }
                    # Checking mac folders
                    if ($obj.$property.directories.count -eq 0) {
                        # Remove mac folders property only
                        $obj.$property = $obj.$property | Select-Object -ExcludeProperty "directories"
                    }
                    # If both files and folders are empty, remove the property
                    if ($obj.$property.files.count -eq 0 -and $obj.$property.directories.count -eq 0) {
                        $obj = $obj | Select-Object -ExcludeProperty $property
                    }
                }
                Default {}
            }
        }
        return $obj
    }
}
#EndRegion '.\Private\Optimize-SEPCloudAllowListPolicyObject.ps1' 125
#Region '.\Private\Remove-SEPCloudToken.ps1' 0
function Remove-SEPCloudToken {

    $script:SEPCloudConnection | Add-Member -MemberType NoteProperty -Name AccessToken -Value $null -Force -ErrorAction SilentlyContinue
    $script:configuration | Add-Member -MemberType NoteProperty -Name AccessToken  -Value $null -Force -ErrorAction SilentlyContinue
    if ($script:configuration.CachedTokenPath) {
        try { Remove-Item $script:configuration.CachedTokenPath -Force } catch {}
    }
}
#EndRegion '.\Private\Remove-SEPCloudToken.ps1' 9
#Region '.\Private\Set-ObjectTypeName.ps1' 0
function Set-ObjectTypeName($typename, $result) {
    <#
        .SYNOPSIS
        Assigns an Object TypeName to cmdlet results
 
        .DESCRIPTION
        In order to better display results for cmdlets returning many objects with many properties TypeName formats may be used.
        This function will assign a TypeName if it exists to a set of returned objects.
 
        .PARAMETER typename
        The name of the TypeName to insert
 
        .PARAMETER result
        The response content which recieves the typename.
    #>


    if ($null -ne $result -and $null -ne $typename) {
        # Using ForEach-Object instead of .ForEach as .ForEach doesn't support single results.
        Write-Verbose -Message "Applying $typename TypeName to results"
        $result | ForEach-Object {
            $_.PSObject.TypeNames.Insert(0, $typename)
        }

    }

    return $result
}
#EndRegion '.\Private\Set-ObjectTypeName.ps1' 28
#Region '.\Private\Submit-Request.ps1' 0

function Submit-Request {
    [cmdletbinding()]
    param(
        # The endpoint's URI
        $uri,
        # The header containing authentication details
        $header = $script:SEPCloudConnection.header,
        # The action (method) to perform on the endpoint
        $method = $($resources.Method),
        # Any optional body data being submitted to the endpoint
        $body
    )


    Write-Verbose -Message 'Submitting the request'
    Write-Verbose -Message "method : $method"

    $WebResult = Invoke-SEPCloudWebRequest -Uri $uri -Headers $header -Method $method -Body $body

    return $WebResult

}
#EndRegion '.\Private\Submit-Request.ps1' 24
#Region '.\Private\Test-QueryObject.ps1' 0
function Test-QueryObject($object, $location, $query) {
    <#
    .SYNOPSIS
    Builds a query string for an endpoint
 
    .DESCRIPTION
    The Test-QueryObject function is used to build a custom query string for supported endpoints
 
    .PARAMETER object
    The parent function's variable holding the user generated query data
 
    .PARAMETER location
    The key/value pair that contains the correct query name value
 
    .PARAMETER params
    An array of query values that are added based on which $objects have been passed by the user
  #>


    if ((-not [string]::IsNullOrWhiteSpace($object)) -and ($location)) {
        # This builds the individual query item for the endpoint
        # Example: /vmware/vm?search_value=SE-CWAHL-WIN&limit=9999 contains 2 queries - search_value and limit
        return "$location=$object"
    }
}
#EndRegion '.\Private\Test-QueryObject.ps1' 25
#Region '.\Private\Test-QueryParam.ps1' 0
function Test-QueryParam($querykeys, $parameters, $uri) {
    <#
    .SYNOPSIS
    Builds a URI with query parameters for an endpoint
 
    .DESCRIPTION
    The Test-QueryParam function is used to build and test a custom query string for supported endpoints.
 
    .PARAMETER querykeys
    The endpoints query keys as defined in Get-SEPCloudAPIData
 
    .PARAMETER parameters
    The set of parameters passed within the cmdlets invocation
 
    .PARAMETER uri
    The endpoints URI
    #>


    Write-Verbose -Message "Build the query parameters for $(if ($querykeys){$querykeys -join ','}else{'<null>'})"
    $querystring = @()
    # Walk through all of the available query options presented by the endpoint
    # Note: Keys are used to search in case the value changes in the future across different API versions
    foreach ($query in $querykeys) {
        # Walk through all of the parameters defined in the function
        # Both the parameter name and parameter alias are used to match against a query option
        # It is suggested to make the parameter name "human friendly" and set an alias corresponding to the query option name
        foreach ($param in $parameters) {
            # If the parameter name matches the query option name, build a query string
            if (($param.Name -eq $query.Keys) -or ($param.Name -eq $query)) {
                if ((Get-Variable -Name $param.Name).Value) {
                    Write-Verbose ('Building Query with "{0}: {1}"' -f $resources.Query[$param.Name], (Get-Variable -Name $param.Name).Value)
                }
                $querystring += Test-QueryObject -object (Get-Variable -Name $param.Name).Value -location $resources.Query[$param.Name] -params $querystring
            }
            # If the parameter alias matches the query option name, build a query string
            elseif (($param.Aliases -eq $query.Keys) -or ($param.Aliases -eq $query)) {
                if ((Get-Variable -Name $param.Name).Value) {
                    Write-Verbose ('Building Query with "{0}: {1}"' -f (-join $resources.Query[$param.Aliases]), (Get-Variable -Name $param.Name).Value)
                }
                $querystring += Test-QueryObject -object (Get-Variable -Name $param.Name).Value -location $resources.Query[$param.Aliases] -params $querystring
            }
        }
    }

    # After all query options are exhausted, build a new URI with all defined query options
    $uri = New-QueryString -query $querystring -uri $uri
    Write-Verbose -Message "URI = $uri"

    return $uri
}
#EndRegion '.\Private\Test-QueryParam.ps1' 51
#Region '.\Private\Test-ReturnFormat.ps1' 0
function Test-ReturnFormat($result, $location) {
    <#
    .SYNOPSIS
    Removes parent encapsulation from returned responses
 
    .DESCRIPTION
    The Test-ReturnFormat function is used to remove any parent variables surrounding return data, such as encapsulating results in a "data" key
 
    .PARAMETER result
    The unformatted API response content
 
    .PARAMETER location
    The key/value pair that contains the name of the key holding the response content's data
    #>


    Write-Verbose -Message 'Formatting return value'
    if ($location -and ($null -ne ($result).$location)) {
        # The $location check assumes that not all endpoints will require findng (and removing) a parent key
        # If one does exist, this extracts the value so that the $result data is consistent across API versions
        return ($result).$location
    } else {
        # When no $location is found, return the original $result
        return $result
    }
}
#EndRegion '.\Private\Test-ReturnFormat.ps1' 26
#Region '.\Private\Test-SEPCloudConnection.ps1' 0
function Test-SEPCloudConnection {

    Write-Verbose -Message "Test-SEPCloudConnection: $script:SEPCloudConnection.AccessToken.Token"

    Write-Verbose -Message "Validate the SEP Cloud token"
    if (Test-SEPCloudToken) {
        Write-Verbose -Message "token valid - returning"
        return $True
    } else {
        Write-Verbose -Message "token expired or invalid - requesting a new one"
        Get-SEPCloudToken
        Write-Verbose -Message ("New token will expire at" + $((Get-Date) -lt $script:SEPCloudConnection.AccessToken.Expiration))
    }
}
#EndRegion '.\Private\Test-SEPCloudConnection.ps1' 15
#Region '.\Private\Test-SEPCloudToken.ps1' 0
function Test-SEPCloudToken {
    [CmdletBinding()]
    param (
        [Parameter()]
        [PSCustomObject]
        $token
    )

    # Token passed as parameter
    if ($token) {
        if ((Get-Date) -lt $token.Expiration) {
            return $True
        }
    }

    # In memory token
    if ($script:SEPCloudConnection.AccessToken) {
        if ((Get-Date) -lt $script:SEPCloudConnection.AccessToken.Expiration) {
            return $True
        } else {
            Remove-SEPCloudToken
            return $false
        }
    }

    return $False
}
#EndRegion '.\Private\Test-SEPCloudToken.ps1' 28
#Region '.\Public\Block-SEPCloudFile.ps1' 0
function Block-SEPCloudFile {

    <#
    .SYNOPSIS
        Quarantines one or many files on one or many SEP Cloud managed endpoint
    .DESCRIPTION
        Quarantines one or many files on one or many SEP Cloud managed endpoint
    .PARAMETER device_ids
        The ID of the SEP Cloud managed endpoint to quarantine file(s) from
    .PARAMETER hash
        hash of the file to quarantine
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .EXAMPLE
        Block-SEPCloudFile -Verbose -device_ids "dGKQS2SyQlCbPjC2VxqO0w" -hash "C4C3115E3A1AF01D6747401AA22AF90A047292B64C4EEFF4D8021CC0CB60B22D"
 
        BLocks a specific file on a specific computer by its device_id and hash
    #>


    [CmdletBinding()]
    Param(
        [Alias('deviceId')]
        [String[]]
        $device_ids,

        [Alias('sha256')]
        [String[]]
        $hash
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        # changing "Host" header specifically for this query, otherwise 500
        $script:SEPCloudConnection.header += @{ 'Host' = $script:SEPCloudConnection.BaseURL }

        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        # removing the "Host" header specifically for this query, otherwise 500
        $script:SEPCloudConnection.header.remove('Host')

        return $result
    }
}
#EndRegion '.\Public\Block-SEPCloudFile.ps1' 63
#Region '.\Public\Clear-SepCloudAuthentication.ps1' 0
function Clear-SEPCloudAuthentication {
    <#
    .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-SepCloudAuthentication
 
        Clears out any API token from memory, as well as from local file storage.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param ()

    # Remove the cached authentication data from memory
    # Using if statements to avoid errors when the variable does not exist or not initialized
    if ($script:configuration.CachedToken) {
        $script:configuration.CachedToken = $null
    }
    if ($script:Credential) {
        $script:Credential = $null
    }
    if ($script:SEPCloudConnection.AccessToken) {
        $script:SEPCloudConnection.AccessToken = $null
    }
    if ($script:SEPCloudConnection.Credential) {
        $script:SEPCloudConnection.Credential = $null
    }

    # remove the cached authentication data from disk
    Remove-Item -Path $($script:configuration.CachedTokenPath) -Force -ErrorAction SilentlyContinue -ErrorVariable ev
    Remove-Item -Path $($script:configuration.SEPCloudCredsPath) -Force -ErrorAction SilentlyContinue -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:configuration.SEPCloudCredsPath)
        $message += "Experienced a problem trying to remove the file that persists the Access Credentials " + $($script:configuration.CachedTokenPath)
        Write-Warning -Message $message
    }
}
#EndRegion '.\Public\Clear-SepCloudAuthentication.ps1' 43
#Region '.\Public\Connect-SEPCloud.ps1' 0
function Connect-SEPCloud {
    [CmdletBinding()]
    param (
        # Additional information to be added, takes hashtable as input
        [hashtable] $UserAgent,
        [switch] $cacheOnly,
        $clientId,
        $secret
    )

    process {
        # Create User Agent string
        $UserAgentString = New-UserAgentString -UserAgentHash $UserAgent
        $PSBoundParameters.Remove($UserAgent) | Out-Null
        Remove-Variable -Force -Name UserAgent -ErrorAction SilentlyContinue

        Write-Verbose -Message "Using User Agent $($UserAgentString)"

        # If called from Initialize-SEPCloudConfiguration
        # get token from cache only to avoid prompting for creds while loading the module
        if ($cacheOnly) {
            Write-Verbose -Message "Token request using cachedOnly"
            $token = Get-SEPCloudToken -cacheOnly
        } elseif ($clientId -and $secret) {
            Write-Verbose -Message "Token request using client and secret"
            $token = Get-SEPCloudToken -client $clientId -secret $secret
        } else {
            $token = Get-SEPCloudToken
        }

        # if we have a token, add it to the header
        if ($null -ne $token) {
            $head = @{
                'Authorization' = "$($Token.Token_Bearer)";
                'User-Agent'    = $UserAgentString#;
                # 'Host' = $($script:SEPCloudConnection.BaseURL)
            }
            $script:SEPCloudConnection | Add-Member -Type NoteProperty -Name 'header' -Value $head -Force
        } else {
            # If no token, just add User-Agent
            $head = @{
                'User-Agent' = $UserAgentString#;
                # 'Host' = $($script:SEPCloudConnection.BaseURL)
            }
            $script:SEPCloudConnection | Add-Member -Type NoteProperty -Name 'header' -Value $head -Force
        }
    }
}
#EndRegion '.\Public\Connect-SEPCloud.ps1' 49
#Region '.\Public\Get-SEPCloudComponentType.ps1' 0
function Get-SEPCloudComponentType {

    <#
    .SYNOPSIS
        This API lets you retrieve policy component host-groups, network-adapters(adapter), network-services(Connection), network IPS details
    .DESCRIPTION
        This API lets you retrieve policy component host-groups, network-adapters(adapter), network-services(Connection), network IPS details
    .NOTES
        Information or caveats about the function e.g. 'This function is not supported in Linux'
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .EXAMPLE
        Get-SEPCloudComponentType -componentType 'network-adapters'
 
        Provides the full list of network adapters available on the cloud.
    #>


    [CmdletBinding()]
    Param(
        # Component Type is one of the list
        [Parameter(
            Mandatory = $true
        )]
        [ValidateSet(
            'network-ips',
            'host-groups',
            'network-adapters',
            'network-services'
        )]
        [string]
        $ComponentType,
        $offset,
        $limit# = 1000
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        # changing "Content-Type" header specifically for this query, otherwise 415 : unsupported media type
        $script:SEPCloudConnection.header += @{ 'Content-Type' = 'application/json' }

        $uri = New-URIString -endpoint ($resources.URI) -id $ComponentType
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if (($result.total -gt $result.data.count) -or ($result.total_count -gt $result.data.count)) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset query param for pagination
                $offset = $result.data.count
                $uri = New-URIString -endpoint ($resources.URI) -id $id
                $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.data += $nextResult.data
            } until (($result.data.count -ge $result.total) -or ($result.data.count -ge $result.total_count))
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result

        # apply correct PSType based on the 4 possible results options
        if ($null -ne $result.identification) {
            $resources.ObjectTName = "SEPCloud.adapter"
        }
        if ($null -ne $result.classifications) {
            $resources.ObjectTName = "SEPCloud.ips_metadata"
        }
        if ($null -ne $result.services) {
            $resources.ObjectTName = "SEPCloud.network-services"
        }
        if ($null -ne $result.hosts) {
            $resources.ObjectTName = "SEPCloud.host-group"
        }

        # Setting PSType to the correct type
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        # Removing "Content-Type: application/json" header
        $script:SEPCloudConnection.header.remove('Content-Type')

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudComponentType.ps1' 100
#Region '.\Public\Get-SEPCloudDevice.ps1' 0
function Get-SEPCloudDevice {
    <#
    .SYNOPSIS
    Gathers list of devices from the SEP Cloud console
    .DESCRIPTION
    Gathers list of devices from the SEP Cloud console
    .PARAMETER client_version
    Version of agent installed on device.
    [NOTE] Provide comma seperated values in case of multiple version search.
    .PARAMETER device_group
    ID of the parent device group.
    [NOTE] Provide comma seperated values in case of multiple name search.
    .PARAMETER device_status
    Device status
    Possible values: SECURE,AT_RISK, COMPROMISED,NOT_COMPUTED
    [NOTE] Provide comma seperated values in case of multiple status search.
    .PARAMETER device_type
    os type of the device
    [NOTE] Provide comma seperated values in case of multiple os type search.
    Possible values: WORKSTATION, SERVER, MOBILE
    .PARAMETER name
    name of the device.
    [NOTE] Provide comma seperated values in case of multiple name search.
    .PARAMETER ipv4_address
    ipv4 address of a device.
 
 
    .EXAMPLE
    Get-SEPCloudDevice
    Get all devices (very slow)
    .EXAMPLE
    Get-SEPCloudDevice -Computername MyComputer
    Get detailed information about a computer
    .EXAMPLE
    Get-SEPCloudDevice -client_version "14.2.1031.0100,14.2.770.0000"
    Get all devices with client version 14.2.1031.0100 and 14.2.770.0000
    .EXAMPLE
    Get-SEPCloudDevice -device_group "Fmp5838YRsyElHM27PdZww,123456789"
    Get all devices from the 2 groups with the group IDs "Fmp5838YRsyElHM27PdZww" and "123456789
    .EXAMPLE
    Get-SEPCloudDevice -device_status AT_RISK
    Get all online devices with AT_RISK status
    .EXAMPLE
    Get-SEPCloudDevice -Client_version "14.3.9681.7000" -device_type WORKSTATION
    Get all workstations with client version 14.3.9681.7000
    .EXAMPLE
    Get-SEPCloudDevice -IPv4 "192.168.1.1"
    Get all devices with IPv4 address
    #>


    [CmdletBinding()]
    param (
        [Alias("ClientVersion")]
        $client_version,

        [Alias("Group")]
        $device_group,

        [Alias("DeviceStatus")]
        [ValidateSet("SECURE", "AT_RISK", "COMPROMISED", "NOT_COMPUTED")]
        $device_status,

        [Alias("DeviceType")]
        [ValidateSet("WORKSTATION", "SERVER", "MOBILE")]
        $device_type,

        [Alias("IPv4")]
        $ipv4_address,

        [Alias("computername")]
        $name,

        [switch]
        $is_virtual,

        $offset
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if ($result.total -gt $result.devices.count) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset query param for pagination
                $offset = $result.devices.count
                $uri = New-URIString -endpoint ($resources.URI) -id $id
                $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.devices += $nextResult.devices
            } until ($result.devices.count -ge $result.total)
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudDevice.ps1' 121
#Region '.\Public\Get-SEPCloudDeviceDetails.ps1' 0
function Get-SEPCloudDeviceDetails {

    <#
    .SYNOPSIS
        Gathers device details from the SEP Cloud console
    .DESCRIPTION
        Gathers device details from the SEP Cloud console
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER device_id
        id used to lookup a unique computer
    .OUTPUTS
        PSObject
    .EXAMPLE
        Get-SepCloudDeviceDetails -id wduiKXDDSr2CVrRaqrFKNx
 
    #>


    [CmdletBinding()]
    Param(
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true)]
        [Alias("id")]
        [string]
        $device_id
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $device_id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result
        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudDeviceDetails.ps1' 54
#Region '.\Public\Get-SEPCloudEDRDumpsList.ps1' 0
function Get-SEPCloudEDRDumpsList {

    <#
        .SYNOPSIS
            Gets a list of the SEP Cloud Commands
        .DESCRIPTION
            Gets a list of the SEP Cloud Commands. All commands are returned by default.
        .LINK
            https://github.com/Douda/PSSymantecCloud
        .PARAMETER query
        Query to be used in the search
        Uses Lucene syntax.
        Is optional. If not used returns all commands by default
        .PARAMETER next
        The next page of results. Used for pagination
        .PARAMETER limit
        The maximum number of results returned. Used for pagination
        default is 25
        .EXAMPLE
            Get-SEPCloudCommand
 
            Gets a list of the SEP Cloud Commands
    #>


    [CmdletBinding()]
    Param(
        $next,
        $limit = 25,
        $query
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if ($result.total -gt $result.commands.count) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset/next query param for pagination
                $next = $result.next
                $uri = New-URIString -endpoint ($resources.URI) -id $id
                $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.commands += $nextResult.commands
            } until ($result.commands.count -ge $result.total)
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudEDRDumpsList.ps1' 72
#Region '.\Public\Get-SEPCloudEvents.ps1' 0
function Get-SEPCloudEvents {
    <#
    .SYNOPSIS
        Get list of SEP Cloud Events. By default it will gather data for past 30 days
    .DESCRIPTION
        Get list of SEP Cloud Events. You can use the following parameters to filter the results: FileDetection, FullScan, or a custom Lucene query
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER feature_name
        Filters events based on a product feature.
        [NOTE] ==== You can add a comma separated list of feature_name values
        (i.e. Agent Framework, Deception, Firewall) to define a unique set of events to search. ====
    .PARAMETER product
        The value is SAEP. This represents Symantec Endpoint Security events.
        [NOTE] ==== SAEP is the only available product value. ====
    .PARAMETER query
        A custom Lucene query to filter the results
        e.g. type_id:8001
    .PARAMETER start_date
        This value identifies the beginning date to filter events.
    .PARAMETER end_date
        This value identifies the ending date to filter events.
    .PARAMETER next
        represents the starting index of the record in a given set.This is used for pagination.
    .PARAMETER limit
        This value identifies batch size.This is also used for pagination.
    .EXAMPLE
        Get-SepCloudEvents
        Gather all possible events. ** very slow approach & limited to 10k events **
    .EXAMPLE
        Get-SepCloudEvents -Query "type_id:8031 OR type_id:8032 OR type_id:8033"
        Runs a custom Lucene query
    #>

    [CmdletBinding(DefaultParameterSetName = 'Query')]
    param (
        $feature_name = "ALL",
        $product = "SAEP",
        $query,
        $start_date = ((Get-Date).AddDays(-29) | Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK"), # Default is 29 days ago
        $end_date = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK"), # Default is today
        $next, # for pagination
        $limit = 1000 # Maximum number of results per page (API default = 100)
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI)
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if ($result.total -gt $result.events.count) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset query param for pagination (called next)
                $next = $next + $result.next
                $uri = New-URIString -endpoint ($resources.URI)
                $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.events += $nextResult.events
            } until ($result.events.count -ge $result.total)
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result

    }
}
#EndRegion '.\Public\Get-SEPCloudEvents.ps1' 89
#Region '.\Public\Get-SEPCloudFileHashDetails.ps1' 0
function Get-SEPCloudFileHashDetails {

    <#
        .SYNOPSIS
            Returns information whether a given file has been blocked by any Symantec technologies
        .DESCRIPTION
            Returns information whether a given file has been blocked by any Symantec technologies
        .LINK
        https://github.com/Douda/PSSymantecCloud
        .PARAMETER file_hash
        required hash to lookup
        .EXAMPLE
        Get-SEPCloudFileHashDetails -file_hash "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d"
    #>


    [CmdletBinding()]
    Param(
        [Alias('hash')]
        $file_hash
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $file_hash
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result
        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudFileHashDetails.ps1' 47
#Region '.\Public\Get-SEPCloudGroup.ps1' 0
function Get-SEPCloudGroup {

    <#
    .SYNOPSIS
        Gathers list of device groups from SEP Cloud
    .DESCRIPTION
        Gathers list of device groups from SEP Cloud. Does not contains device information
    .PARAMETER offset
        Page number to query. Defaults to 0. If pagination is required, this parameter is used to specify the page number
    .EXAMPLE
        Get-SEPCloudGroup
 
        Gets the full list of groups
    #>


    [CmdletBinding()]
    param (
        # Query
        [Alias('api_page')]
        $offset
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if ($result.total -gt $result.device_groups.count) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset query param for pagination
                $offset = $result.device_groups.count
                $uri = New-URIString -endpoint ($resources.URI) -id $id
                $uri = Test-QueryParam -querykeys $resources.query -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.device_groups += $nextResult.device_groups
            } until ($result.device_groups.count -ge $result.total)
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        # Add custom property fullPathName
        Write-Verbose -Message "Adding new property fullPathName for each group"
        $result | ForEach-Object {
            $fullPathName = Get-SEPCloudGroupFullPath -CurrentGroup $_ -AllGroups $result -Chain ""
            $_ | Add-Member -NotePropertyName "fullPathName" -NotePropertyValue $fullPathName.TrimEnd(" > ")
        }

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudGroup.ps1' 72
#Region '.\Public\Get-SEPCloudGroupPolicies.ps1' 0
function Get-SEPCloudGroupPolicies {

    <#
        .SYNOPSIS
        Gathers list of policies applied for a device group
        .DESCRIPTION
        Gathers list of policies applied for a device group
        .LINK
        https://github.com/Douda/PSSymantecCloud
        .PARAMETER group_id
        id of device group
        .EXAMPLE
        Get-SEPCloudGroupPolicies -GroupID "Fmp5838YRsyElHM27PdZxx"
        Gets the list of every policies applied to a device group
    #>


    [CmdletBinding()]
    param (
        # Group ID
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('groupID')]
        [String]
        $group_id
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $group_id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters  ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudGroupPolicies.ps1' 56
#Region '.\Public\Get-SepCloudIncidentDetails.ps1' 0
function Get-SepCloudIncidentDetails {

    <#
        .SYNOPSIS
        Gathers details about an open incident
        .DESCRIPTION
        Gathers details about an open incident
        .LINK
        https://github.com/Douda/PSSymantecCloud
        .PARAMETER incidentId
            ID of incident
        .EXAMPLE
        Get-SepCloudIncidentDetails -incident_ID "21b23af2-ea44-479c-a235-9540082da98f"
 
 
    #>


    [CmdletBinding()]
    Param(
        # Query
        [Alias('incident_id')]
        [String]$incidentId
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $incidentId
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SepCloudIncidentDetails.ps1' 53
#Region '.\Public\Get-SEPCloudPolicesSummary.ps1' 0
function Get-SEPCloudPolicesSummary {

    <#
    .SYNOPSIS
        Provides a list of all SEP Cloud policies
    .DESCRIPTION
        Provides a list of all SEP Cloud policies
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER limit
        The number of records fetched in a given request .
        [NOTE] The maximum number of records supported per request is 1000
    .PARAMETER offset
        When this field is not present, it returns the first page
    .PARAMETER name
        The name of the policy you want to search for
    .PARAMETER type
        The type of policy you want to search for
    .EXAMPLE
        Get-SEPCloudPolicesSummary
        Gathers all possible policies in your SEP Cloud account
    .EXAMPLE
        Get-SEPCloudPolicesSummary -name "My Exploit Protection Policy"
 
        name : My Exploit Protection Policy
        author : Imported from SEPM
        policy_uid : abcdef123-abcd-5678-1234-123456789012
        policy_version : 1
        policy_type : Exploit Protection
        is_imported : True
        locked : True
        created : 28/06/2024 11:33:45
        modified : 28/06/2024 11:33:45
 
        Gathers a summary of your specific policy
    .EXAMPLE
        Get-SEPCloudPolicesSummary -type "Exploit Protection"
 
        Gathers all Exploit Protection policies from your tenant
    #>


    [CmdletBinding()]
    param (
        $limit, # Defaults maximum limit is 1000
        $offset,
        $name,
        $type
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if ($result.total -gt $result.policies.count) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset query param for pagination
                $offset = $result.policies.count
                $uri = New-URIString -endpoint ($resources.URI) -id $id
                $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.policies += $nextResult.policies
            } until ($result.policies.count -ge $result.total)
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudPolicesSummary.ps1' 92
#Region '.\Public\Get-SEPCloudTargetRules.ps1' 0
function Get-SepCloudTargetRules {

    <#
    .SYNOPSIS
        Provides a list of all target rules in your SEP Cloud account
    .DESCRIPTION
        Provides a list of all target rules in your SEP Cloud account. Formely known as SEP Location awareness
    .PARAMETER
        None
    .OUTPUTS
        PSObject
    .EXAMPLE
        Get-SepCloudTargetRules
        Gathers all possible target rules
    #>


    [CmdletBinding()]
    param (
        # Query
        [Alias('api_page')]
        $offset
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        # Test if pagination required
        if ($result.total -gt $result.target_rules.count) {
            Write-Verbose -Message "Result limits hit. Retrieving remaining data based on pagination"
            do {
                # Update offset query param for pagination
                $offset = $result.target_rules.count
                $uri = Test-QueryParam -querykeys $resources.query -parameters ((Get-Command $function).Parameters.Values) -uri $uri
                $nextResult = Submit-Request  -uri $uri  -header $script:SEPCloudConnection.header  -method $($resources.Method) -body $body
                $result.target_rules += $nextResult.target_rules
            } until ($result.target_rules.count -ge $result.total)
        }

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudTargetRules.ps1' 65
#Region '.\Public\Get-SEPCloudThreatIntelCveProtection.ps1' 0
function Get-SEPCloudThreatIntelCveProtection {

    <#
    .SYNOPSIS
        Provide information whether a given CVE has been blocked by any of Symantec technologies
    .DESCRIPTION
        Provide information whether a given URL/domain has been blocked by any of Symantec technologies.
        These technologies include Antivirus (AV), Intrusion Prevention System (IPS) and Behavioral Analysis & System Heuristics (BASH)
    .PARAMETER cve
        Specify one or many CVE to check
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .EXAMPLE
        Get-SepThreatIntelCveProtection -cve CVE-2023-35311
        Gathers information whether CVE-2023-35311 has been blocked by any of Symantec technologies
    .EXAMPLE
        "CVE-2023-35311","CVE-2023-35312" | Get-SepThreatIntelCveProtection
        Gathers cve from pipeline by value whether CVE-2023-35311 & CVE-2023-35312 have been blocked by any of Symantec technologies
    #>


    [CmdletBinding()]
    param (
        # Mandatory cve
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('vuln', 'vulnerability')]
        [string[]]
        $cve
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $cve
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudThreatIntelCveProtection.ps1' 61
#Region '.\Public\Get-SEPCloudThreatIntelFileInsight.ps1' 0
function Get-SEPCloudThreatIntelFileInsight {

    <#
    .SYNOPSIS
        Provide file insight enrichments for a given file
    .DESCRIPTION
        Provide file insight enrichments for a given file
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER file_sha256
        Specify one or many sha256 hash
    .EXAMPLE
        PS C:\PSSymantecCloud> Get-SepThreatIntelFileInsight -file_sha256 eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d
 
        file : eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d
        reputation : BAD
        prevalence : Hundreds
        firstSeen : 2018-04-13
        lastSeen : 2023-09-03
        targetOrgs :
    .EXAMPLE
    "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d" | Get-SepThreatIntelFileInsight
    #>


    [CmdletBinding()]
    param (
        # Mandatory file sha256
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('sha256')]
        $file_sha256
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $file_sha256
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudThreatIntelFileInsight.ps1' 68
#Region '.\Public\Get-SEPCloudThreatIntelFileProtection.ps1' 0
function Get-SEPCloudThreatIntelFileProtection {

    <#
    .SYNOPSIS
        Provide information whether a given file has been blocked by any of Symantec technologies
    .DESCRIPTION
        Provide information whether a given file has been blocked by any of Symantec technologies.
        These technologies include Antivirus (AV), Intrusion Prevention System (IPS) and Behavioral Analysis & System Heuristics (BASH)
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER file_sha256
        Specify one or many sha256 hash
    .EXAMPLE
        Get-SepThreatIntelFileProtection -file_sha256 eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d
        Gathers information whether the file with sha256 has been blocked by any of Symantec technologies
    .EXAMPLE
        "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d","eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8e" | Get-SepThreatIntelFileProtection
        Gathers sha from pipeline by value whether the files with sha256 have been blocked by any of Symantec technologies
    #>


    [CmdletBinding()]
    Param(
        # Mandatory file sha256
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('sha256')]
        $file_sha256
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $file_sha256
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudThreatIntelFileProtection.ps1' 64
#Region '.\Public\Get-SEPCloudThreatIntelFileRelated.ps1' 0
function Get-SEPCloudThreatIntelFileRelated {

    <#
    .SYNOPSIS
        Provide related file for a given file
    .DESCRIPTION
        Provide related file for a given file
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER file_sha256
        Specify one or many sha256 hash
    .EXAMPLE
        PS C:\PSSymantecCloud> "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d" | Get-SepThreatIntelFileRelated
 
        file related
        ---- -------
        eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d {@{iocType=File; iocValues=System.Object[]; relation=byProcessChain}, @{iocType=File; iocValues=System.Object[]; relation=bySignature}}
    #>


    [CmdletBinding()]
    Param(
        # Mandatory file sha256
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('sha256')]
        $file_sha256
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $file_sha256
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudThreatIntelFileRelated.ps1' 63
#Region '.\Public\Get-SEPCloudThreatIntelNetworkInsight.ps1' 0
function Get-SEPCloudThreatIntelNetworkInsight {

    <#
    .SYNOPSIS
        Provide domain insight enrichments for a given domain
    .DESCRIPTION
        Provide domain insight enrichments for a given domain
    .INPUTS
        domain
    .OUTPUTS
        PSObject
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER domain
        Specify one or many domain
    .EXAMPLE
        PS C:\PSSymantecCloud> Get-SepThreatIntelNetworkInsight -domain "elblogdeloscachanillas.com.mx/s3sy8rq10/ophn.png"
 
        network : elblogdeloscachanillas.com.mx/s3sy8rq10/ophn.png
        threatRiskLevel : @{level=10}
        categorization : @{categories=System.Object[]}
        reputation : BAD
        targetOrgs : @{topCountries=System.Object[]; topIndustries=System.Object[]}
    .EXAMPLE
    "elblogdeloscachanillas.com.mx/s3sy8rq10/ophn.png" | Get-SepThreatIntelNetworkInsight
    #>


    [CmdletBinding()]
    Param(
        # Mandatory domain
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('URL')]
        $domain
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $domain
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudThreatIntelNetworkInsight.ps1' 67
#Region '.\Public\Get-SEPCloudThreatIntelNetworkProtection.ps1' 0
function Get-SEPCloudThreatIntelNetworkProtection {

    <#
    .SYNOPSIS
        Provide information whether a given URL/domain has been blocked by any of Symantec technologies
    .DESCRIPTION
        Provide information whether a given URL/domain has been blocked by any of Symantec technologies.
        These technologies include Antivirus (AV), Intrusion Prevention System (IPS) and Behavioral Analysis & System Heuristics (BASH)
    .PARAMETER domain
        Specify one or many URL/domain to check
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .OUTPUTS
        PSObject
    .EXAMPLE
        Get-SepThreatIntelNetworkProtection -domain nicolascoolman.eu
        Gathers information whether the URL/domain has been blocked by any of Symantec technologies
    .EXAMPLE
        "nicolascoolman.eu" | Get-SepThreatIntelNetworkProtection
        Gathers somains from pipeline by value whether the URLs/domains have been blocked by any of Symantec technologies
    #>


    [CmdletBinding()]
    param (
        # Mandatory domain name
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('domain', 'url')]
        $network
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $network
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPCloudThreatIntelNetworkProtection.ps1' 62
#Region '.\Public\Get-SEPCloudToken.ps1' 0
function Get-SEPCloudToken {
    <#
    .SYNOPSIS
    Generates an authenticated Token from the SEP Cloud API
    .DESCRIPTION
    Gathers Bearer Token from the SEP Cloud console to interact with the authenticated API
    Securely stores credentials or valid token locally (By default on TEMP location)
    Connection information available here : https://sep.securitycloud.symantec.com/v2/integration/client-applications
 
    .PARAMETER clientId
    clientId parameter required to generate a token
 
    .PARAMETER secret
    secret parameter required in combinaison to clientId to generate a token
 
    .PARAMETER cacheOnly
    if set to $true, will only lookup for in-memory or local cache of token/credentials.
    Will not prompt for credentials or generate a new token.
    Usefful for automation.
 
    .INPUTS
    [string] clientId
    [string] secret
    .OUTPUTS
    [PSCustomObject] Token
 
    .EXAMPLE
    Get-SEPCloudToken
    .EXAMPLE
    Get-SEPCloudToken(clientId,secret)
    .EXAMPLE
    Get-SEPCloudToken -clientId "myclientid" -secret "mysecret"
 
    .NOTES
    Function logic
    - Test if token is already loaded in memory (and verify its validity)
    - Test locally stored encrypted token (and verify its validity)
    - Test if credentials is already loaded in memory to generate a token
    - Test locally stored encrypted Client/secret to generate a token
    - Requests Client/secret to generate token
    #>



    [CmdletBinding(DefaultParameterSetName = 'ClientIdSecret')]
    param (
        # clientId from SEP Cloud Connection App
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ClientIdSecret'
        )]
        [string]
        $clientId,

        # secret from SEP Cloud Connection App
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'ClientIdSecret'
        )]
        [string]
        $secret,

        # Unattended
        [switch]
        [Alias("unattended")]
        $cacheOnly
    )

    # If -cacheOnly do not attempt to prompt for credentials for unattended mode
    if ($cacheOnly) {
        return $null
    }

    # Test if clientId and secret are provided to generate a token without testing for locally stored encrypted token/credentials
    if ($clientId -and $secret) {
        Write-Verbose -Message "clientId & secret provided - testing to generate a token"
        $encodedCreds = [convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($clientId + ':' + $secret)))

        try {
            $params = @{
                Uri             = 'https://' + $script:SEPCloudConnection.baseURL + '/v1/oauth2/tokens'
                Method          = 'POST'
                Headers         = @{
                    Host          = $script:SEPCloudConnection.baseURL
                    Accept        = "application/json"
                    Authorization = "Basic " + $encodedCreds
                }
                useBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params

            if ($null -ne $response) {
                # Cache the credentials
                Write-Verbose -Message "credentials valid - caching credentials : $($script:configuration.SEPCloudCredsPath)"
                $credentialsDirectory = Split-Path -Path $script:configuration.SEPCloudCredsPath -Parent
                if (-not (Test-Path -Path $credentialsDirectory)) {
                    New-Item -ItemType Directory -Path $credentialsDirectory | Out-Null
                }
                $encodedCreds | Export-Clixml -Path $script:configuration.SEPCloudCredsPath
                $script:SEPCloudConnection.Credential = $encodedCreds

                # Cache the token
                Write-Verbose "credentials valid - returning valid Bearer token"
                $cachedToken = [PSCustomObject]@{
                    Token        = $response.access_token
                    Token_Type   = $response.token_type
                    Token_Bearer = $response.token_type.ToString() + " " + $response.access_token
                    Expiration   = (Get-Date).AddSeconds($response.expires_in) # token expiration is 3600s
                }
                $script:SEPCloudConnection.AccessToken = $cachedToken
                $tokenDirectory = Split-Path -Path $script:configuration.cachedTokenPath -Parent
                if (-not (Test-Path -Path $tokenDirectory)) {
                    New-Item -ItemType Directory -Path $tokenDirectory | Out-Null
                }
                $cachedToken | Export-Clixml -Path $script:configuration.cachedTokenPath

                Write-Verbose -Message "stored valid token : $($script:configuration.cachedTokenPath)"
                return $cachedToken
            }
        } catch {
            $message = "Authentication error - Failed to gather token from locally stored credentials"
            $message = $message + "`n" + "Expected HTTP 200, got $($_.Exception.Response.StatusCode)" + "`n"
            $message = $message + "delete cached credentials"
            $message = $message + "`n" + "Error : $($_.Exception.Response.StatusCode) : $($_.Exception.Response.StatusDescription)"
            Write-Verbose -Message $message
        }
    }

    # Test if token already in memory
    if ($null -ne $script:SEPCloudConnection.AccessToken.access_token) {
        # Check if still valid
        if (Test-SEPCloudToken) {
            Write-Verbose -Message "Token in-memory is still valid"
            return $script:SEPCloudConnection.AccessToken
        } else {
            try { Remove-Item -Path $script:configuration.cachedTokenPath -ErrorAction SilentlyContinue } catch {}
            $script:SEPCloudConnection.AccessToken = $null
        }
    }

    # Test if token present on the disk
    if (Test-Path -Path $script:configuration.cachedTokenPath) {
        $cachedToken = Import-Clixml -Path $script:configuration.cachedTokenPath
        # Check if still valid
        if (Test-SEPCloudToken -token $cachedToken) {
            Write-Verbose "Token from disk is still valid"
            return $cachedToken
        } else {
            Write-Verbose -Message "Token from disk expired - deleting"
            try { Remove-Item -Path $script:configuration.cachedTokenPath -ErrorAction SilentlyContinue } catch {}
            $script:SEPCloudConnection.AccessToken = $null
        }
    }

    # Test if OAuth cred present in memory
    if ($script:SEPCloudConnection.Credential) {
        Write-Verbose -Message "credentials in-memory available - testing"
        try {
            $params = @{
                Uri     = 'https://' + $script:SEPCloudConnection.baseURL + '/v1/oauth2/tokens'
                Method  = 'POST'
                Headers = @{
                    Host          = $script:SEPCloudConnection.baseURL
                    Accept        = "application/json"
                    Authorization = "Basic " + $script:SEPCloudConnection.Credential
                }
            }
            $response = Invoke-RestMethod @params

            if ($null -ne $response) {
                # Get the auth token from the response. Store it locally & in memory
                Write-Verbose -Message "credentials in-memory valid - returning valid token"
                $cachedToken = [PSCustomObject]@{
                    Token        = $response.access_token
                    Token_Type   = $response.token_type
                    Token_Bearer = $response.token_type + " " + $response.access_token
                    Expiration   = (Get-Date).AddSeconds($response.expires_in) # token expiration is 3600s
                }
                $cachedToken | Export-Clixml -Path $script:configuration.cachedTokenPath
                $script:SEPCloudConnection.AccessToken = $cachedToken
                Write-Verbose -Message "stored valid token : $($script:configuration.cachedTokenPath)"
                return $cachedToken
            }
        } catch {
            $message = "Authentication error - Failed to gather token from locally stored credentials"
            $message = $message + "`n" + "Expected HTTP 200, got $($_.Exception.Response.StatusCode)" + "`n"
            $message = $message + "delete cached credentials"
            $message = $message + "`n" + "Error : $($_.Exception.Response.StatusCode) : $($_.Exception.Response.StatusDescription)"
            Write-Verbose -Message $message
            $script:SEPCloudConnection.Credential = $null
        }
    }

    # Test if OAuth cred present on the disk
    if ((Test-Path -Path $script:configuration.SEPCloudCredsPath)) {
        Write-Verbose "credentials on disk available - testing"
        try {
            $params = @{
                Uri     = 'https://' + $script:SEPCloudConnection.baseURL + '/v1/oauth2/tokens'
                Method  = 'POST'
                Headers = @{
                    Host          = $script:SEPCloudConnection.baseURL
                    Accept        = "application/json"
                    Authorization = "Basic " + (Import-Clixml -Path $script:configuration.SEPCloudCredsPath)
                }
            }
            $response = Invoke-RestMethod @params

            if ($null -ne $response) {
                # Get the auth token from the response. Store it locally & in memory
                Write-Verbose "credentials valid - returning valid token"
                $cachedToken = [PSCustomObject]@{
                    Token        = $response.access_token
                    Token_Type   = $response.token_type
                    Token_Bearer = $response.token_type + " " + $response.access_token
                    Expiration   = (Get-Date).AddSeconds($response.expires_in) # token expiration is 3600s
                }
                $cachedToken | Export-Clixml -Path $script:configuration.cachedTokenPath
                Write-Verbose -Message "stored valid token : $($script:configuration.cachedTokenPath)"
                $script:SEPCloudConnection.AccessToken = $cachedToken
                return $cachedToken
            }

        } catch {
            $message = "Authentication error - Failed to gather token from locally stored credentials"
            $message = $message + "`n" + "Expected HTTP 200, got $($_.Exception.Response.StatusCode)" + "`n"
            $message = $message + "delete cached credentials"
            $message = $message + "`n" + "Error : $($_.Exception.Response.StatusCode) : $($_.Exception.Response.StatusDescription)"
            Write-Verbose -Message $message
        }
    }

    # If no token nor OAuth creds available locally
    # Encode clientId and secret to create Basic Auth string
    # Authentication requires the following "Basic + encoded CliendID:Clientsecret"

    Write-Verbose -Message "testing authentication with client/secret provided"
    if ($clientID -eq "" -or $secret -eq "") {

        Write-Host "No local credentials found. Please provide clientId and secret to generate a token"
        $clientId = Read-Host -Prompt "Enter clientId"
        $secret = Read-Host -Prompt "Enter secret" # -MaskInput TODO removed maskedInput. Not working with PS 5.1
    }
    $encodedCreds = [convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($clientId + ':' + $secret)))

    try {
        $params = @{
            Uri             = 'https://' + $script:SEPCloudConnection.baseURL + '/v1/oauth2/tokens'
            Method          = 'POST'
            Headers         = @{
                Host          = $script:SEPCloudConnection.baseURL
                Accept        = "application/json"
                Authorization = "Basic " + $encodedCreds
            }
            useBasicParsing = $true
        }
        $Response = Invoke-RestMethod @params

        if ($null -ne $response) {
            # Cache the credentials
            Write-Verbose -Message "credentials valid. caching credentials : $($script:configuration.SEPCloudCredsPath)"
            $credentialsDirectory = Split-Path -Path $script:configuration.SEPCloudCredsPath -Parent
            if (-not (Test-Path -Path $credentialsDirectory)) {
                New-Item -ItemType Directory -Path $credentialsDirectory | Out-Null
            }
            $encodedCreds | Export-Clixml -Path $script:configuration.SEPCloudCredsPath
            $script:SEPCloudConnection.Credential = $encodedCreds

            # Cache the token
            Write-Verbose "credentials valid - returning valid Bearer token"
            $cachedToken = [PSCustomObject]@{
                Token        = $response.access_token
                Token_Type   = $response.token_type
                Token_Bearer = $response.token_type.ToString() + " " + $response.access_token
                Expiration   = (Get-Date).AddSeconds($response.expires_in) # token expiration is 3600s
            }
            $script:SEPCloudConnection.AccessToken = $cachedToken
            $tokenDirectory = Split-Path -Path $script:configuration.cachedTokenPath -Parent
            if (-not (Test-Path -Path $tokenDirectory)) {
                New-Item -ItemType Directory -Path $tokenDirectory | Out-Null
            }
            $cachedToken | Export-Clixml -Path $script:configuration.cachedTokenPath

            Write-Verbose -Message "stored valid token : $($script:configuration.cachedTokenPath)"
            return $cachedToken
        }
    } catch {
        $message = "Authentication error - Failed to gather token from locally stored credentials"
        $message = $message + "`n" + "Expected HTTP 200, got $($_.Exception.Response.StatusCode)" + "`n"
        $message = $message + "delete cached credentials"
        $message = $message + "`n" + "Error : $($_.Exception.Response.StatusCode) : $($_.Exception.Response.StatusDescription)"
        Write-Verbose -Message $message
    }
}
#EndRegion '.\Public\Get-SEPCloudToken.ps1' 294
#Region '.\Public\Get-SEPThreatIntelFileProcessChain.ps1' 0
function Get-SEPCloudThreatIntelFileProcessChain {

    <#
    .SYNOPSIS
        Provide topK process lineage enrichment for the provided file sha256.
    .DESCRIPTION
        Provide topK process lineage enrichment for the provided file sha256.
    .INPUTS
        sha256
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .OUTPUTS
        PSObject
    .PARAMETER file_sha256
        Specify one or many sha256 hash
    .EXAMPLE
        PS C:\PSSymantecCloud> $ProcessChain = Get-SEPCloudThreatIntelFileProcessChain -file_sha256 eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d
 
        file chain
        ---- -----
        eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d {@{parent=}}
 
        PS C:\PSSymantecCloud> $ProcessChain.chain
 
        parent
        ------
        @{parent=; file=18bba9ff311154415404e2fb16f3784e4c82b57ad110092ea5f9b76ed549e7cb; processName=fe392ea0a9f14s4dfeda8d9u0233a6ioq6e47a5n3.exe}
 
        .EXAMPLE
        "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d" | Get-SEPCloudThreatIntelFileProcessChain
    #>


    [CmdletBinding()]
    Param(
        # Mandatory file sha256
        [Parameter(
            Mandatory,
            ValueFromPipeline = $true)]
        [Alias('sha256')]
        $file_sha256
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $file_sha256
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)

        Write-Verbose -Message "Body is $(ConvertTo-Json -InputObject $body)"
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body

        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        return $result
    }
}
#EndRegion '.\Public\Get-SEPThreatIntelFileProcessChain.ps1' 72
#Region '.\Public\Move-SEPCloudDevice.ps1' 0
function Move-SEPCloudDevice {

    <#
    .SYNOPSIS
        Moves one or many devices to a different group
    .DESCRIPTION
        Moves one or many devices to a different group.
        Requires group ID and device ID. does not support device name or group name.
        You can use :
            Get-SEPCloudDevice to get the device ID
            Get-SEPCloudGroup to get the group ID
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER GroupID
        The group ID to move the device to
    .PARAMETER deviceId
        The device ID to move
        can be an array of device ID's
        [NOTE] maximum of 200 devices per call
    None
    .EXAMPLE
        Move-SEPCloudDevice -GroupID "tqrSman3RyqFFd1EqLlZZA" -DeviceID "f3teVmApQlya8XJvEf-wpw"
 
            Move-SEPCloudDevice -GroupID "tqrSman3RyqFFd1EqLlZZA" -DeviceID "f3teVmApQlya8XJvEf-wpw"
 
            device_uid message status
            ---------- ------- ------
            f3teVmApQlya8XJvEf-wpw Moved successfully MOVED
 
        Moves a device to a different group, returns the status of the move.
    .EXAMPLE
        $list = @('123', '456', '789')
        Move-SEPCloudDevice -groupId "I5tExK6hQfC-cnUXk1Siug" -deviceId $list
 
        Moves all devices from their identifier (here 123,456,789) to a group from a single API call
    #>


    [CmdletBinding()]
    Param(
        # Group ID
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        $groupId,

        # Device ID
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true
        )]
        [Alias('device_uids')]
        [ValidateCount(1, 200)]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $deviceId
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $groupId
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result
        return $result
    }
}
#EndRegion '.\Public\Move-SEPCloudDevice.ps1' 83
#Region '.\Public\Remove-SEPCloudPolicy.ps1' 0
function Remove-SEPCloudPolicy {

    <#
    .SYNOPSIS
        Removes a SEP Cloud policy from a device group
    .DESCRIPTION
        Removes a SEP Cloud policy from a device group.
        Must include a specific location (also called policy target rule)
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER policyName
        Name of the policy to apply
    .PARAMETER policyVersion
        Version of the policy to apply.
        If not provided, the latest version will be used
    .PARAMETER targetRules
        Alias: location
        Location (policy target rule) to apply the policy to
        If not provided, the default location will be used
    .PARAMETER deviceGroupId
        Device group ID to apply the policy to
    .OUTPUTS
        None
    .EXAMPLE
        Remove-SEPCloudPolicy -policyName "My Policy" -location "Default" -deviceGroupId "123456"
        Removes the latest version of the SEP Cloud policy named "My Policy" to the device group with ID "123456" at the location "Default"
    #>


    [CmdletBinding()]
    param (
        $policyName,

        [Alias("version")]
        $policyVersion,

        [Alias("policy_uid")]
        $policyId,

        [Parameter(Mandatory = $true)]
        [Alias("target_rules")]
        [Alias("location")]
        [string[]]
        $targetRule = "Default",

        [Parameter(Mandatory = $true)]
        [Alias("device_group_ids")]
        [string[]]
        $deviceGroupId
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        # changing "Content-Type" header specifically for this query, otherwise 415 : unsupported media type
        # $script:SEPCloudConnection.header += @{ 'Accept' = 'application/json' }

        if ($policyName -and ($null -eq $policyId)) {
            Write-Verbose -Message "Searching ID for $policyName"
            $policyId = (Get-SEPCloudPolicesSummary | Where-Object { $_.name -eq "$policyName" }).policy_uid
        }
        if ($null -eq $policyVersion ) {
            Write-Verbose -Message "No policy version provided, retrieving the latest version of $policyName"
            # By default the API returns the latest version of a policy
            $policyVersion = (Get-SEPCloudPolicesSummary | Where-Object { $_.name -eq "$policyName" }).policy_version
        }
        $id = @($policyId, $policyVersion)
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        # Removing "Content-Type: application/json" header
        # $script:SEPCloudConnection.header.remove('Content-Type')

        return $result
    }
}
#EndRegion '.\Public\Remove-SEPCloudPolicy.ps1' 93
#Region '.\Public\Set-SEPCloudPolicy.ps1' 0
function Set-SEPCloudPolicy {

    <#
    .SYNOPSIS
        Apply a SEP Cloud policy to a device group
    .DESCRIPTION
        Apply a SEP Cloud policy to a device group. Must include a specific location (also called policy target rule)
    .LINK
        https://github.com/Douda/PSSymantecCloud
    .PARAMETER policyName
        Name of the policy to apply
    .PARAMETER policyId
        Id of the policy to apply
        If not provided, Id will be pulled from the policy name
    .PARAMETER policyVersion
        Version of the policy to apply.
        If not provided, the latest version will be used by default
    .PARAMETER target_rules
        Alias: location
        Location (policy target rule) to apply the policy to.
        If not provided, the default location will be used
    .PARAMETER device_group_ids
        Device group ID to apply the policy to
    .EXAMPLE
        Set-SEPCloudPolicy -policyName "My Policy" -location "Default" -deviceGroupID "123456"
        Apply the latest version of the SEP Cloud policy named "My Policy" to the device group with ID "123456" at the location "Default"
    #>


    [CmdletBinding(DefaultParameterSetName = "byName")]
    Param(
        [Parameter(ParameterSetName = "byName")]
        $policyName,

        [Alias("policy_uid")]
        $policyId,

        [Parameter(ParameterSetName = "byId")]
        [Alias("version")]
        $policyVersion,

        [Alias("location")]
        [Alias("targetRule")]
        [string[]]
        $target_rules = "Default",

        [Alias("deviceGroupId")]
        [string[]]
        $device_group_ids,

        $override = $true
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        if ($policyName -and ($null -eq $policyId)) {
            Write-Verbose -Message "Searching ID for $policyName"
            $policyId = (Get-SEPCloudPolicesSummary | Where-Object { $_.name -eq "$policyName" }).policy_uid
        }

        if ($null -eq $policyVersion ) {
            Write-Verbose -Message "No policy version provided, retrieving the latest version of $policyName"
            # By default the API returns the latest version of a policy
            $policyVersion = (Get-SEPCloudPolicesSummary | Where-Object { $_.name -eq "$policyName" }).policy_version
        }

        # Mandatory field for the API call
        # $override = $true

        $id = @($policyId, $policyVersion)
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result
        return $result
    }
}
#EndRegion '.\Public\Set-SEPCloudPolicy.ps1' 93
#Region '.\Public\Start-SEPCloudDefinitionUpdate.ps1' 0
function Start-SEPCloudDefinitionUpdate {

    <#
    .SYNOPSIS
        Initiate a definition update request command on SEP Cloud managed devices
    .DESCRIPTION
        Initiate a definition update request command on SEP Cloud managed devices
    .PARAMETER device_ids
        Array of device ids for which to initiate a definition update request
    .EXAMPLE
        Start-SepCloudDefinitionUpdate -deviceId "u7IcxqPvQKmH47MPinPsFw"
    .LINK
        https://github.com/Douda/PSSymantecCloud
    #>


    [CmdletBinding()]
    Param(
        [Alias('deviceId')]
        [string[]]
        $device_ids,

        [string[]]
        [Alias('orgId')]
        $org_unit_ids,

        [Alias('recursive')]
        [switch]
        $is_recursive
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        # TODO function is not working (500 error response)
        # changing "Content-Type" header specifically for this query, otherwise 415 : unsupported media type
        # $script:SEPCloudConnection.header += @{ 'Content-Type' = 'application/json' }
        $script:SEPCloudConnection.header += @{ 'Accept' = 'application/json' }

        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result

        # Removing "Content-Type: application/json" header
        $script:SEPCloudConnection.header.remove('Accept')

        return $result
    }
}
#EndRegion '.\Public\Start-SEPCloudDefinitionUpdate.ps1' 65
#Region '.\Public\Start-SEPCloudFullScan.ps1' 0
function Start-SEPCloudFullScan {

    <#
    .SYNOPSIS
        Initiate a full scan command on SEP Cloud managed devices
    .DESCRIPTION
        Initiate a full scan command on SEP Cloud managed devices
        Currently only takes a device_id as parameter
        device ID can be gathered from Get-SEPCloudDevice
    .EXAMPLE
        Start-SEPCloudFullScan -device_ids "u7IcxqPvQKmH47MPinPsFw"
    #>


    [CmdletBinding()]
    Param(
        [Alias('deviceId')]
        [string[]]
        $device_ids,

        [string[]]
        [Alias('orgId')]
        $org_unit_ids,

        [Alias('recursive')]
        [switch]
        $is_recursive
    )

    begin {
        # Check to ensure that a session to the SaaS exists and load the needed header data for authentication
        Test-SEPCloudConnection | Out-Null

        # API data references the name of the function
        # For convenience, that name is saved here to $function
        $function = $MyInvocation.MyCommand.Name

        # Retrieve all of the URI, method, body, query, result, and success details for the API endpoint
        Write-Verbose -Message "Gather API Data for $function"
        $resources = Get-SEPCLoudAPIData -endpoint $function
        Write-Verbose -Message "Load API data for $($resources.Function)"
        Write-Verbose -Message "Description: $($resources.Description)"
    }

    process {
        $uri = New-URIString -endpoint ($resources.URI) -id $id
        $uri = Test-QueryParam -querykeys ($resources.Query.Keys) -parameters ((Get-Command $function).Parameters.Values) -uri $uri
        $body = New-BodyString -bodykeys ($resources.Body.Keys) -parameters ((Get-Command $function).Parameters.Values)
        $result = Submit-Request -uri $uri -header $script:SEPCloudConnection.header -method $($resources.Method) -body $body
        $result = Test-ReturnFormat -result $result -location $resources.Result
        $result = Set-ObjectTypeName -TypeName $resources.ObjectTName -result $result
        return $result
    }
}
#EndRegion '.\Public\Start-SEPCloudFullScan.ps1' 54
#Region '.\Public\zz_Initialize-SEPCloudConfiguration.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

# Update the data types when loading the module
Update-TypeData -PrependPath (Join-Path -Path $PSScriptRoot -ChildPath 'PSSymantecCloud.Types.ps1xml')

# The credentials used to authenticate to the SES Cloud API
[string] $script:Credential = $null # string type used as credentials is OAuth2 token

# The session-cached copy of the module's configuration properties
# Configuration contains user-defined properties
# SEPCloudConnection contains the connection information to the SES Cloud API
[PSCustomObject] $script:configuration = $null
[PSCustomObject] $script:SEPCloudConnection = [PSCustomObject]@{
    BaseURL     = "api.sep.eu.securitycloud.symantec.com"
    Credential  = $null
    AccessToken = $null
    time        = (Get-Date)
    header      = $null
}

# Module name
[string] $script:ModuleName = "PSSymantecCloud"

# Load the configuration file
$script:configuration = [PSCustomObject]@{
    BaseURL           = "api.sep.securitycloud.symantec.com"
    SEPCloudCredsPath = [System.IO.Path]::Combine(
        [System.Environment]::GetFolderPath('LocalApplicationData'),
        'PSSymantecCloud',
        'creds.xml')
    CachedTokenPath   = [System.IO.Path]::Combine(
        [System.Environment]::GetFolderPath('LocalApplicationData'),
        'PSSymantecCloud',
        'accessToken.xml')
}


function Initialize-SEPCloudConfiguration {
    <#
    .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 credential from disk if it exists
    if (Test-Path -Path $($script:configuration.SEPCloudCredsPath)) {
        try {
            Write-Verbose -Message "Loading credential from $($script:configuration.SEPCloudCredsPath)"
            $script:Credential = Import-Clixml -Path $($script:configuration.SEPCloudCredsPath) -ErrorAction SilentlyContinue
            $script:SEPCloudConnection.Credential = Import-Clixml -Path $($script:configuration.SEPCloudCredsPath) -ErrorAction SilentlyContinue
        } catch {
            Write-Verbose "No credentials found from $($script:configuration.SEPCloudCredsPath)"
        }
    }

    # Load access token from disk
    if (Test-Path -Path $($script:configuration.CachedTokenPath)) {
        try {
            Write-Verbose -Message "Loading access token from $($script:configuration.CachedTokenPath)"
            Add-Member -Type NoteProperty -Name AccessToken -Value (Import-Clixml -Path $($script:configuration.CachedTokenPath) -ErrorAction SilentlyContinue) -InputObject $SEPCloudConnection -Force
        } catch {
            Write-Verbose -Message "Failed to import access token from $($script:configuration.CachedTokenPath): $_" -Verbose
        }
    }

    # Test for existing access token and refresh token
    if (Test-SEPCloudToken) {
        # Load headers if access token exists
        $UserAgentString = New-UserAgentString
        $script:SEPCloudConnection.Header = @{
            'Authorization' = $script:SEPCloudConnection.AccessToken.Token_Bearer
            'User-Agent'    = $UserAgentString
        }
    }

    # Attempt to connect to the SaaS with cached token or credentials
    # Will only attempt to connect via cached method (token or credentials) and not prompt for credentials
    Connect-SEPCloud -cacheOnly
}

# Invoke the initialization method to populate the configuration
Initialize-SEPCloudConfiguration #-Verbose #TODO remove verbose when done testing
#EndRegion '.\Public\zz_Initialize-SEPCloudConfiguration.ps1' 99