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\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-ConfigurationPath.ps1' 0
function Get-ConfigurationPath {
    <#
    .SYNOPSIS
        returns hashtable object with the BaseURL, SepCloudCreds, SepCloudToken full path
    .DESCRIPTION
        returns hashtable object with the BaseURL, SepCloudCreds, SepCloudToken full path
    .INPUTS
        None
    .OUTPUTS
        Hashtable
    .NOTES
        Created by: Aurélien BOUMANNE (02122022)
        helper function
    #>


    @{
        BaseUrl       = "api.sep.securitycloud.symantec.com"
        SepCloudCreds = "$env:TEMP\SepCloudOAuthCredentials.xml"
        CachedTokenPath   = "$env:TEMP\SepCloudCachedToken.xml"
    }

}
#EndRegion '.\Private\Get-ConfigurationPath.ps1' 23
#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-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
    .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
    1. Test locally stored encrypted token
    2. Test locally stored encrypted Client/Secret to generate a token
    3. Requests Client/Secret to generate token
    #>



    [CmdletBinding()]
    param (
        # ClientID from SEP Cloud Connection App
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $ClientID,

        # Secret from SEP Cloud Connection App
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $Secret
    )
    begin {
        try {
            # init
            $BaseURL = (Get-ConfigurationPath).BaseUrl
            $SepCloudCreds = (Get-ConfigurationPath).SepCloudCreds
            $CachedTokenPath = (Get-ConfigurationPath).CachedTokenPath
            $URI_Tokens = 'https://' + $BaseURL + '/v1/oauth2/tokens'

            if (-not $BaseURL) { throw "Missing 'BaseUrl' configuration value" }
            if (-not $SepCloudCreds) { throw "Missing 'SepCloudCreds' configuration value" }
            if (-not $CachedTokenPath) { throw "Missing 'CachedTokenPath' configuration value" }
        } catch {
            throw "Error initializing SEPCloudToken: $_"
        }
    }

    process {
        # Check if we already have a valid token
        if (Test-Path -Path "$CachedTokenPath") {
            $CachedToken = Import-Clixml -Path $CachedTokenPath
            # Check if still valid
            if ((Get-Date) -lt $CachedToken.Expiration) {
                Write-Verbose "cached token valid - returning"
                return $CachedToken
            } else {
                Write-Verbose "Cached token expired - deleting"
                Remove-Item $CachedTokenPath
            }
        }

        # Test if OAuth cred present on the disk
        if (Test-Path -Path "$SepCloudCreds") {
            <# If true, Attempt to get a token #>
            $OAuth = "Basic " + (Import-Clixml -Path $SepCloudCreds)
            $Headers = @{
                Host          = $BaseURL
                Accept        = "application/json"
                Authorization = $OAuth
            }

            try {
                $response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers
                # Get the auth token from the response & store it locally
                Write-Verbose "Valid credentials - 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 $CachedTokenPath
                return $CachedToken

            } catch {
                $StatusCode = $_.Exception.Response.StatusCode
                Write-Verbose "Authentication error - From locally stored credentials - Expected HTTP 200, got $([int]$StatusCode) - Continue..."
                # Invalid Credentials, deleting local credentials file
                Remove-Item $SepCloudCreds
            }
        }

        # 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"
        if ($clientID -eq "" -or $Secret -eq "") {
            Write-Host "No local credentials found"
            $ClientID = Read-Host -Prompt "Enter ClientID"
            $Secret = Read-Host -Prompt "Enter Secret"
        }
        $Encoded_Creds = [convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($ClientID + ':' + $Secret)))
        $Encoded_Creds | Export-Clixml -Path $SepCloudCreds

        # Create Basic Auth string
        $BasicAuth = "Basic " + $Encoded_Creds
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $BasicAuth
        }
        $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -UseBasicParsing
        # Get the auth token from the response & store it locally
        Write-Verbose "Valid credentials - returning valid Bearer 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 $CachedTokenPath
        return $CachedToken
    }
}
#EndRegion '.\Private\Get-SEPCloudToken.ps1' 144
#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\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 '.\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
    .EXAMPLE
        Block-SepCloudFile -ComputerName MyComputer -sha256 0536B2E0ACF3EF1933CF326EB4B423902A9E12B9348A28A336516EE32C94E25B
 
        BLocks a specific file on a specific computer
        .EXAMPLE
        Block-SepCloudFile -ComputerName Computer01,Computer02 -sha256 0536B2E0ACF3EF1933CF326EB4B423902A9E12B9348A28A336516EE32C94E25B, 0536B2E0ACF3EF1933CF326EB4B423902A9E12B9348A28A336516EE32C94E25B
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [Alias('Computer', 'Device', 'Hostname', 'Host')]
        [Collections.Generic.List[System.String]]
        $ComputerName,

        [Parameter()]
        [Alias('sha256')]
        [Collections.Generic.List[System.String]]
        $file_sha256
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        #Get list of devices ID from ComputerNames
        $Device_ID_list = New-Object System.Collections.Generic.List[string]
        foreach ($Computer in $ComputerName) {
            $Device_ID = (Get-SepCloudDevices -Computername $Computer).id
            $Device_ID_list += $Device_ID
        }

        $URI = 'https://' + $BaseURL + "/v1/commands/files/contain"
        $body = @{
            device_ids = $Device_ID_list
            hash       = $file_sha256
        }

        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        $params = @{
            Uri             = $URI
            Method          = 'POST'
            Body            = $Body | ConvertTo-Json
            Headers         = $Headers
            UseBasicParsing = $true
        }

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

        return $Resp
    }
}
#EndRegion '.\Public\Block-SepCloudFile.ps1' 72
#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 GitHub API token from memory, as well as from local file storage.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param ()

    Remove-Item -Path (Get-ConfigurationPath).CachedTokenPath -Force -ErrorAction SilentlyContinue -ErrorVariable ev
    Remove-Item -Path (Get-ConfigurationPath).SepCloudCreds -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 " + (Get-ConfigurationPath).SepCloudCreds
        $message += "Experienced a problem trying to remove the file that persists the Access Credentials " + (Get-ConfigurationPath).CachedTokenPath
        Write-Warning -Message $message
    }
}
#EndRegion '.\Public\Clear-SepCloudAuthentication.ps1' 27
#Region '.\Public\Export-SepCloudAllowListPolicyToExcel.ps1' 0
function Export-SepCloudAllowListPolicyToExcel {
    <#
    .SYNOPSIS
        Export an Allow List policy to a human readable excel report
    .DESCRIPTION
        Exports an allow list policy object it to an Excel file, with one tab per allow type (filename/file hash/directory etc...)
        Supports pipeline input with allowlist policy object
    .INPUTS
        Policy name
        Policy version
        Excel path
    .OUTPUTS
        Excel file
    .PARAMETER Policy_Name
        Name of the policy to export
    .PARAMETER Policy_Version
        Version of the policy to export
    .PARAMETER Excel_Path
        Path of the excel file to export
    .PARAMETER obj_policy
        Policy object to export
    .EXAMPLE
        Export-SepCloudAllowListPolicyToExcel -Name "My Allow list Policy" -Version 1 -Path "allow_list.xlsx"
        Exports the policy with name "My Allow list Policy" and version 1 to an excel file named "allow_list.xlsx"
    .EXAMPLE
        Get-SepCloudPolicyDetails -Name "My Allow list Policy" | Export-SepCloudAllowListPolicyToExcel -Path "allow_list.xlsx"
        Gathers policy in an object, pipes the output to Export-SepCloudAllowListPolicyToExcel to export in excel format
    #>

    [CmdletBinding(DefaultParameterSetName = 'PolicyName')]
    param (
        # Path of Export
        [Parameter(Mandatory)]
        [Alias('Excel', 'Path')]
        [string]
        $excel_path,

        # Policy version
        [Parameter(
            ParameterSetName = 'PolicyName'
        )]
        [string]
        [Alias("Version")]
        $Policy_Version,

        # Exact policy name
        [Parameter(
            ParameterSetName = 'PolicyName'
        )]
        [string]
        [Alias("Name")]
        $Policy_Name,

        # Policy Obj to work with
        [Parameter(
            ValueFromPipeline,
            ParameterSetName = 'PolicyObj'
        )]
        [Alias("PolicyObj")]
        [pscustomobject]
        $obj_policy
    )

    process {
        # If no PSObject is provided, get it from Get-SepCloudPolicyDetails
        if ($null -eq $PSBoundParameters['obj_policy']) {
            # Use specific version or by default latest
            if ($Policy_version -ne "") {
                $obj_policy = Get-SepCloudPolicyDetails -Name $Policy_Name -Policy_Version $Policy_Version
            } else {
                $obj_policy = Get-SepCloudPolicyDetails -Name $Policy_Name
            }
        }

        # Verify the policy is an allow list policy
        if ($obj_policy.type -ne "EXCEPTION") {
            throw "ERROR - The policy is not an allow list policy"
        }

        # Init
        $Applications = $obj_policy.features.configuration.applications.processfile
        $Certificates = $obj_policy.features.configuration.certificates
        $Webdomains = $obj_policy.features.configuration.webdomains
        $Extensions_list = $obj_policy.features.configuration.extensions.names
        $Files = $obj_policy.features.configuration.windows.files
        $Directories = $obj_policy.features.configuration.windows.directories
        $linux_Files = $obj_policy.features.configuration.linux.files
        $linux_Directories = $obj_policy.features.configuration.linux.directories
        $mac_Files = $obj_policy.features.configuration.mac.files
        $mac_Directories = $obj_policy.features.configuration.mac.directories

        # Split IPS ipv4 addresses & subnet in different arrays to export in different excel sheets
        $Ips_Hosts = $obj_policy.features.configuration.ips_hosts | Where-Object { $null -ne $_.ip } # removing empty strings
        $Ips_Hosts_subnet_v4 = $obj_policy.features.configuration.ips_hosts.ipv4_subnet | Where-Object { $_ } # removing empty strings
        $Ips_Hosts_subnet_v6_list = $obj_policy.features.configuration.ips_hosts.ipv6_subnet | Where-Object { $_ } # removing empty strings
        $Ips_range = $obj_policy.features.configuration.ips_hosts.ip_range | Where-Object { $_ } # removing empty strings

        # Split Extensions in an array of objects for correct formating
        $Extensions = @()
        foreach ($line in $Extensions_list) {
            $obj = New-Object -TypeName PSObject
            $obj | Add-Member -MemberType NoteProperty -Name Extensions -Value $line
            $Extensions += $obj
        }

        # split ipv6 subnet in an array of objects for correct formating
        $Ips_Hosts_subnet_v6 = @()
        foreach ($line in $Ips_Hosts_subnet_v6_list) {
            $obj = New-Object -TypeName PSObject
            $obj | Add-Member -MemberType NoteProperty -Name ipv6_subnet -Value $line
            $Ips_Hosts_subnet_v6 += $obj
        }

        # Exporting data to Excel
        $excel_params = @{
            ClearSheet   = $true
            BoldTopRow   = $true
            AutoSize     = $true
            FreezeTopRow = $true
            AutoFilter   = $true
        }
        # Import-Module -Name ImportExcel
        $Applications | Export-Excel $excel_path -WorksheetName "Applications" @excel_params
        $Files | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Files" @excel_params
        $Directories | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Directories" @excel_params
        $Extensions |  Export-Excel $excel_path -WorksheetName "Extensions" @excel_params
        $Webdomains | Export-Excel $excel_path -WorksheetName "Webdomains" @excel_params
        $Ips_Hosts | Export-Excel $excel_path -WorksheetName "Ips_Hosts" @excel_params
        $Ips_Hosts_subnet_v4 | Export-Excel $excel_path -WorksheetName "Ips_Hosts_subnet_v4" @excel_params
        $Ips_Hosts_subnet_v6 | Export-Excel $excel_path -WorksheetName "Ips_Hosts_subnet_v6" @excel_params
        $Ips_range | Export-Excel $excel_path -WorksheetName "Ips_range" @excel_params
        $Certificates | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Certificates" @excel_params
        $linux_Files | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Linux Files" @excel_params
        $linux_Directories | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Linux Directories" @excel_params
        $mac_Files | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Mac Files" @excel_params
        $mac_Directories | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Mac Directories" @excel_params
    }
}
#EndRegion '.\Public\Export-SepCloudAllowListPolicyToExcel.ps1' 138
#Region '.\Public\Export-SepCloudDenyListPolicyToExcel.ps1' 0
function Export-SepCloudDenyListPolicyToExcel {
    <# TODO : change help for deny list
    .SYNOPSIS
        Export a Deny List policy to a human readable excel report
    .INPUTS
        Policy name
        Policy version
        Excel path
    .OUTPUTS
        Excel file
    .DESCRIPTION
        Exports a Deny list policy object it to an Excel file, with one tab per allow type (Executable file / Non-PE file etc...)
        Supports pipeline input with allowlist policy object
    .EXAMPLE
        Export-SepCloudDenyListPolicyToExcel -Name "My Deny list Policy" -Version 1 -Path "deny_list.xlsx"
        Exports the policy with name "My Deny list Policy" and version 1 to an excel file named "deny_list.xlsx"
    .EXAMPLE
        Get-SepCloudPolicyDetails -Name "My Deny list Policy" | Export-SepCloudDenyListPolicyToExcel -Path "deny_list.xlsx"
        Gathers policy in an object, pipes the output to Export-SepCloudDenyListPolicyToExcel to export in excel format
    #>

    [CmdletBinding(DefaultParameterSetName = 'PolicyName')]
    param (
        # Path of Export
        [Parameter(Mandatory)]
        [Alias('Excel', 'Path')]
        [string]
        $excel_path,

        # Policy version
        [Parameter(
            # ParameterSetName = 'PolicyName'
        )]
        [string]
        [Alias("Version")]
        $Policy_Version,

        # Exact policy name
        [Parameter(
            # ParameterSetName = 'PolicyName'
        )]
        [string]
        [Alias("Name")]
        $Policy_Name,

        # Policy Obj to work with
        [Parameter(
            ValueFromPipeline,
            ParameterSetName = 'PolicyObj'
        )]
        [Alias("PolicyObj")]
        [pscustomobject]
        $obj_policy
    )

    process {
        # If no PSObject is provided, get it from Get-SepCloudPolicyDetails
        if ($null -eq $PSBoundParameters['obj_policy']) {
            # Use specific version or by default latest
            if ($Policy_version -ne "") {
                $obj_policy = Get-SepCloudPolicyDetails -Name $Policy_Name -Policy_Version $Policy_Version
            } else {
                $obj_policy = Get-SepCloudPolicyDetails -Name $Policy_Name
            }
        }

        # Verify the policy is a deny list policy
        if ($obj_policy.type -ne "BLACKLIST") {
            throw "ERROR - The policy is not a deny list policy"
        }

        # init
        $Executable_Files = $obj_policy.features.configuration.blacklistrules
        $NonPEFiles = $obj_policy.features.configuration.nonperules

        # formating data
        # Explicitely define the properties to export for file actors
        $params_actor = @{
            Property = 'sha2', 'md5', 'directory'
        }
        # Explicitely define the properties to export for non-pe files
        $params_file = @{
            Property = 'name', 'sha2', 'size', 'directory'
        }
        # loop through all files and actors to select the properties we want to export
        $i = 0
        foreach ($n in $NonPEFiles) {
            $actors = $NonPEFiles[$i].actor | Select-Object @params_actor
            $NonPEFiles[$i].actor = $actors

            $files = $NonPEFiles[$i].file | Select-Object @params_file
            $NonPEFiles[$i].file = $files
            $i++
        }

        # Exporting data to Excel
        $excel_params = @{
            ClearSheet   = $true
            BoldTopRow   = $true
            AutoSize     = $true
            FreezeTopRow = $true
            AutoFilter   = $true
        }
        Import-Module -Name ImportExcel
        $Executable_Files | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Executable Files" @excel_params
        $NonPEFiles | ConvertTo-FlatObject | Export-Excel $excel_path -WorksheetName "Non-PE Files" @excel_params
    }
}
#EndRegion '.\Public\Export-SepCloudDenyListPolicyToExcel.ps1' 108
#Region '.\Public\Get-EDRDumps.ps1' 0
function Get-EDRDumps {

    <# TODO write help
    .SYNOPSIS
        Gets a list of the SEP Cloud Commands
    .DESCRIPTION
        Gets a list of the SEP Cloud Commands. All commands are returned by default.
    .EXAMPLE
        Get-SepCloudCommands
 
        Gets a list of the SEP Cloud Commands
    #>


    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        #TODO add command_id by using get-SepCloudCommands
        $URI = 'https://' + $BaseURL + "/v1/commands/endpoint-search"
        $allResults = @()
        $body = @{
            query = ""
            next  = 0
        }
        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        do {
            try {
                $params = @{
                    Uri             = $URI
                    Method          = 'POST'
                    Body            = $Body | ConvertTo-Json
                    Headers         = $Headers
                    UseBasicParsing = $true
                }

                $Resp = Invoke-RestMethod @params
                $allResults += $Resp.commands
                $body.next = $Resp.next
            } catch {
                Write-Warning -Message "Error: $_"
            }

        } until ($null -eq $resp.next)

        return $allResults
    }
}
#EndRegion '.\Public\Get-EDRDumps.ps1' 57
#Region '.\Public\Get-SepCloudCommands.ps1' 0
function Get-SepCloudCommands {

    <# TODO write help
    .SYNOPSIS
        Gets a list of the SEP Cloud Commands
    .DESCRIPTION
        Gets a list of the SEP Cloud Commands. All commands are returned by default.
    .EXAMPLE
        Get-SepCloudCommands
 
        Gets a list of the SEP Cloud Commands
    #>


    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        $URI = 'https://' + $BaseURL + "/v1/commands/endpoint-search"
        $allResults = @()
        $body = @{
            query = ""
            next  = 0
        }
        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        do {
            try {
                $params = @{
                    Uri             = $URI
                    Method          = 'POST'
                    Body            = $Body | ConvertTo-Json
                    Headers         = $Headers
                    UseBasicParsing = $true
                }

                $Resp = Invoke-RestMethod @params
                $allResults += $Resp.commands
                $body.next = $Resp.next
            } catch {
                Write-Warning -Message "Error: $_"
            }

        } until ($null -eq $resp.next)

        return $allResults
    }
}
#EndRegion '.\Public\Get-SepCloudCommands.ps1' 56
#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
    .PARAMETER Device_ID
        id used to lookup a unique computer
    .PARAMETER Computername
        Computername used to lookup a unique computer
    .INPUTS
        Device_ID
        Computername
    .OUTPUTS
        PSObject
    .EXAMPLE
        Get-SepCloudDeviceDetails -id wduiKXDDSr2CVrRaqrFKNx
    .EXAMPLE
        Get-SepCloudDeviceDetails -computername MyComputer
    #>


    [CmdletBinding(DefaultParameterSetName = 'Computername')]
    param (
        # device_ID
        [Parameter(
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Device_ID')]
        [Alias("id")]
        [string]
        $Device_ID,

        # Computer Name
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Computername'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('Computer', 'Device', 'Hostname', 'Host')]
        [string]
        $Computername
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        switch ($PSBoundParameters.Keys) {
            Computername {
                # Get Device_ID if computername is provided
                $Device_ID = (Get-SepCloudDevices -Computername $Computername).id
            }
            Default {}
        }

        # Setup URI
        $URI = 'https://' + $BaseURL + "/v1/devices/$Device_ID"

        # Setup Headers
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
        $Response = Invoke-RestMethod -Method GET -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing
        return $Response

    }
}
#EndRegion '.\Public\Get-SepCloudDeviceDetails.ps1' 75
#Region '.\Public\Get-SepCloudDevices.ps1' 0
function Get-SepCloudDevices {
    <#
    .SYNOPSIS
    Gathers list of devices from the SEP Cloud console
    .PARAMETER Computername
    Specify one or many computer names. Accepts pipeline input
    Supports partial match
    .PARAMETER is_online
    Switch to lookup only online machines
    .PARAMETER Device_status
    Lookup devices per security status. Accepts only "SECURE", "AT_RISK", "COMPROMISED", "NOT_COMPUTED"
    .PARAMETER include_details
    Switch to include details in the output
    .PARAMETER Device_group
    Specify a device group ID to lookup. Accepts only device group ID, no group name
    .EXAMPLE
    Get-SepCloudDevices
    Get all devices (very slow)
    .EXAMPLE
    Get-SepCloudDevices -Computername MyComputer
    Get detailed information about a computer
    .EXAMPLE
    "MyComputer" | Get-SepCloudDevices
    Get detailed information about a computer
    .EXAMPLE
    Get-SepCloudDevices -Online -Device_status AT_RISK
    Get all online devices with AT_RISK status
    .EXAMPLE
    Get-SepCloudDevices -group "Aw7oerlBROSIl9O_IPFewx"
    Get all devices in a device group
    .EXAMPLE
    Get-SepCloudDevices -Client_version "14.3.9681.7000" -Device_type WORKSTATION
    Get all workstations with client version 14.3.9681.7000
    .EXAMPLE
    Get-SepCloudDevices -EdrEnabled -Device_type SERVER
    Get all servers with EDR enabled
    .EXAMPLE
    Get-SepCloudDevices -IPv4 "192.168.1.1"
    Get all devices with IPv4 address
    #>


    [CmdletBinding()]
    param (
        # Optional ComputerName parameter
        [Parameter(ValueFromPipeline = $true)]
        [string]
        $Computername,

        # Optional Is_Online parameter
        [Parameter()]
        [Alias("Online")]
        [switch]
        $is_online,

        # Optional include_details parameter
        [Parameter()]
        [Alias("Details")]
        [switch]
        $include_details,

        # Device Group
        [Parameter()]
        [Alias("Group")]
        [string]
        $Device_group,

        # Optional Device_Status parameter
        [Parameter()]
        [Alias("DeviceStatus")]
        [ValidateSet("SECURE", "AT_RISK", "COMPROMISED", "NOT_COMPUTED")]
        $Device_status,

        # Optional Device_Type parameter
        [Parameter()]
        [Alias("DeviceType")]
        [ValidateSet("WORKSTATION", "SERVER", "MOBILE")]
        $Device_type,

        # Optional Client_version parameter
        [Parameter()]
        [Alias("ClientVersion")]
        [string]
        $Client_version,

        # Optional edr_enabled parameter
        [Parameter()]
        [Alias("EdrEnabled")]
        [switch]
        $edr_enabled,

        # Optional IPv4 parameter
        [Parameter()]
        [Alias("IPv4")]
        [string]
        $ipv4_address
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $URI_Tokens = 'https://' + $BaseURL + "/v1/devices"
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        $ArrayResponse = @()
        # HTTP body content containing all the queries
        $Body = @{}

        # Iterating through all parameters and add them to the HTTP body
        switch ($PSBoundParameters.Keys) {
            Computername {
                $Body.Add("name", "$Computername")
            }
            is_online {
                $Body.Add("is_online", "true")
            }
            include_details {
                $Body.Add("include_details", "true")
            }
            edr_enabled {
                $Body.Add("edr_enabled", "true")
            }
            Device_status {
                $Body.Add("device_status", "$Device_status")
            }
            Device_type {
                $Body.Add("device_type", "$Device_type")
            }
            Client_version {
                $Body.Add("client_version", "$Client_version")
            }
            Device_group {
                $Body.Add("device_group", "$Device_group")
            }
            ipv4_address {
                $Body.Add("ipv4_address", "$IP_v4")
            }
            Default {}
        }

        # Setup Headers
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
        }

        try {
            $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing
            $ArrayResponse += $Response.devices
            $Devices_count_gathered = (($ArrayResponse | Measure-Object).count)
            <# If pagination #>
            if ($Response.total -gt $Devices_count_gathered) {
                <# Loop through via Offset parameter as there is no "next" parameter for /devices/ API call #>
                do {
                    # change the "offset" parameter for next query
                    $Body.Remove("offset")
                    $Body.Add("offset", $Devices_count_gathered)
                    # Run query, add it to the array, increment counter
                    $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing
                    $ArrayResponse += $Response.devices
                    $Devices_count_gathered = (($ArrayResponse | Measure-Object).count)
                } until (
                    $Devices_count_gathered -ge $Response.total
                )
            }

        } catch {
            $StatusCode = $_
            $StatusCode
        }

        return $ArrayResponse
    }
}
#EndRegion '.\Public\Get-SepCloudDevices.ps1' 177
#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
    .INPUTS
        [string] Query
        [int] PastDays
        [switch] FileDetection
        [switch] FullScan
    .OUTPUTS
        [PSCustomObject] Events
    .PARAMETER Query
        Runs a custom Lucene query
    .PARAMETER PastDays
        Number of days to go back in the past. Default is 29 days
    .PARAMETER FullScan
        Runs the following Lucene query "Event Type Id:8020-Scan AND Scan Name:Full Scan"
    .PARAMETER FileDetection
        Runs the following Lucene query "feature_name:MALWARE_PROTECTION AND ( type_id:8031 OR type_id:8032 OR type_id:8033 OR type_id:8027 OR type_id:8028 ) AND ( id:12 OR id:11 AND type_id:8031 )"
    .EXAMPLE
        Get-SepCloudEvents
        Gather all possible events. ** very slow approach & limited to 10k events **
    .EXAMPLE
        Get-SepCloudEvents -FileDetection -pastdays 20
        Gather all file detection events for the past 20 days
    .EXAMPLE
        Get-SepCloudEvents -FullScan
        Gather all full scan events
    .EXAMPLE
        Get-SepCloudEvents -Query "type_id:8031 OR type_id:8032 OR type_id:8033"
        Runs a custom Lucene query
    .EXAMPLE
        Get-SepCloudEvents -Query "type_id:8031 OR type_id:8032 OR type_id:8033" -PastDays 14
        Runs a custom Lucene query for the past 14 days
    #>

    [CmdletBinding(DefaultParameterSetName = 'Query')]
    param (
        # file Detection
        [Parameter(ParameterSetName = 'FileDetection')]
        [switch]
        $FileDetection,

        # full scan
        [Parameter(ParameterSetName = 'FullScan')]
        [switch]
        $FullScan,

        # Custom query to run
        [Parameter(ParameterSetName = 'Query')]
        [string]
        $Query,

        # Date parameter
        [Parameter()]
        [Alias("Days")]
        [int]
        $PastDays = 29
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $URI_Tokens = 'https://' + $BaseURL + "/v1/event-search"
        $ArrayResponse = @()
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {

        # HTTP body content containing all mandatory info to start a query
        $Body = @{
            "product"      = "SAEP"
            "feature_name" = "ALL"
        }
        <# Setting dates for the query Date Format required : -UFormat "%Y-%m-%dT%T.000+00:00"
        Example :
        "start_date": "2022-10-16T00:00:00.000+00:00",
        "end_date": "2022-11-16T00:00:00.000+00:00" #>


        # Setting dates for the query Date Format required : -UFormat "%Y-%m-%dT%T.000+00:00"
        $end_date = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK")
        $start_date = ((Get-Date).AddDays(-$PastDays) | Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK")
        $Body.Add("start_date", $start_date)
        $Body.Add("end_date", $end_date)

        # Iterating through all parameter and adding them to the HTTP body
        switch ($PSBoundParameters.Keys) {
            'FileDetection' {
                $Body.Add("query", '( feature_name:MALWARE_PROTECTION AND ( type_id:8031 OR type_id:8032 OR type_id:8033 OR type_id:8027 OR type_id:8028 ) AND ( id:12 OR id:11 AND type_id:8031 ) )')
            }
            'FullScan' {
                $Body.Add("query", 'Event Type Id:8020-Scan AND Scan Name:Full Scan')
            }
            'Query' {
                $Body.Add("query", "$Query")
            }
            Default {}
        }

        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        try {
            $params = @{
                Uri             = $URI_Tokens
                Method          = 'POST'
                Body            = $Body | ConvertTo-Json
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $ArrayResponse += $Response

            while ($Response.next) {
                # change the "next" offset for pagination
                $Body.Remove("next")
                $Body.Add("next", $Response.next)
                $Body_Json = ConvertTo-Json $Body

                # Run query & add it to the array
                $Response = Invoke-RestMethod -Method POST -Uri $URI_Tokens -Headers $Headers -Body $Body_Json -UseBasicParsing
                $ArrayResponse += $Response
            }
        } catch {
            $StatusCode = $_
            $StatusCode
        }

        return $ArrayResponse.events
    }
}
#EndRegion '.\Public\Get-SepCloudEvents.ps1' 138
#Region '.\Public\Get-SepCloudFeatureList.ps1' 0
function Get-SepCloudFeatureList {
    <#
    .SYNOPSIS
        retrieve SES enumeration details for your devices like feature names, security status and reason codes.
    .DESCRIPTION
        retrieve SES enumeration details for your devices like feature names, security status and reason codes.
    .PARAMETER
        None
    .INPUTS
        None
    .OUTPUTS
        PSObject
    .EXAMPLE
        Get-SepCloudFeatureList
        Gathers all possible feature name, content name, security status and reason codes the API can provide and its related IDs
    #>

    [CmdletBinding()]
    param ()

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $URI_Tokens = 'https://' + $BaseURL + "/v1/devices/enums"
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        # HTTP body content containing all the queries
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
        $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing
        return $Response
    }
}
#EndRegion '.\Public\Get-SepCloudFeatureList.ps1' 40
#Region '.\Public\Get-SepCloudFilesInfo.ps1' 0
function Get-SepCloudFilesInfo {

    <#
    .SYNOPSIS
        Gets information about a specific file hash and devices seen on
    .DESCRIPTION
        Gets information about a specific file hash and devices seen on
    .EXAMPLE
        Get-SepCloudFilesInfo -FileHash 423b212eab8073b53c46522ee2e2a5f14ffe090b5835f385b9818b08c242c2b6
 
        Gets information about a specific file hash and provides amount of devices seen on
        C:\PSSymantecCloud> $test
 
        most_prevalent_file_name : relog.exe
        folders : {C:\Windows\WinSxS\wow64_microsoft-windows-p..ncetoolscommandline_31bf3856ad364e35_10.0.19041.546_none_49716c2392052aca\r,
                                    C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.1706.1.7\wow64_microsoft-windows-p..ncetoolscommandline_31bf3856ad364e35_10.0.19041.546_none_49716c2392052aca\r,
                                    C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.1645.1.11\wow64_microsoft-windows-p..ncetoolscommandline_31bf3856ad364e35_10.0.19041.546_none_49716c2392052aca\r }
        file_hash : 423b212eab8073b53c46522ee2e2a5f14ffe090b5835f385b9818b08c242c2b6
        risk : VERY_LOW
        certificate_signer : Unsigned
        devices_seen_on : 891
        first_seen : 20/10/2021 14:09:45
        last_action : 02/04/2022 13:31:29
        reputation : GOOD
        prevalence : VERY_FEW_USERS
        global_prevalence : MANY_USERS
        file_size : 177
        hidden : False
        policies : {}
 
    .EXAMPLE
        Get-SepCloudFilesInfo -FileHash 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef -IncludeDevices
 
        Gets information about a specific file hash and provides list and details of devices seen on
    #>

    [CmdletBinding()]
    param (
        # devices switch
        [switch]
        $IncludeDevices,

        # file hash string
        [Parameter(
            ValueFromPipeline
        )]
        [string]
        $FileHash
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }
    }

    process {
        $URI_File_Hash_Details = 'https://' + $BaseURL + "/v1/files/" + $FileHash
        $URI_Devices_Seen_On = 'https://' + $BaseURL + "/v1/files/" + $FileHash + "/devices"

        # Devices seen on
        # If the devices switch is enabled
        if ($IncludeDevices) {
            # Init
            $allResults = @()

            # URI parameters
            $pageOffset = 0
            $limit = 500

            do {
                try {
                    $params = @{
                        Uri             = $URI_Devices_Seen_On + "?pageOffset=$pageOffset&limit=$limit"
                        Method          = 'GET'
                        Headers         = $Headers
                        UseBasicParsing = $true
                    }

                    $Resp = Invoke-RestMethod @params
                    $allResults += $Resp.devices
                    $pageOffset++
                } catch {
                    Write-Warning -Message "Error: $_"
                }
            } until ($allResults.count -eq $Resp.total)
            return $allResults
        }

        # File hash info (without devices details)
        if (-not $IncludeDevices) {
            do {
                try {
                    $params = @{
                        Uri             = $URI_File_Hash_Details
                        Method          = 'GET'
                        Headers         = $Headers
                        UseBasicParsing = $true
                    }

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

            } until ($null -eq $resp.next)
            return $Resp
        }
    }
}
#EndRegion '.\Public\Get-SepCloudFilesInfo.ps1' 116
#Region '.\Public\Get-SepCloudIncidentDetails.ps1' 0
function Get-SepCloudIncidentDetails {

    <#
    TODO finish up the API query from incident_number and not UID for ease of use. So far, not implemented in the API to query a closed INC by incident number
.SYNOPSIS
    Gathers details about an open incident
.DESCRIPTION
    Gathers details about an open incident. Currently only supports gathering details from an incident UID
.PARAMETER incident_uid
    Incident GUID
.PARAMETER incident_number
    Incident number -- NOT IMPLEMENTED YET --
.INPUTS
    None
.OUTPUTS
    PSObject
.EXAMPLE
    Get-SepCloudIncidentDetails -incident_uid "ed5924c6-b36d-4449-88c1-4a1f974a01bb"
#>

    [CmdletBinding()]
    param (
        # Incident GUID
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        [Alias("incident_uid")]
        $Incident_ID

        # # Incident number
        # [Parameter(
        # ValueFromPipeline
        # )]
        # [string[]]
        # [Alias("Name")]
        # $Incident_number
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        $URI = 'https://' + $BaseURL + "/v1/incidents/$Incident_ID"

        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
        $Resp = Invoke-RestMethod -Method GET -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing

        $Resp
    }
}
#EndRegion '.\Public\Get-SepCloudIncidentDetails.ps1' 60
#Region '.\Public\Get-SepCloudIncidents.ps1' 0
function Get-SepCloudIncidents {

    <#
    .SYNOPSIS
        Get list of SEP Cloud incidents. By default, shows only opened incidents
    .DESCRIPTION
        Get list of SEP Cloud incidents. Using the LUCENE query syntax, you can customize which incidents to gather.
        More information : https://techdocs.broadcom.com/us/en/symantec-security-software/endpoint-security-and-management/endpoint-security/sescloud/Endpoint-Detection-and-Response/investigation-page-overview-v134374740-d38e87486/Cloud-Database-Search/query-and-filter-operators-by-data-type-v134689952-d38e88796.html
    .PARAMETER Open
        filters only opened incidents. Simulates a query "state_id: [0 TO 3]" which represents incidents with the following states <0 Unknown | 1 New | 2 In Progress | 3 On Hold>
    .PARAMETER Include_events
        Includes every events that both are part of the context & triggered incident events
    .PARAMETER Query
        Custom Lucene query to pass to the API
    .INPUTS
        None
    .OUTPUTS
        PSObject containing all SEP incidents
    .EXAMPLE
        Get-SepCloudIncidents -Open -Include_Events
    .EXAMPLE
        Get-SepCloudIncidents -Query "state_id: [0 TO 5]"
        This query a list of every possible incidents (opened, closed and with "Unknown" status)
    #>

    [CmdletBinding(DefaultParameterSetName = 'QueryOpen')]
    param (
        # Opened incidents
        [Parameter(
            ParameterSetName = "QueryOpen"
        )]
        [switch]
        $Open,

        # Include events
        [Parameter()]
        [switch]
        $Include_events,

        # Custom query to run
        [Parameter(
            ParameterSetName = "QueryCustom"
        )]
        [string]
        $Query

    )
    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $URI_Tokens = 'https://' + $BaseURL + "/v1/incidents"
        $ArrayResponse = @()
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {

        # HTTP body content containing all the queries
        $Body = @{}

        # Settings dates
        $end_date = Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK"
        $start_date = ((Get-Date).addDays(-29) | Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffK")
        $Body.Add("start_date", $start_date)
        $Body.Add("end_date", $end_date)

        # Iterating through all parameter and adding them to the HTTP body
        switch ($PSBoundParameters.Keys) {
            'Query' {
                $Body.Add("query", "$Query")
            }
            'Open' {
                $Body.Add("query", "state_id: [0 TO 3]")
            }
            'Include_events' {
                $Body.Add("include_events", "true")
            }
            Default {
            }
        }

        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }


        try {
            do {
                $params = @{
                    Uri             = $URI_Tokens
                    Method          = 'POST'
                    Body            = $Body | ConvertTo-Json
                    Headers         = $Headers
                    UseBasicParsing = $true
                }

                $Response = Invoke-RestMethod @params
                $ArrayResponse += $Response
                $Body.Remove("next")
                $Body.Add("next", $Response.next)
            } until ($null -eq $Response.next)
        } catch {
                Write-Warning -Message "Error: $_"
            }

        return $ArrayResponse.incidents
    }
}
#EndRegion '.\Public\Get-SepCloudIncidents.ps1' 111
#Region '.\Public\Get-SepCloudPolices.ps1' 0
function Get-SepCloudPolices {
    <#
    .SYNOPSIS
        Provides a list of all policies in your SEP Cloud account
    .DESCRIPTION
        Provides a list of all policies in your SEP Cloud account
    .PARAMETER
        None
    .OUTPUTS
        PSObject
    .EXAMPLE
        Get-SepCloudPolices
        Gathers all possible policies in your SEP Cloud account
    #>


    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $URI_Tokens = 'https://' + $BaseURL + "/v1/policies"
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        try {
            $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing
        }

        catch {
            $StatusCode = $_
            $StatusCode
        }

        $Response
    }
}
#EndRegion '.\Public\Get-SepCloudPolices.ps1' 43
#Region '.\Public\Get-SepCloudPolicyDetails.ps1' 0
function Get-SepCloudPolicyDetails {

    <#
    .SYNOPSIS
        Gathers detailed information on SEP Cloud policy
    .DESCRIPTION
        Gathers detailed information on SEP Cloud policy
    .PARAMETER Policy_UUID
        Policy UUID
    .PARAMETER Policy_Version
        Policy version
    .PARAMETER Policy_Name
        Exact policy name
    .OUTPUTS
        PSObject
    .EXAMPLE
    Get-SepCloudPolicyDetails -name "My Policy"
    Gathers detailed information on the latest version SEP Cloud policy named "My Policy"
    .EXAMPLE
    Get-SepCloudPolicyDetails -name "My Policy" -version 1
    Gathers detailed information on the version 1 of SEP Cloud policy named "My Policy"
    .EXAMPLE
    "My Policy","My Policy 2" | Get-SepCloudPolicyDetails
    Piped strings are used as policy name to gather detailed information on the latest version SEP Cloud policy named "My Policy" & "My Policy 2"
    #>



    param (
        # Policy UUID
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        [Alias("policy_uid")]
        $Policy_UUID,

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

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

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $obj_policies = (Get-SepCloudPolices).policies
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        # Get list of all SEP Cloud policies and get only the one with the correct name
        $obj_policy = ($obj_policies | Where-Object { $_.name -eq "$Policy_Name" })

        if ($null -eq $Policy_version ) {
            $obj_policy = ($obj_policy | Sort-Object -Property policy_version -Descending | Select-Object -First 1)
        }

        $Policy_Version = ($obj_policy).policy_version
        $Policy_UUID = ($obj_policy).policy_uid
        $URI = 'https://' + $BaseURL + "/v1/policies/$Policy_UUID/versions/$Policy_Version"

        $Resp = Invoke-RestMethod -Method GET -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing

        return $Resp
    }
}
#EndRegion '.\Public\Get-SepCloudPolicyDetails.ps1' 84
#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
    #>


    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $URI_Tokens = 'https://' + $BaseURL + "/v1/policies/target-rules"
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        try {
            $Response = Invoke-RestMethod -Method GET -Uri $URI_Tokens -Headers $Headers -Body $Body -UseBasicParsing
        }

        catch {
            $StatusCode = $_
            $StatusCode
        }

        $Response
    }
}
#EndRegion '.\Public\Get-SepCloudTargetRules.ps1' 43
#Region '.\Public\Get-SepTaxiiDiscovery.ps1' 0
function Get-SepTaxiiDiscovery {
    <#
    .SYNOPSIS
        Provide related file for a given file
    .DESCRIPTION
        Provide related file for a given file
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .PARAMETER file_sha256
        Specify one or many sha256 hash
    .EXAMPLE
        PS C:\PSSymantecCloud> "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d" | Get-SepTaxiiDiscovery
 
        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')]
    # [string[]]
    # $file_sha256
    # )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        # $Token = (Get-SEPCloudToken).Token_Bearer
        $SepCloudCreds = (Get-ConfigurationPath).SepCloudCreds
        $OAuth = "Basic " + (Import-Clixml -Path $SepCloudCreds)

        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Body          = $Body
            Authorization = $OAuth
        }
    }

    process {
        $params = @{
            Uri             = 'https://' + $BaseURL + "/v1/threat-intel/taxii11/discovery/"
            Method          = 'GET'
            Headers         = $Headers
            UseBasicParsing = $true
        }
        $Response = Invoke-RestMethod @params
        $
        return $Response
    }
}
#EndRegion '.\Public\Get-SepTaxiiDiscovery.ps1' 60
#Region '.\Public\Get-SepThreatIntelCveProtection.ps1' 0
function Get-SepThreatIntelCveProtection {
    <#
    .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
    .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 {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        # HTTP body content containing all the queries
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_cve = @()
        foreach ($c in $cve) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/protection/cve/" + $c
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_cve += $Response
        }
        return $array_cve
    }
}
#EndRegion '.\Public\Get-SepThreatIntelCveProtection.ps1' 58
#Region '.\Public\Get-SepThreatIntelFileInsight.ps1' 0
function Get-SepThreatIntelFileInsight {
    <#
    .SYNOPSIS
        Provide file insight enrichments for a given file
    .DESCRIPTION
        Provide file insight enrichments for a given file
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .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')]
        [string[]]
        $file_sha256
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_file_sha256 = @()
        foreach ($f in $file_sha256) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/insight/file/" + $f
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_file_sha256 += $Response
        }
        return $array_file_sha256
    }
}
#EndRegion '.\Public\Get-SepThreatIntelFileInsight.ps1' 65
#Region '.\Public\Get-SepThreatIntelFileProcessChain.ps1' 0
function Get-SepThreatIntelFileProcessChain {
    <#
    .SYNOPSIS
        Provide topK process lineage enrichment for the provided file sha256.
    .DESCRIPTION
        Provide topK process lineage enrichment for the provided file sha256.
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .PARAMETER file_sha256
        Specify one or many sha256 hash
    .EXAMPLE
        PS C:\PSSymantecCloud> $ProcessChain = Get-SepThreatIntelFileProcessChain -file_sha256 eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d
 
        file chain
        ---- -----
        eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d {@{parent=}}
 
        PS C:\PSSymantecCloud> $ProcessChain.chain
 
        parent
        ------
        @{parent=; file=18bba9ff311154415404e2fb16f3784e4c82b57ad110092ea5f9b76ed549e7cb; processName=fe392ea0a9f14s4dfeda8d9u0233a6ioq6e47a5n3.exe}
 
        .EXAMPLE
        "eec3f761f7eabe9ed569f39e896be24c9bbb8861b15dbde1b3d539505cd9dd8d" | Get-SepThreatIntelFileProcessChain
    #>


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

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_file_sha256 = @()
        foreach ($f in $file_sha256) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/processchain/file/" + $f
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_file_sha256 += $Response
        }
        return $array_file_sha256
    }
}
#EndRegion '.\Public\Get-SepThreatIntelFileProcessChain.ps1' 69
#Region '.\Public\Get-SepThreatIntelFileProtection.ps1' 0
function Get-SepThreatIntelFileProtection {
    <#
    .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
    .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')]
        [string[]]
        $file_sha256
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_file_sha256 = @()
        foreach ($f in $file_sha256) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/protection/file/" + $f
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_file_sha256 += $Response
        }
        return $array_file_sha256
    }
}
#EndRegion '.\Public\Get-SepThreatIntelFileProtection.ps1' 61
#Region '.\Public\Get-SepThreatIntelFileRelated.ps1' 0
function Get-SepThreatIntelFileRelated {
    <#
    .SYNOPSIS
        Provide related file for a given file
    .DESCRIPTION
        Provide related file for a given file
    .INPUTS
        sha256
    .OUTPUTS
        PSObject
    .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')]
        [string[]]
        $file_sha256
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_file_sha256 = @()
        foreach ($f in $file_sha256) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/related/file/" + $f
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_file_sha256 += $Response
        }
        return $array_file_sha256
    }
}
#EndRegion '.\Public\Get-SepThreatIntelFileRelated.ps1' 60
#Region '.\Public\Get-SepThreatIntelNetworkInsight.ps1' 0
function Get-SepThreatIntelNetworkInsight {
    <#
    .SYNOPSIS
        Provide domain insight enrichments for a given domain
    .DESCRIPTION
        Provide domain insight enrichments for a given domain
    .INPUTS
        domain
    .OUTPUTS
        PSObject
    .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')]
        [string[]]
        $domain
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_domain = @()
        foreach ($d in $domain) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/insight/network/" + $d
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_domain += $Response
        }
        return $array_domain
    }
}
#EndRegion '.\Public\Get-SepThreatIntelNetworkInsight.ps1' 64
#Region '.\Public\Get-SepThreatIntelNetworkProtection.ps1' 0
function Get-SepThreatIntelNetworkProtection {
    <#
    .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
    .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","google.com" | 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')]
        [string[]]
        $network
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
        $Body = @{}
        $Headers = @{
            Host          = $BaseURL
            Accept        = "application/json"
            Authorization = $Token
            Body          = $Body
        }
    }

    process {
        $array_network = @()
        foreach ($n in $network) {
            $params = @{
                Uri             = 'https://' + $BaseURL + "/v1/threat-intel/protection/network/" + $n
                Method          = 'GET'
                Headers         = $Headers
                UseBasicParsing = $true
            }
            $Response = Invoke-RestMethod @params
            $array_network += $Response
        }
        return $array_network
    }
}
#EndRegion '.\Public\Get-SepThreatIntelNetworkProtection.ps1' 59
#Region '.\Public\New-EDRFullDump.ps1' 0
function New-EDRFullDump {

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


    [CmdletBinding()]
    param (
        # ComputerName
        [Parameter(
            ValueFromPipeline = $true
        )]
        [string]
        [Alias("Hostname", "Computer")]
        $ComputerName,

        # description
        [string]
        $Description

    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        # Get Computer UID from ComputerName
        $Device_ID = (Get-SepCloudDevices -Computername $Computername).id
        $URI = 'https://' + $BaseURL + "/v1/commands/endpoint-search/fulldump"

        if ([string]::IsNullOrEmpty($Description)) {
            $message = "$ComputerName - Full Dump request"
            $Description = $message
        }
        $body = @{
            device_id   = $Device_ID
            description = $Description
        }

        $params = @{
            Uri             = $URI
            Method          = 'POST'
            Body            = $body | ConvertTo-Json
            Headers         = @{
                Host           = $BaseURL
                Accept         = "application/json"
                "Content-Type" = "application/json"
                Authorization  = $Token
            }
            UseBasicParsing = $true
        }

        $Resp = Invoke-RestMethod @params

        return $Resp
    }
}
#EndRegion '.\Public\New-EDRFullDump.ps1' 71
#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
    .EXAMPLE
        Start-SepCloudDefinitionUpdate -ComputerName MyComputer
 
        Initiate a definition update request command on a specific computer
    .EXAMPLE
        Start-SepCloudDefinitionUpdate -ComputerName Computer01,Computer02
    .EXAMPLE
        Start-SepCloudDefinitionUpdate -ComputerName (Get-Content -Path .\ComputerList.txt)
 
        Initiate a definition update request command on a list of computers
    .EXAMPLE
        Get-Content -Path .\ComputerList.txt | Start-SepCloudDefinitionUpdate
 
        Initiate a definition update request command on a list of computers
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('Computer', 'Device', 'Hostname', 'Host')]
        [Collections.Generic.List[System.String]]
        $ComputerName
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        #Get list of devices ID from ComputerNames
        $Device_ID_list = New-Object System.Collections.Generic.List[string]
        foreach ($Computer in $ComputerName) {
            $Device_ID = (Get-SepCloudDevices -Computername $Computer).id
            $Device_ID_list += $Device_ID
        }

        $URI = 'https://' + $BaseURL + "/v1/commands/update_content"
        $body = @{
            device_ids = $Device_ID_list
        }

        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        $params = @{
            Uri             = $URI
            Method          = 'POST'
            Body            = $Body | ConvertTo-Json
            Headers         = $Headers
            UseBasicParsing = $true
        }

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

        return $Resp
    }
}
#EndRegion '.\Public\Start-SepCloudDefinitionUpdate.ps1' 78
#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
    .EXAMPLE
        Start-SepCloudFullScan -ComputerName MyComputer
 
        Initiate a full scan command on a specific computer
    .EXAMPLE
        Start-SepCloudFullScan -ComputerName Computer01,Computer02
    .EXAMPLE
        Start-SepCloudFullScan -ComputerName (Get-Content -Path .\ComputerList.txt)
 
        Initiate a quick scan command on a list of computers
    .EXAMPLE
        Get-Content -Path .\ComputerList.txt | Start-SepCloudFullScan
 
        Initiate a quick scan command on a list of computers
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('Computer', 'Device', 'Hostname', 'Host')]
        [Collections.Generic.List[System.String]]
        $ComputerName
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        #Get list of devices ID from ComputerNames
        $Device_ID_list = New-Object System.Collections.Generic.List[string]
        foreach ($Computer in $ComputerName) {
            $Device_ID = (Get-SepCloudDevices -Computername $Computer).id
            $Device_ID_list += $Device_ID
        }

        $URI = 'https://' + $BaseURL + "/v1/commands/scans/full"
        $body = @{
            device_ids = $Device_ID_list
        }

        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        $params = @{
            Uri             = $URI
            Method          = 'POST'
            Body            = $Body | ConvertTo-Json
            Headers         = $Headers
            UseBasicParsing = $true
        }

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

        return $Resp
    }
}
#EndRegion '.\Public\Start-SepCloudFullScan.ps1' 78
#Region '.\Public\Start-SepCloudQuickScan.ps1' 0
function Start-SepCloudQuickScan {
    <#
    .SYNOPSIS
        Initiate a quick scan command on SEP Cloud managed devices
    .DESCRIPTION
        Initiate a quick scan command on SEP Cloud managed devices
    .EXAMPLE
        Start-SepCloudQuickScan -ComputerName MyComputer
 
        Initiate a quick scan command on a specific computer
    .EXAMPLE
        Start-SepCloudQuickScan -ComputerName Computer01,Computer02
    .EXAMPLE
        Start-SepCloudQuickScan -ComputerName (Get-Content -Path .\ComputerList.txt)
 
        Initiate a quick scan command on a list of computers
    .EXAMPLE
        Get-Content -Path .\ComputerList.txt | Start-SepCloudQuickScan
 
        Initiate a quick scan command on a list of computers
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('Computer', 'Device', 'Hostname', 'Host')]
        [Collections.Generic.List[System.String]]
        $ComputerName
    )

    begin {
        # Init
        $BaseURL = (Get-ConfigurationPath).BaseUrl
        $Token = (Get-SEPCloudToken).Token_Bearer
    }

    process {
        #Get list of devices ID from ComputerNames
        $Device_ID_list = New-Object System.Collections.Generic.List[string]
        foreach ($Computer in $ComputerName) {
            $Device_ID = (Get-SepCloudDevices -Computername $Computer).id
            $Device_ID_list += $Device_ID
        }

        $URI = 'https://' + $BaseURL + "/v1/commands/scans/quick"
        $body = @{
            device_ids = $Device_ID_list
        }

        $Headers = @{
            Host           = $BaseURL
            Accept         = "application/json"
            "Content-Type" = "application/json"
            Authorization  = $Token
        }

        $params = @{
            Uri             = $URI
            Method          = 'POST'
            Body            = $Body | ConvertTo-Json
            Headers         = $Headers
            UseBasicParsing = $true
        }

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

        return $Resp
    }
}
#EndRegion '.\Public\Start-SepCloudQuickScan.ps1' 78
#Region '.\Public\Test-SepCloudConnectivity.ps1' 0
function Test-SepCloudConnectivity {
    <#
    .SYNOPSIS
        Test SEP Cloud connectivity
    .DESCRIPTION
        Test SEP Cloud connectivity. returns boolean $true if OK, $false if not
    .INPUTS
        None
    .OUTPUTS
        [boolean] $true or $false
    .EXAMPLE
        Test-SepCloudConnectivity
        Test SEP Cloud connectivity and return $true if OK, $false if not
 
    #>

    param (
    )

    if (Get-SEPCloudToken) {
        Write-Host "Authentication OK"
        return $true
        else {
            Write-Host "Authentication failed - Use Clear-SepCloudAuthentication to clear your API token and try again"
            return $false
        }
    }
}
#EndRegion '.\Public\Test-SepCloudConnectivity.ps1' 28
#Region '.\Public\Update-SepCloudAllowlistPolicy.ps1' 0
function Update-SepCloudAllowlistPolicy {
    <# TODO Update description with -add & sha/name parameters
    .SYNOPSIS
        Updates Symantec Allow List policy using an excel file
    .DESCRIPTION
        Gathers Allow List policy information from an Excel file generated from Export-SepCloudAllowListPolicyToExcel function
        You can manually add or remove lines to the Excel file, and the updated Excel will be used to add or remove new exceptions to the Allow list policy of your choice
    .INPUTS
        [string] Policy_Name
        [string] ExcelFile
        optional [string] Policy_Version
    .OUTPUTS
        [PSCustomObject] Policy
    .PARAMETER Policy_Version
        Optional parameter - Version of the policy to update. By default, latest version selected
    .PARAMETER Policy_Name
        Exact name of the policy to update
    .PARAMETER ExcelFile
        Path fo the Excel file that contains updated information on Allow list to update
        Takes Excel template from Export-SepCloudAllowListPolicyToExcel function
    .PARAMETER Add
        [switch] Add content to the policy. Supports only -sha2 and -name parameters
    .PARAMETER Remove
        [switch] Remove content from the policy. Supports only -sha2 and -name parameters
    .PARAMETER sha2
        [string] sha2 hash to add or remove from the policy
    .PARAMETER name
        [string] name of the file to add or remove from the policy
    .EXAMPLE
        First generate an excel file from the policy you want to update
        Get-SepCloudPolicyDetails -name "Workstations Allow List Policy" | Export-SepCloudAllowListPolicyToExcel -Path .\Data\WorkstationsAllowList.xlsx
        Manualy perform changes to the Excel file (add or remove exceptions)
        Then update the policy to reflect the changes to the cloud
        Update-SepCloudAllowlistPolicy -policy "Workstations Allow List Policy" -ExcelFile .\WorkstationsAllowList.xlsx
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByExcelFileLatestVersion')]
    param (
        # Policy version
        [Parameter(Mandatory = $true, ParameterSetName = 'ByExcelFileVersionSpecific')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByHashVersionSpecific')]
        [string]
        [Alias("Version")]
        $Policy_Version,

        # Exact policy name
        # TODO : test if policy name provided exists
        [Parameter(Mandatory)]
        [Alias("PolicyName")]
        [string]
        $Policy_Name,

        # Excel file to import data from
        # TODO test if excel path provided exists
        [Parameter(Mandatory = $true, ParameterSetName = 'ByExcelFileLatestVersion')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByExcelFileVersionSpecific')]
        [string]
        [Alias("Excel")]
        $excel_path,

        # Add Action to perform
        [Parameter(ParameterSetName = "ByHashVersionSpecific")]
        [Parameter(ParameterSetName = "ByHashLatestVersion")]
        [switch]$Add,

        # Remove Action to perform
        [Parameter(ParameterSetName = "ByHashVersionSpecific")]
        [Parameter(ParameterSetName = "ByHashLatestVersion")]
        [switch]$Remove,

        # Hash to add or remove
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByHashVersionSpecific")]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByHashLatestVersion")]
        [ValidateScript({ # Validate sha2 hash format
                if ($_ -match "^[0-9a-f]{64}$") {
                    $true
                } else {
                    throw "Invalid hash"
                }
            })]
        [ValidateNotNullOrEmpty()]
        [Alias('hash')]
        [string]$sha2,

        # File name to add or remove
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByHashVersionSpecific")]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByHashLatestVersion")]
        [ValidateNotNullOrEmpty()]
        [Alias('name')]
        [string]$file_name
    )

    # init
    $BaseURL = (Get-ConfigurationPath).BaseUrl
    $Token = (Get-SEPCloudToken).Token_Bearer
    # Get list of all versions of the SEP Cloud policy
    $obj_policy = ((Get-SepCloudPolices).policies | Where-Object { $_.name -eq "$Policy_Name" })

    ##################################
    # if parameter excel is provided #
    ##################################
    if ($null -ne $PSBoundParameters['excel_path']) {
        # Verify if a specific version of the policy is requested
        switch ($PSCmdlet.ParameterSetName) {
            "ByExcelFileLatestVersion" {
                # Merge cloud policy with excel file with latest version
                $obj_merged_policy = Merge-SepCloudAllowList -Excel $excel_path -Policy_Name $Policy_Name
                $obj_policy = ($obj_policy | Sort-Object -Property policy_version -Descending | Select-Object -First 1)
            }
            "ByExcelFileVersionSpecific" {
                # Merge cloud policy with excel file with specified version
                $obj_merged_policy = Merge-SepCloudAllowList -Excel $excel_path -Policy_Name $Policy_Name -Policy_Version $Policy_Version
                $obj_policy = $obj_policy | Where-Object {
                    $_.name -eq "$Policy_Name" -and $_.policy_version -eq $Policy_Version
                }
            }
            Default {}
        }

        # Setup API query
        $Body = $obj_merged_policy | Optimize-SepCloudAllowListPolicyObject | ConvertTo-Json -Depth 100
        $Policy_UUID = ($obj_policy).policy_uid
        $Policy_Version = ($obj_policy).policy_version
        $URI = 'https://' + $BaseURL + "/v1/policies/allow-list/$Policy_UUID/versions/$Policy_Version"

        # API query
        $Headers = @{
            Host           = $BaseURL
            "Content-Type" = "application/json"
            Accept         = "application/json"
            Authorization  = $Token
        }
        $Response = Invoke-RestMethod -Method PATCH -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing

        return $Response
    }
    ##########################################
    # If add or remove parameter is provided #
    ##########################################
    if ($PSBoundParameters['Add'] -or $PSBoundParameters['Remove']) {
        # Verify if a specific version of the policy is requested
        if ($null -ne $PSBoundParameters['Policy_Version']) {
            # Get policy information about the specified version
            $obj_policy = $obj_policy | Where-Object {
                $_.name -eq "$Policy_Name" -and $_.policy_version -eq $Policy_Version
            }
        } else {
            # Get policy information about the latest version
            $obj_policy = ($obj_policy | Sort-Object -Property policy_version -Descending | Select-Object -First 1)
        }

        # Initialize structured obj that will be later converted to JSON
        $obj_body = [UpdateAllowlist]::new()
        if ($PSBoundParameters['Add']) {
            # Add new hash to the obj
            $obj_body.add.AddProcessFile(
                $sha2,
                $file_name
            )
        }
        if ($PSBoundParameters['Remove']) {
            # Add new hash to the obj
            $obj_body.remove.AddProcessFile(
                $sha2,
                $file_name
            )
        }

        # Convert $obj_body from a custom class to a PSCustomObject
        $obj_body = $obj_body | ConvertTo-Json -Depth 100 | ConvertFrom-Json -Depth 100

        # Setup API query
        # Running on body Optimize-SepCloudAllowListPolicyObject to remove empty properties before converting to JSON
        $Body = $obj_body | Optimize-SepCloudAllowListPolicyObject | ConvertTo-Json -Depth 100
        $Policy_UUID = ($obj_policy).policy_uid
        $Policy_Version = ($obj_policy).policy_version
        $URI = 'https://' + $BaseURL + "/v1/policies/allow-list/$Policy_UUID/versions/$Policy_Version"

        # API query
        $Headers = @{
            Host           = $BaseURL
            "Content-Type" = "application/json"
            Accept         = "application/json"
            Authorization  = $Token
        }
        $Response = Invoke-RestMethod -Method PATCH -Uri $URI -Headers $Headers -Body $Body -UseBasicParsing

        return $Response
    } else {
        throw "ERROR - No action provided. Use -add or -remove or provide an excel file"
    }
}
#EndRegion '.\Public\Update-SepCloudAllowlistPolicy.ps1' 192