PSSymantecSEPM.psm1
#Region '.\Classes\Exceptions-Policy.ps1' 0 # Imported PSSymantecCloud module Classes # SEPM uses API v2 for exception policy # TODO confirm if same classes can be used for API v2 on SEPM # 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' 311 #Region '.\Private\Get-RestError.ps1' 0 function Get-RestError($Err) { #Allows backwards compatibility with older versions of Powershell if ($PSVersionTable.PSVersion.Major -lt 6) { if ($Err.Exception.Response) { $Reader = New-Object System.IO.StreamReader($Err.Exception.Response.GetResponseStream()) $Reader.BaseStream.Position = 0 $Reader.DiscardBufferedData() $ResponseBody = $Reader.ReadToEnd() if ($ResponseBody.StartsWith('{')) { $ResponseBody = $ResponseBody | ConvertFrom-Json } return $ResponseBody } else { return $Err.ErrorDetails.Message } } } #EndRegion '.\Private\Get-RestError.ps1' 18 #Region '.\Private\Get-RestErrorDetails.ps1' 0 Function Get-RestErrorDetails { <# .SYNOPSIS Provides basic Rest Error Information from the API call in the event that the REST API call could not be performed successfully. .EXAMPLE try{ Invoke-RestMethod -Method GET -URI $URI -headers $headers }catch{ Get-RestErrorDetails } #> "An error was found with this command. Please review the resultant error for details." "Error Code:" + $_.Exception.Response.StatusCode.Value__ + " " + $_.Exception.Response.ReasonPhrase "Error Message: " + $_ } #EndRegion '.\Private\Get-RestErrorDetails.ps1' 17 #Region '.\Private\Import-SepmConfiguration.ps1' 0 function Import-SepmConfiguration { <# .SYNOPSIS Loads in the default configuration values, and then updates the individual properties with values that may exist in a file. .DESCRIPTION Loads in the default configuration values, and then updates the individual properties with values that may exist in a file. .PARAMETER Path The file that may or may not exist with a serialized version of the configuration values for this module. .OUTPUTS PSCustomObject .NOTES Internal helper method. No side-effects. .EXAMPLE Import-SepmConfiguration -Path 'c:\foo\config.json' Creates a new default config object and updates its values with any that are found within a deserialized object from the content in $Path. The configuration object is then returned. #> [CmdletBinding()] param( [string] $Path ) # Create a configuration object with all the default values. We can then update the values # with any that we find on disk. $config = [PSCustomObject]@{ 'ServerAddress' = '' 'port' = '8446' 'domain' = '' } $jsonObject = Read-SepmConfiguration -Path $Path Get-Member -InputObject $config -MemberType NoteProperty | ForEach-Object { $name = $_.Name $type = $config.$name.GetType().Name $config.$name = Resolve-PropertyValue -InputObject $jsonObject -Name $name -Type $type -DefaultValue $config.$name } return $config } #EndRegion '.\Private\Import-SepmConfiguration.ps1' 53 #Region '.\Private\Invoke-ABRestMethod.ps1' 0 function Invoke-ABRestMethod { <# .SYNOPSIS Invokes a REST method with a PS version-appropriate method .DESCRIPTION Invokes a REST method with a PS version-appropriate method Handles the differences between PS versions 5 and 6 for certificate validation skipping Helper function for Invoke-ABRestMethod .PARAMETER params A hashtable of parameters to pass to the Invoke-RestMethod cmdlet .EXAMPLE $params = @{ Method = 'POST' Uri = $URI headers = $headers } Invoke-ABRestMethod -params $params #> param ( # Hashtable of parameters [Parameter( Mandatory = $true )] [hashtable] $params ) switch ($PSVersionTable.PSVersion.Major) { { $_ -ge 6 } { try { if ($script:accessToken.skipCert -eq $true) { $resp = Invoke-RestMethod @params -SkipCertificateCheck } else { $resp = Invoke-RestMethod @params } } catch { Write-Warning -Message "Error: $_" return "Error: $_" } } default { try { if ($script:accessToken.skipCert -eq $true) { Skip-Cert $resp = Invoke-RestMethod @params } else { $resp = Invoke-RestMethod @params } } catch { Write-Warning -Message "Error: $_" return "Error: $_" } } } # return the response return $resp } #EndRegion '.\Private\Invoke-ABRestMethod.ps1' 61 #Region '.\Private\Read-SepmConfiguration.ps1' 0 function Read-SepmConfiguration { <# .SYNOPSIS Loads in the default configuration values and returns the deserialized object. .DESCRIPTION Loads in the default configuration values and returns the deserialized object. .PARAMETER Path The file that may or may not exist with a serialized version of the configuration values for this module. .OUTPUTS PSCustomObject .NOTES Internal helper method. No side-effects. .EXAMPLE Read-SepmConfiguration -Path 'c:\foo\config.json' Returns back an object with the deserialized object contained in the specified file, if it exists and is valid. #> [CmdletBinding()] param( [string] $Path ) $content = Get-Content -Path $Path -Encoding UTF8 -ErrorAction Ignore if (-not [String]::IsNullOrEmpty($content)) { try { return ($content | ConvertFrom-Json) } catch { $message = 'The configuration file for this module is in an invalid state. Use Reset-SEPMConfiguration to recover.' Write-Warning -Message $message } } return [PSCustomObject]@{} } #EndRegion '.\Private\Read-SepmConfiguration.ps1' 43 #Region '.\Private\Resolve-PropertyValue.ps1' 0 function Resolve-PropertyValue { <# .SYNOPSIS Returns the requested property from the provided object, if it exists and is a valid value. Otherwise, returns the default value. .DESCRIPTION Returns the requested property from the provided object, if it exists and is a valid value. Otherwise, returns the default value. .PARAMETER InputObject The object to check the value of the requested property. .PARAMETER Name The name of the property on InputObject whose value is desired. .PARAMETER Type The type of the value stored in the Name property on InputObject. Used to validate that the property has a valid value. .PARAMETER DefaultValue The value to return if Name doesn't exist on InputObject or is of an invalid type. .EXAMPLE Resolve-PropertyValue -InputObject $config -Name defaultOwnerName -Type String -DefaultValue $null Checks $config to see if it has a property named "defaultOwnerName". If it does, and it's a string, returns that value, otherwise, returns $null (the DefaultValue). #> [CmdletBinding()] param( [PSCustomObject] $InputObject, [Parameter(Mandatory)] [string] $Name, [Parameter(Mandatory)] [ValidateSet('String', 'Boolean', 'Int32', 'Int64')] [String] $Type, $DefaultValue ) if ($null -eq $InputObject) { return $DefaultValue } $typeType = [String] if ($Type -eq 'Boolean') { $typeType = [Boolean] } if ($Type -eq 'Int32') { $typeType = [Int32] } if ($Type -eq 'Int64') { $typeType = [Int64] } $numberEquivalents = @('Int32', 'Int64', 'long', 'int') if (Test-PropertyExists -InputObject $InputObject -Name $Name) { if (($InputObject.$Name -is $typeType) -or (($Type -in $numberEquivalents) -and ($InputObject.$Name.GetType().Name -in $numberEquivalents))) { return $InputObject.$Name } else { return $DefaultValue } } else { return $DefaultValue } } #EndRegion '.\Private\Resolve-PropertyValue.ps1' 65 #Region '.\Private\Save-SepmConfiguration.ps1' 0 function Save-SepmConfiguration { <# .SYNOPSIS Serializes the provided settings object to disk as a JSON file. .DESCRIPTION Serializes the provided settings object to disk as a JSON file. .PARAMETER Configuration The configuration object to persist to disk. .PARAMETER Path The path to the file on disk that Configuration should be persisted to. .NOTES Internal helper method. .EXAMPLE Save-SepmConfiguration -Configuration $config -Path 'c:\foo\config.json' Serializes $config as a JSON object to 'c:\foo\config.json' #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [PSCustomObject] $Configuration, [Parameter(Mandatory)] [string] $Path ) if (-not $PSCmdlet.ShouldProcess('Sepm Configuration', 'Save')) { return } $null = New-Item -Path $Path -Force ConvertTo-Json -InputObject $Configuration | Set-Content -Path $Path -Force -ErrorAction SilentlyContinue -ErrorVariable ev if (($null -ne $ev) -and ($ev.Count -gt 0)) { $message = "Failed to persist these updated settings to disk. They will remain for this PowerShell session only." Write-Warning -Message $message } } #EndRegion '.\Private\Save-SepmConfiguration.ps1' 45 #Region '.\Private\Skip-Cert.ps1' 0 function Skip-Cert { <# .SYNOPSIS This function allows skipping the SSL/TLS Secure channel check in the event that there is not a valid certificate available .DESCRIPTION This function allows skipping the SSL/TLS Secure channel check in the event that there is not a valid certificate available .NOTES Required for self-signed certificates skipping with Windows Powershell 5.1 and below This function is used internally by the module and should not be called directly .PARAMETER None .EXAMPLE Skip-Cert .OUTPUTS None #> if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) { $certCallback = @" using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; public class ServerCertificateValidationCallback { public static void Ignore() { if(ServicePointManager.ServerCertificateValidationCallback ==null) { ServicePointManager.ServerCertificateValidationCallback += delegate ( Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors ) { return true; }; } } } "@ Add-Type $certCallback } [ServerCertificateValidationCallback]::Ignore() } #EndRegion '.\Private\Skip-Cert.ps1' 48 #Region '.\Private\Test-CertificateSelfSigned.ps1' 0 function Test-CertificateSelfSigned { <# .SYNOPSIS This function tests a webserver to see if it is using a self-signed certificate .DESCRIPTION This function tests a webserver to see if it is using a self-signed certificate If so, sets the SkipCert variable to $true to continue with the connection .PARAMETER URI The URI of the webserver to test .INPUTS System.String .OUTPUTS None .EXAMPLE Test-CertificateSelfSigned -URI https://www.example.com Tests the webserver at https://www.example.com to see if it is using a self-signed certificate #> [CmdletBinding()] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [string] $URI ) try { # Test the certificate Invoke-WebRequest $URI } catch { # Get SEPM server name from URI $uriObject = New-Object System.Uri($URI) $domain = $uriObject.Host # Get the error message $message = "SSL Certificate test failed. The certificate for $domain is self-signed." Write-Warning -Message $message # Prompt the user to continue $Response = Read-Host -Prompt 'Press enter to ignore this and continue without SSL/TLS secure channel' if ($Response -eq "") { if ($PSVersionTable.PSVersion.Major -lt 6) { Skip-Cert } $script:SkipCert = $true } } } #EndRegion '.\Private\Test-CertificateSelfSigned.ps1' 53 #Region '.\Private\Test-PropertyExists.ps1' 0 function Test-PropertyExists { <# .SYNOPSIS Determines if an object contains a property with a specified name. .DESCRIPTION Determines if an object contains a property with a specified name. This is essentially using Get-Member to verify that a property exists, but additionally adds a check to ensure that InputObject isn't null. .PARAMETER InputObject The object to check to see if it has a property named Name. .PARAMETER Name The name of the property on InputObject that is being tested for. .EXAMPLE Test-PropertyExists -InputObject $listing -Name 'title' Returns $true if $listing is non-null and has a property named 'title'. Returns $false otherwise. .NOTES Internal-only helper method. #> [CmdletBinding()] [OutputType([bool])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Exists isn't a noun and isn't violating the intention of this rule.")] param( [Parameter(Mandatory)] [AllowNull()] $InputObject, [Parameter(Mandatory)] [String] $Name ) return (($null -ne $InputObject) -and ($null -ne (Get-Member -InputObject $InputObject -Name $Name -MemberType Properties))) } #EndRegion '.\Private\Test-PropertyExists.ps1' 42 #Region '.\Private\Test-SEPMAccessToken.ps1' 0 function Test-SEPMAccessToken { <# .SYNOPSIS Test if the access token is still valid .DESCRIPTION Test if the access token is still valid. If no token is passed, will test the cached token. Returns $true if the token is still valid, $false otherwise .PARAMETER TokenInfo The token to test .OUTPUTS System.Boolean .NOTE Internal helper method. This function is used internally by the module and should not be called directly. #> param ( [Alias('AccessToken', 'Token')] [PSCustomObject]$TokenInfo ) # If no paramater is passed, test the cached token if ($null -eq $TokenInfo) { # if token in memory if (-not [string]::IsNullOrEmpty($script:accessToken.token) ) { # if token still valid if ($script:accessToken.tokenExpiration -gt (Get-Date)) { return $true } } } # Check if the access token has expired if ($TokenInfo.tokenExpiration -gt (Get-Date)) { return $true } # If we get here, no valid token was found return $false } #EndRegion '.\Private\Test-SEPMAccessToken.ps1' 44 #Region '.\Public\Add-SEPMFileFingerprintList.ps1' 0 function Add-SEPMFileFingerprintList { <# .SYNOPSIS Adds a blacklist as a file fingerprint list .DESCRIPTION Adds a blacklist as a file fingerprint list .PARAMETER name The name of the blacklist to be added .PARAMETER domainId The domain id of the domain to add the blacklist to Only takes the domain id. Can be found using Get-SEPMDomain .PARAMETER HashType The type of hash to use for the blacklist Valid values are SHA256 and MD5 .PARAMETER description The description of the blacklist .PARAMETER hashlist The hash list to add to the blacklist Can be generated using Get-FileHash or takes a string array of hashes .EXAMPLE $DomainId = Get-SEPMDomain | Where-Object { $_.name -eq "Default" } $HashList = ls -file C:\Users\$env:USERNAME\Downloads\*.exe | Get-FileHash -algorithm SHA256 Add-SEPMFileFingerprintList -name "My Blacklist" -domainId $domainId -HashType "SHA256" -description "My Blacklist" -hashlist $hashlist.hash Gets the domain id for the default domain Create a hash list of all the files in the downloads folder of the currently logged in user Adds the hash list as a blacklist to the default domain #> [CmdletBinding()] param ( [Parameter()] [string]$name, [Parameter()] [string]$domainId, [Parameter()] [ValidateSet('SHA256', 'MD5')] [string]$HashType, [Parameter()] [string]$description, [Parameter()] $hashlist ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { $URI = $script:BaseURLv1 + "/policy-objects/fingerprints" # Construct the body & required fields $body = @{ name = $name domainId = $domainId hashType = $HashType description = $description data = $hashlist } $params = @{ Method = 'POST' Uri = $URI headers = $headers Body = $body | ConvertTo-Json ContentType = 'application/json' } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Add-SEPMFileFingerprintList.ps1' 86 #Region '.\Public\Clear-SepmAuthentication.ps1' 0 function Clear-SEPMAuthentication { <# .SYNOPSIS Clears out any API token from memory, as well as from local file storage. .DESCRIPTION Clears out any API token from memory, as well as from local file storage. .EXAMPLE Clear-SEPMAuthentication Clears out any API token from memory, as well as from local file storage. .NOTES This command will not clear your configuration settings. Please use Reset-SEPMConfiguration to accomplish that. #> $script:Credential = $null $script:accessToken = $null Remove-Item -Path $script:credentialsFilePath -Force -ErrorAction SilentlyContinue -ErrorVariable ev Remove-Item -Path $script:accessTokenFilePath -Force -ErrorAction SilentlyContinue -ErrorVariable ev if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) { $message = "Experienced a problem trying to remove the file that persists the Access Token [$script:credentialsFilePath]." Write-Warning -Message $message } $message = "This has not cleared your configuration settings. Call Reset-SEPMConfiguration to accomplish that." Write-Verbose -Message $message } #EndRegion '.\Public\Clear-SepmAuthentication.ps1' 35 #Region '.\Public\Confirm-SEPMEventInfo.ps1' 0 function Confirm-SEPMEventInfo { <# # TODO add examples once finished .SYNOPSIS Post Acknowledgement For Notification .DESCRIPTION Acknowledges a specified event for a given event ID. A system administrator account is required for this REST API. .EXAMPLE PS C:\PSSymantecSEPM> $SEPMEvents = Confirm-SEPMEventInfo -eventID 30D8A67F0A6606220DEB5989DC3FAC50 #> [CmdletBinding()] param ( [Parameter( Mandatory = $true )] [string] $EventID ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/events/acknowledge/$eventID" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } } #EndRegion '.\Public\Confirm-SEPMEventInfo.ps1' 47 #Region '.\Public\ConvertTo-FlatObject.ps1' 0 # From https://github.com/EvotecIT/PSSharedGoods/tree/master/Public/Converts Function ConvertTo-FlatObject { <# .SYNOPSIS Flattends a nested object into a single level object. .DESCRIPTION Flattends a nested object into a single level object. .PARAMETER Objects The object (or objects) to be flatten. .PARAMETER Separator The separator used between the recursive property names .PARAMETER Base The first index name of an embedded array: - 1, arrays will be 1 based: <Parent>.1, <Parent>.2, <Parent>.3, … - 0, arrays will be 0 based: <Parent>.0, <Parent>.1, <Parent>.2, … - "", the first item in an array will be unnamed and than followed with 1: <Parent>, <Parent>.1, <Parent>.2, … .PARAMETER Depth The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop. .PARAMETER Uncut The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop. .PARAMETER ExcludeProperty The propertys to be excluded from the output. .EXAMPLE $Object3 = [PSCustomObject] @{ "Name" = "Przemyslaw Klys" "Age" = "30" "Address" = @{ "Street" = "Kwiatowa" "City" = "Warszawa" "Country" = [ordered] @{ "Name" = "Poland" } List = @( [PSCustomObject] @{ "Name" = "Adam Klys" "Age" = "32" } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = "33" } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = 30 } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = $null } ) } ListTest = @( [PSCustomObject] @{ "Name" = "Sława Klys" "Age" = "33" } ) } $Object3 | ConvertTo-FlatObject .NOTES Based on https://powersnippets.com/convertto-flatobject/ #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeLine)][Object[]]$Objects, [String]$Separator = ".", [ValidateSet("", 0, 1)]$Base = 1, [int]$Depth = 5, [string[]] $ExcludeProperty, [Parameter(DontShow)][String[]]$Path, [Parameter(DontShow)][System.Collections.IDictionary] $OutputObject ) Begin { $InputObjects = [System.Collections.Generic.List[Object]]::new() } Process { foreach ($O in $Objects) { if ($null -ne $O) { $InputObjects.Add($O) } } } End { If ($PSBoundParameters.ContainsKey("OutputObject")) { $Object = $InputObjects[0] $Iterate = [ordered] @{} if ($null -eq $Object) { #Write-Verbose -Message "ConvertTo-FlatObject - Object is null" } elseif ($Object.GetType().Name -in 'String', 'DateTime', 'TimeSpan', 'Version', 'Enum') { $Object = $Object.ToString() } elseif ($Depth) { $Depth-- If ($Object -is [System.Collections.IDictionary]) { $Iterate = $Object } elseif ($Object -is [Array] -or $Object -is [System.Collections.IEnumerable]) { $i = $Base foreach ($Item in $Object.GetEnumerator()) { $NewObject = [ordered] @{} If ($Item -is [System.Collections.IDictionary]) { foreach ($Key in $Item.Keys) { if ($Key -notin $ExcludeProperty) { $NewObject[$Key] = $Item[$Key] } } } elseif ($Item -isnot [Array] -and $Item -isnot [System.Collections.IEnumerable]) { foreach ($Prop in $Item.PSObject.Properties) { if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) { $NewObject["$($Prop.Name)"] = $Item.$($Prop.Name) } } } else { $NewObject = $Item } $Iterate["$i"] = $NewObject $i += 1 } } else { foreach ($Prop in $Object.PSObject.Properties) { if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) { $Iterate["$($Prop.Name)"] = $Object.$($Prop.Name) } } } } If ($Iterate.Keys.Count) { foreach ($Key in $Iterate.Keys) { if ($Key -notin $ExcludeProperty) { ConvertTo-FlatObject -Objects @(, $Iterate["$Key"]) -Separator $Separator -Base $Base -Depth $Depth -Path ($Path + $Key) -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty } } } else { $Property = $Path -Join $Separator if ($Property) { # We only care if property is not empty if ($Object -is [System.Collections.IDictionary] -and $Object.Keys.Count -eq 0) { $OutputObject[$Property] = $null } else { $OutputObject[$Property] = $Object } } } } elseif ($InputObjects.Count -gt 0) { foreach ($ItemObject in $InputObjects) { $OutputObject = [ordered]@{} ConvertTo-FlatObject -Objects @(, $ItemObject) -Separator $Separator -Base $Base -Depth $Depth -Path $Path -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty [PSCustomObject] $OutputObject } } } } #EndRegion '.\Public\ConvertTo-FlatObject.ps1' 162 #Region '.\Public\Get-SEPClientDefVersions.ps1' 0 function Get-SEPClientDefVersions { <# .SYNOPSIS Gets a list of clients for a group by content version. .DESCRIPTION Gets a list of clients for a group by content version. .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPClientDefVersions version clientsCount ------- ------------ 2023-09-04 rev. 002 15 2023-09-03 rev. 002 4 2023-09-01 rev. 008 2 2023-08-31 rev. 021 2 2023-08-31 rev. 002 1 2023-08-29 rev. 003 1 Gets a list of clients grouped by content version. #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/stats/client/content" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPClientDefVersions.ps1' 58 #Region '.\Public\Get-SEPClientInfectedStatus.ps1' 0 function Get-SEPClientInfectedStatus { <# .SYNOPSIS Gets SEP Clients with Infected or Clean status .DESCRIPTION Gets SEP Clients with Infected or Clean status NOTES : Clean status is just Infected = 0 .INPUTS None .OUTPUTS List of SEP Clients with Infected status .PARAMETER Clean If specified, returns SEP Clients with Clean status .EXAMPLE Get-SEPClientInfectedStatus Gets computer details for all computers in the domain .EXAMPLE Get-SEPClientInfectedStatus -Clean Gets computer details for all computers in the domain that are not infected #> [CmdletBinding()] param ( [Parameter()] [switch] $Clean ) process { if ($clean) { $non_infected = Get-SEPComputers | Where-Object { $_.infected -ne 1 } return $non_infected } else { $infected = Get-SEPComputers | Where-Object { $_.infected -eq 1 } return $Infected } } } #EndRegion '.\Public\Get-SEPClientInfectedStatus.ps1' 43 #Region '.\Public\Get-SEPClientStatus.ps1' 0 function Get-SEPClientStatus { <# .SYNOPSIS Gets a list and count of the online and offline clients. .DESCRIPTION Gets a list and count of the online and offline clients. .EXAMPLE C:\PSSymantecSEPM> Get-SEPClientStatus lastUpdated clientCountStatsList ----------- -------------------- 1693910248728 {@{status=ONLINE; clientsCount=212}, @{status=OFFLINE; clientsCount=48}} Gets a list and count of the online and offline clients. #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/stats/client/onlinestatus" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPClientStatus.ps1' 53 #Region '.\Public\Get-SEPClientVersion.ps1' 0 function Get-SEPClientVersion { <# .SYNOPSIS Gets a list and count of clients by client product version. .DESCRIPTION Gets a list and count of clients by client product version. .EXAMPLE PS C:\PSSymantecSEPM> $SEPversions = Get-SEPClientVersion PS C:\PSSymantecSEPM> $SEPversions.clientVersionList version clientsCount formattedVersion ------- ------------ ---------------- 11.0.6000.550 1 11.0.6 (11.0 MR6) build 550 12.1.2015.2015 1 12.1.2 (12.1 RU2) build 2015 12.1.6867.6400 1 12.1.6 (12.1 RU6 MP4) build 6867 12.1.7004.6500 3 12.1.6 (12.1 RU6 MP5) build 7004 12.1.7454.7000 177 12.1.7 (12.1 RU7) build 7454 14.0.3752.1000 36 14.0.3 (14.0 RU3 MP7) build 1000 14.2.1031.0100 21 14.2.1 (14.2 RU1) build 0100 14.2.3335.1000 3 14.2.3 (14.2 RU3 MP3) build 1000 14.3.510.0000 12 14.3 (14.3) build 0000 14.3.558.0000 5 14.3 (14.3) build 0000 Gets a list and count of clients by client product version. #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/stats/client/version" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPClientVersion.ps1' 63 #Region '.\Public\Get-SEPComputers.ps1' 0 function Get-SEPComputers { <# .SYNOPSIS Gets the information about the computers in a specified domain .DESCRIPTION Gets the information about the computers in a specified domain. either from computer names or group names .PARAMETER ComputerName Specifies the name of the computer for which you want to get the information. Supports wildcards .PARAMETER GroupName Specifies the group full path name for which you want to get the information. Supports wildcards .PARAMETER IncludeSubGroups Specifies whether to include subgroups when querying by group name .EXAMPLE Get-SEPComputers Gets computer details for all computers in the domain .EXAMPLE "MyComputer1","MyComputer2" | Get-SEPComputers Gets computer details for the specified computer MyComputer via pipeline .EXAMPLE Get-SEPComputers -ComputerName "MyComputer*" Gets computer details for all computer names starting by MyComputer .EXAMPLE Get-SEPComputers -GroupName "My Company\EMEA\Workstations" Gets computer details for all computers in the specified group MyGroup .EXAMPLE Get-SEPComputers -GroupName "My Company\EMEA\Workstations" -IncludeSubGroups Gets computer details for all computers in the specified group MyGroup and its subgroups #> [CmdletBinding( DefaultParameterSetName = 'ComputerName' )] Param ( # ComputerName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ComputerName' )] [Alias("Hostname", "DeviceName", "Device", "Computer")] [String] $ComputerName, # group name [Parameter( ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GroupName' )] [Alias("Group")] [String] $GroupName, # switch parameter to include subgroups [Parameter( ParameterSetName = 'GroupName' )] [switch] $IncludeSubGroups ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # Using computer name API call if ($ComputerName) { $allResults = @() $URI = $script:BaseURLv1 + "/computers" # URI query strings $QueryStrings = @{ sort = "COMPUTER_NAME" pageIndex = 1 pageSize = 100 computerName = $ComputerName } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() do { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response $allResults += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } until ($resp.lastPage -eq $true) # return the response return $allResults } # Using computer name API call then filtering elseif ($GroupName) { $allResults = @() $URI = $script:BaseURLv1 + "/computers" # URI query strings $QueryStrings = @{ sort = "COMPUTER_NAME" pageIndex = 1 pageSize = 100 computerName = $ComputerName # empty string value to ensure the URI is constructed correctly & query all computers } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() do { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response $allResults += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } until ($resp.lastPage -eq $true) # Filtering if ($IncludeSubGroups) { $allResults = $allResults | Where-Object { $_.group.name -like "$GroupName*" } } else { $allResults = $allResults | Where-Object { $_.group.name -eq $GroupName } } # return the response return $allResults } # No parameters else { $allResults = @() $URI = $script:BaseURLv1 + "/computers" # URI query strings $QueryStrings = @{ sort = "COMPUTER_NAME" pageIndex = 1 pageSize = 100 } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() do { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response $allResults += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } until ($resp.lastPage -eq $true) # return the response return $allResults } } } #EndRegion '.\Public\Get-SEPComputers.ps1' 235 #Region '.\Public\Get-SEPFileDetails.ps1' 0 function Get-SEPFileDetails { <# .SYNOPSIS Gets the details of a binary file, such as the checksum and the file size .DESCRIPTION Gets the details of a binary file, such as the checksum and the file size .PARAMETER FileID The ID of the file to get the details of Is a required parameter Can be found in the command ID of the response from Send-SEPMCommandGetFile .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPFileDetails -FileID 12345678901234567890123456789 id fileSize checksum -- -------- -------- CD02BC8E0A6606D53533F2428BB86D4E 1071101 4BE0BB3B57044CAD186FB59C2B7A13BB #> [CmdletBinding()] param ( [Parameter()] [string] $FileID ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/command-queue/file/$FileID/details" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{ file_id = $FileID } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPFileDetails.ps1' 64 #Region '.\Public\Get-SEPGUPList.ps1' 0 function Get-SEPGUPList { <# .SYNOPSIS Gets a list of group update providers .DESCRIPTION Gets a list of SEP clients acting as group update providers .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPGUPList Gets a list of GUPs clients .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPGUPList | Select-Object Computername, AgentVersion, IpAddress, port computerName agentVersion ipAddress port ------------ ------------ --------- ---- Server01 12.1.7454.7000 10.0.0.150 2967 Server02 14.3.558.0000 10.1.0.150 2967 Workstation01 12.1.7454.7000 192.168.0.1 2967 Workstation02 14.3.558.0000 192.168.1.1 2967 Gets a list of GUPs clients with specific properties #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/gup/status" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPGUPList.ps1' 60 #Region '.\Public\Get-SEPMAccessToken.ps1' 0 function Get-SEPMAccessToken { <# .SYNOPSIS Retrieves the API token for use in the rest of the module. .DESCRIPTION Retrieves the API token for use in the rest of the module. First will try to use the one that may have been provided as a parameter. If not provided, then will try to use the one already cached in memory. If still not found, will look to see if there is a file with the API token stored on disk Finally, if there is still no available token : - check if the SEPM server name is configured - check if the credentials are configured or stored on disk - query one from the SEPM server - store it in memory and on disk - return the token .PARAMETER AccessToken If provided, this will be returned instead of using the cached/configured value .OUTPUTS System.String #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [PSCustomObject] $AccessToken ) # First will try to use the one that may have been provided as a parameter. if (-not [String]::IsNullOrEmpty($AccessToken.token)) { if (Test-SEPMAccessToken -Token $AccessToken) { $script:accessToken = $AccessToken return $AccessToken } } # If not provided, then will try to use the one already cached in memory. if (-not [String]::IsNullOrEmpty($script:accessToken)) { if (Test-SEPMAccessToken -Token $script:accessToken) { return $script:accessToken } } # If still not found, will look to see if there is a file with the API token stored in the disk if (Test-Path $script:accessTokenFilePath) { $AccessToken = Import-Clixml -Path $script:accessTokenFilePath -ErrorAction Ignore if (Test-SEPMAccessToken -Token $AccessToken) { $script:accessToken = $AccessToken return $script:accessToken } } # Finally, if there is still no available token, query one from the SEPM server. # Then caches the token in memory and stores it in a file on disk as a SecureString if ($null -eq $script:configuration.ServerAddress) { $message = "SEPM Server name not found. Provide server name :" Write-Warning -Message $message $ServerAddress = Read-Host -Prompt $message Set-SepmConfiguration -ServerAddress $ServerAddress } # Look for credentials stored in the disk if (Test-Path $script:credentialsFilePath) { $script:Credential = Import-Clixml -Path $script:credentialsFilePath -ErrorAction Ignore } if ($null -eq $script:Credential) { $message = "Credentials not found. Provide credentials :" Write-Warning -Message $message Set-SEPMAuthentication -credential (Get-Credential) } # Test the certificate of the SEPM server $URI_Authenticate = $script:BaseURLv1 + '/identity/authenticate' Test-CertificateSelfSigned -URI $URI_Authenticate # Construct the request $body = @{ "username" = $script:Credential.UserName "password" = ([System.Net.NetworkCredential]::new("", $script:Credential.Password).Password) "appName" = "PSSymantecSEPM PowerShell Module" "domain" = $script:configuration.domain } $Params = @{ Method = 'POST' Uri = $URI_Authenticate ContentType = "application/json" Body = ($body | ConvertTo-Json) } # Invoke the request and SkipCert if needed $Response = Invoke-ABRestMethod -params $Params # Sort the response $CachedToken = [PSCustomObject]@{ token = $response.token tokenExpiration = (Get-Date).AddSeconds($Response.tokenExpiration) SkipCert = $script:SkipCert } # Caches the token in memory $script:accessToken = $CachedToken # Stores it in a file on disk as a SecureString if (-not (Test-Path ($Script:accessTokenFilePath | Split-Path))) { New-Item -ItemType Directory -Path ($Script:accessTokenFilePath | Split-Path) -Force | Out-Null } $script:accessToken | Export-Clixml -Path $script:accessTokenFilePath -Force # return the token return $script:accessToken } #EndRegion '.\Public\Get-SEPMAccessToken.ps1' 115 #Region '.\Public\Get-SEPMAdmins.ps1' 0 Function Get-SEPMAdmins { <# .SYNOPSIS Displays a list of admins in the Symantec Database .DESCRIPTION Gets the list of administrators for a particular domain. The Git repo for this module can be found here: https://github.com/Douda/PSSymantecSEPM .PARAMETER AdminName Displays only a specific user from the Admin List .EXAMPLE Get-SEPMAdmins .EXAMPLE Get-SEPMAdmins -AdminName admin #> [CmdletBinding()] Param ( # AdminName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [String] [Alias("Admin")] $AdminName ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/admin-users" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{ domain = $script:configuration.domain } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response if ([string]::IsNullOrEmpty($AdminName)) { return $resp } else { $resp = $resp | Where-Object { $_.loginName -eq $AdminName } return $resp } } } #EndRegion '.\Public\Get-SEPMAdmins.ps1' 78 #Region '.\Public\Get-SEPMCommandStatus.ps1' 0 function Get-SEPMCommandStatus { <# .SYNOPSIS Get Command Status Details .DESCRIPTION Gets the details of a command status .EXAMPLE PS C:\PSSymantecSEPM> $status = Get-SEPMCommandStatus -Command_ID D17D6DF9877049559910DD7B0306711C content : {@{beginTime=; lastUpdateTime=; computerName=MyWorkstation01; computerIp=192.168.1.1; domainName=Default; currentLoginUserName=localadmin; stateId=0; subStateId=0; subStateDesc=; binaryFileId=; resultInXML=; computerId=ABCDEF2837CD5C4FD167AD5E2CB31C71; hardwareKey=ABCDEF2837CD5C4FD167AD5E2CB31C71}} number : 0 size : 20 sort : {@{direction=ASC; property=Begintime; ascending=True}} numberOfElements : 1 firstPage : True totalPages : 1 lastPage : True totalElements : 1 PS C:\PSSymantecSEPM> $status.content beginTime : lastUpdateTime : computerName : MyWorkstation01 computerIp : 192.168.1.1 domainName : Default currentLoginUserName : localadmin stateId : 0 subStateId : 0 subStateDesc : binaryFileId : resultInXML : computerId : ABCDEF2837CD5C4FD167AD5E2CB31C71 hardwareKey : ABCDEF2837CD5C4FD167AD5E2CB31C71 Gets the status of a command .PARAMETER Command_ID The ID of the command to get the status of #> [CmdletBinding()] param ( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [string] [Alias("ID", "CommandID")] $Command_ID ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/command-queue/$command_id" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMCommandStatus.ps1' 89 #Region '.\Public\Get-SEPMDatabaseInfo.ps1' 0 function Get-SEPMDatabaseInfo { <# .SYNOPSIS Gets the database infromation of local site. .DESCRIPTION Gets the database infromation of local site .INPUTS None .OUTPUTS System.Object .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMDatabaseInfo name : SQLSRV01 description : address : SQLSRV01 instanceName : port : 1433 type : Microsoft SQL Server version : 12.00.5000 installedBySepm : False database : sem5 dbUser : sem5 dbPasswords : dbTLSRootCertificate : Gets detailed information on the database of the local site #> # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/admin/database" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } #EndRegion '.\Public\Get-SEPMDatabaseInfo.ps1' 49 #Region '.\Public\Get-SEPMDomain.ps1' 0 function Get-SEPMDomain { <# .SYNOPSIS Gets a list of all accessible domains .DESCRIPTION Gets a list of all accessible domains .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMDomain id : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX name : Default description : createdTime : 1360247301316 enable : True companyName : contactInfo : administratorCount : 15 Gets a list of all accessible domains #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/domains" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMDomain.ps1' 58 #Region '.\Public\Get-SEPMEventInfo.ps1' 0 function Get-SEPMEventInfo { <# .SYNOPSIS Gets the information about the computers in a specified domain .DESCRIPTION Gets the information about the computers in a specified domain. A system administrator account is required for this REST API. .EXAMPLE PS C:\PSSymantecSEPM> $SEPMEvents = Get-SEPMEventInfo lastUpdated totalUnacknowledgedMessages criticalEventsInfoList ----------- --------------------------- ---------------------- 1693911276712 4906 {@{eventId=XXXXXXXXXXXXXXXXXXXXXXXXX; eventDateTime=2023-08-12 19:22:21.0... PS C:\PSSymantecSEPM> $SEPMEvents.criticalEventsInfoList | Select-Object -First 1 eventId : XXXXXXXXXXXXXXXXXXXXXXXXX eventDateTime : 2023-08-12 19:22:21.0 subject : CRITICAL: OLD SONAR DEFINITIONS message : 306 computers found with SONAR definitions older than 7 days. acknowledged : 0 Example of an event gathered from the SEPM server. #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/events/critical" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } } #EndRegion '.\Public\Get-SEPMEventInfo.ps1' 53 #Region '.\Public\Get-SEPMExceptionPolicy.ps1' 0 function Get-SEPMExceptionPolicy { <# .SYNOPSIS Get Exception Policy .DESCRIPTION Get Exception Policy details Note this is a V2 API call, and replies are originally JSON based .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMExceptionPolicy -PolicyName "Standard Servers - Exception policy" Name Value ---- ----- sources {} configuration {[files, System.Object[]], [non_pe_rules, System.Object[]], [directories, System.Object[]], [webdomains, System.Object[]]…} lockedoptions {[knownrisk, True], [extension, True], [file, True], [domain, True]…} enabled True desc name Standard Servers - Exception policy lastmodifiedtime 1646398353107 Shows an example of getting the Exception policy details for the policy named "Standard Servers - Exception policy" #> [CmdletBinding()] Param ( # PolicyName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true )] [Alias("Policy_Name")] [String] $PolicyName ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } # BaseURL V2 $URI = $script:BaseURLv2 + "/policies/exceptions" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } # Stores the policy summary for all policies only once $policies = Get-SEPMPoliciesSummary } process { # Get Policy ID from policy name $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype if ($policy_type -ne "exceptions") { $message = "policy type is not of type EXCEPTIONS or does not exist - Please verify the policy name" Write-Error -Message $message throw $message } # Updating URI with policy ID $URI = $URI + "/" + $policyID # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers UseBasicParsing = $true } $resp = Invoke-ABRestMethod -params $params # JSON response to convert to PSObject $resp = $resp | ConvertFrom-Json -AsHashtable -Depth 100 # return the response return $resp } } #EndRegion '.\Public\Get-SEPMExceptionPolicy.ps1' 95 #Region '.\Public\Get-SEPMFileFingerprintList.ps1' 0 function Get-SEPMFileFingerprintList { <# TODO update help .SYNOPSIS Get File Finger Print List By Name .DESCRIPTION Gets the file fingerprint list for a specified Name as a set of hash values .PARAMETER FingerprintListName The name of the file fingerprint list .PARAMETER FingerprintListID The ID of the file fingerprint list .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMFileFingerprintList -FingerprintListName "Fingerprint list for workstations" id : 2A331150CDB44B9A9F1332E27321A1EE name : Fingerprint list for workstations hashType : MD5 source : WEBSERVICE description : data : {01BCE403043C0695EBB04D89C2B3A027, 03F3C0A7A2DD4EE1E81FABDBC557E2E8, 043A1B77C731F053FCA5DCC4AA18838F, 07996DCEEA57D8615B91A48AA7B49EC3…} groupIds : {46B9A36B0A66062224C839F606E6B1CE, AD3CD4620A95B05502CBDB658A6F7BE3, 09CC40530A6606221853DEA0AC606451, 96017A1E0A6906231EFEACCBD915B592…} Gets the file fingerprint list for a specified Name as a set of hash values .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMFileFingerprintList -FingerprintListID 2A331150CDB44B9A9F1332E27321A1EE id : 2A331150CDB44B9A9F1332E27321A1EE name : ASD01P0215 hashType : MD5 source : WEBSERVICE description : data : {01BCE403043C0695EBB04D89C2B3A027, 03F3C0A7A2DD4EE1E81FABDBC557E2E8, 043A1B77C731F053FCA5DCC4AA18838F, 07996DCEEA57D8615B91A48AA7B49EC3…} groupIds : {46B9A36B0A66062224C839F606E6B1CE, AD3CD4620A95B05502CBDB658A6F7BE3, 09CC40530A6606221853DEA0AC606451, 96017A1E0A6906231EFEACCBD915B592…} Gets the file fingerprint list for a specified ID as a set of hash values #> [CmdletBinding()] param ( [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [string] $FingerprintListName, [Parameter( ValueFromPipelineByPropertyName = $true )] [string] $FingerprintListID ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { if ($FingerprintListName) { $URI = $script:BaseURLv1 + "/policy-objects/fingerprints" # URI query strings $QueryStrings = @{ name = $FingerprintListName } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params } if ($FingerprintListID) { $URI = $script:BaseURLv1 + "/policy-objects/fingerprints/$FingerprintListID" # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params } # return the response return $resp } } #EndRegion '.\Public\Get-SEPMFileFingerprintList.ps1' 121 #Region '.\Public\Get-SEPMFirewallPolicy.ps1' 0 function Get-SEPMFirewallPolicy { <# .SYNOPSIS Get Firewall Policy .DESCRIPTION Get Firewall Policy details .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMFirewallPolicy -PolicyName "Standard Servers - Firewall policy" sources : configuration : @{enforced_rules=System.Object[]; baseline_rules=System.Object[]; ignore_parent_rules=; smart_dhcp=False; smart_dns=False; smart_wins=False; token_ring_traffic=False; netbios_protection=False; reverse_dns=False; port_scan=False; dos=False; antimac_spoofing=False; autoblock=False; autoblock_duration=600; stealth_web=False; antiIP_spoofing=False; hide_os=False; windows_firewall=NO_ACTION; windows_firewall_notification=False; endpoint_notification=; p2p_auth=; mac=} enabled : True desc : Standard Server Firewall Policy - This policy is for standard servers. It is a strict policy that blocks all traffic except for the services that are explicitly allowed. name : Standard Servers - Firewall policy lastmodifiedtime : 1692253688318 Shows an example of getting the firewall policy details for the policy named "Standard Servers - Firewall policy" #> [CmdletBinding()] Param ( # PolicyName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true )] [Alias("Policy_Name")] [String] $PolicyName ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/policies/firewall" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } # Stores the policy summary for all policies only once $policies = Get-SEPMPoliciesSummary } process { # Get Policy ID from policy name $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype if ($policy_type -ne "fw") { $message = "policy type is not of type FIREWALL or does not exist - Please verify the policy name" Write-Error -Message $message throw $message } # Updating URI with policy ID $URI = $URI + "/" + $policyID # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMFirewallPolicy.ps1' 86 #Region '.\Public\Get-SEPMGroups.ps1' 0 function Get-SEPMGroups { <# .SYNOPSIS Gets a group list .DESCRIPTION Gets a group list .EXAMPLE PS C:\GitHub_Projects\PSSymantecSEPM> Get-SEPMGroups | Select-Object -First 1 id : XXXXXXXXXXXXXXXXXXXXXXXXX name : My Company description : fullPathName : My Company numberOfPhysicalComputers : 0 numberOfRegisteredUsers : 0 createdBy : XXXXXXXXXXXXXXXXXXXXXXXXX created : 1360247401336 lastModified : 1639056401576 policySerialNumber : 718B-09/04/2023 12:56:58 775 policyDate : 1693832218775 customIpsNumber : domain : @{id=XXXXXXXXXXXXXXXXXXXXXXXXX; name=Default} policyInheritanceEnabled : False Gets the first group of the list of groups #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/groups" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } # Invoke the request # If the version of PowerShell is 6 or greater, then we can use the -SkipCertificateCheck parameter # else we need to use the Skip-Cert function if self-signed certs are being used. do { try { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response $allResults += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } catch { Write-Warning -Message "Error: $_" } } until ($resp.lastPage -eq $true) # return the response return $allResults } } #EndRegion '.\Public\Get-SEPMGroups.ps1' 94 #Region '.\Public\Get-SEPMIpsPolicy.ps1' 0 function Get-SEPMIpsPolicy { # TODO : returned object has empty configuration fields. Could be a bug ? # Example # PS C:\PSSymantecSEPM> $IPS_example | ConvertTo-Json # { # "sources": null, # "configuration": {}, # "enabled": true, # "desc": "Summary : added IP as excluded host to avoid ServiceNow discovery service conflicts", # "name": "Intrusion Prevention policy PRODUCTION", # "lastmodifiedtime": 1693559858824 # } <# .SYNOPSIS Get IPS Policy .DESCRIPTION Get IPS Policy details .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMIpsPolicy -PolicyName "Intrusion Prevention policy PRODUCTION" sources : configuration : enabled : True desc : IPS description field name : Intrusion Prevention policy PRODUCTION lastmodifiedtime : 1693559858824 Shows an example of getting the IPS policy details for the policy named "Intrusion Prevention policy PRODUCTION" #> [CmdletBinding()] Param ( # PolicyName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true )] [Alias("Policy_Name")] [String] $PolicyName ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/policies/ips" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } # Stores the policy summary for all policies only once $policies = Get-SEPMPoliciesSummary } process { # Get Policy ID from policy name $policyID = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty id $policy_type = $policies | Where-Object { $_.name -eq $PolicyName } | Select-Object -ExpandProperty policytype if ($policy_type -ne "ips") { $message = "policy type is not of type IPS or does not exist - Please verify the policy name" Write-Error -Message $message throw $message } # Updating URI with policy ID $URI = $URI + "/" + $policyID # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers UseBasicParsing = $true } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMIpsPolicy.ps1' 97 #Region '.\Public\Get-SEPMLatestDefinition.ps1' 0 function Get-SEPMLatestDefinition { <# .SYNOPSIS Get AV Def Latest Info .DESCRIPTION Gets the latest revision information for antivirus definitions from Symantec Security Response. .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMLatestDefinition contentName publishedBySymantec publishedBySEPM ----------- ------------------- --------------- AV_DEFS 9/4/2023 rev. 2 9/4/2023 rev. 2 Gets the latest revision information for antivirus definitions from Symantec Security Response. #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/content/avdef/latest" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMLatestDefinition.ps1' 53 #Region '.\Public\Get-SEPMPoliciesSummary.ps1' 0 function Get-SEPMPoliciesSummary { <# .SYNOPSIS Get summary of all or feature specific policies .DESCRIPTION Get the policy summary for specified policy type. Also gets the list of groups to which the policies are assigned. .PARAMETER PolicyType The policy type for which the summary is to be retrieved. The valid values are hid, exceptions, mem, ntr, av, fw, ips, lu, hi, adc, msl, upgrade. .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMPoliciesSummary content : {@{sources=System.Object[]; enabled=True; desc=Created automatically during product ...}} size : 136 number : 0 sort : numberOfElements : 136 totalElements : 136 totalPages : 1 lastPage : True firstPage : True Get policy statistics for all policies and its assigned groups .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMPoliciesSummary -PolicyType fw Get policy statistics for firewall policies and its assigned groups #> [CmdletBinding()] param ( [Parameter()] [ValidateSet( 'hid', 'exceptions', 'mem', 'ntr', 'av', 'fw', 'ips', 'lucontent', 'lu', 'hi', 'adc', 'msl', 'upgrade' )] [string] $PolicyType ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/policies/summary" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } # Get the list of groups and IDs to inject into the response $groups = Get-SEPMGroups } process { if (-not $PolicyType) { $allResults = @() # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } # Invoke the request # If the version of PowerShell is 6 or greater, then we can use the -SkipCertificateCheck parameter # else we need to use the Skip-Cert function if self-signed certs are being used. do { try { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Add group FullPath to the response from their Group ID for ease of use # Parsing every response object foreach ($policy in $resp.content) { # Parsing every location this policy is applied to foreach ($location in $policy.assignedtolocations) { # Getting the group name from the group ID, and adding it to the response object $group = $groups | Where-Object { $_.id -match $location.groupid } | Get-Unique $location | Add-Member -NotePropertyName "groupNameFullPath" -NotePropertyValue $group.fullPathName } } # Process the response $allResults += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } catch { Write-Warning -Message "Error: $_" } } until ($resp.lastPage -eq $true) # return the response return $allResults } if ($PolicyType) { $URI = $script:BaseURLv1 + "/policies/summary" + "/" + $PolicyType $allResults = @() # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } # Invoke the request # If the version of PowerShell is 6 or greater, then we can use the -SkipCertificateCheck parameter # else we need to use the Skip-Cert function if self-signed certs are being used. do { try { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Add group FullPath to the response from their Group ID for ease of use # Parsing every response object foreach ($policy in $resp.content) { # Parsing every location this policy is applied to foreach ($location in $policy.assignedtolocations) { # Getting the group name from the group ID, and adding it to the response object $group = $groups | Where-Object { $_.id -match $location.groupid } | Get-Unique $location | Add-Member -NotePropertyName "groupNameFullPath" -NotePropertyValue $group.fullPathName } } # Process the response $allResults += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } catch { Write-Warning -Message "Error: $_" } } until ($resp.lastPage -eq $true) # return the response return $allResults } } } #EndRegion '.\Public\Get-SEPMPoliciesSummary.ps1' 205 #Region '.\Public\Get-SEPMReplicationStatus.ps1' 0 function Get-SEPMReplicationStatus { <# .SYNOPSIS Get Replication Status .DESCRIPTION Get Replication Status .EXAMPLE PS C:\GitHub_Projects\PSSymantecSEPM> Get-SEPMReplicationStatus replicationStatus ----------------- @{siteName=Site Europe; siteLocation=Paris; replicationPartnerStatusList=System.Object[]; id=XXXXXXXXXXXXXXXXXXXXXXXX} Get a list of replication status with every remote site #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/replication/status" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMReplicationStatus.ps1' 53 #Region '.\Public\Get-SEPMThreatStats.ps1' 0 function Get-SEPMThreatStats { <# .SYNOPSIS Gets threat statistics .DESCRIPTION Gets threat statistics .EXAMPLE PS C:\PSSymantecSEPM> Get-SEPMThreatStats Stats ----- @{lastUpdated=1693912098821; infectedClients=1} Gets threat statistics #> begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/stats/threat" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{} # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Get-SEPMThreatStats.ps1' 53 #Region '.\Public\Get-SEPMVersion.ps1' 0 Function Get-SEPMVersion { <# .SYNOPSIS Gets the current version of Symantec Endpoint Protection Manager. .DESCRIPTION Gets the current version of Symantec Endpoint Protection Manager. This function dot not require authentication. .EXAMPLE PS C:\GitHub_Projects\PSSymantecSEPM> Get-SEPMVersion API_SEQUENCE API_VERSION version ------------ ----------- ------- 230504014 14.3.7000 14.3.9816.7000 Gets the current version of Symantec Endpoint Protection Manager. #> # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/version" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } $body = @{ } $params = @{ Method = 'GET' Uri = $URI headers = $headers Body = ($body | ConvertTo-Json) } $resp = Invoke-ABRestMethod -params $params return $resp } #EndRegion '.\Public\Get-SEPMVersion.ps1' 41 #Region '.\Public\Remove-SEPMFileFingerprintList.ps1' 0 function Remove-SEPMFileFingerprintList { <# .SYNOPSIS Deletes a file fingerprint list .DESCRIPTION Deletes a file fingerprint list, and removes it from a group to which it applies .PARAMETER FingerprintListName The name of the file fingerprint list .PARAMETER FingerprintListID The ID of the file fingerprint list .EXAMPLE PS C:\PSSymantecSEPM> Remove-SEPMFileFingerprintList -FingerprintListName "Fingerprint list for workstations" Removes the file fingerprint list with the name "Fingerprint list for workstations" .EXAMPLE PS C:\PSSymantecSEPM> "Fingerprint list for workstations" | Remove-SEPMFileFingerprintList Removes the file fingerprint list with the name "Fingerprint list for workstations" via the pipeline .EXAMPLE PS C:\PSSymantecSEPM> Remove-SEPMFileFingerprintList -FingerprintListID 2A331150CDB44B9A9F1332E27321A1EE Removes the file fingerprint list with the ID "2A331150CDB44B9A9F1332E27321A1EE" #> [CmdletBinding( DefaultParameterSetName = 'Name' )] param ( [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Name' )] [string] $FingerprintListName, [Parameter( ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ID' )] [string] $FingerprintListID ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # Get the FingerprintListID if the FingerprintListName is provided if ($FingerprintListName) { $URI = $script:BaseURLv1 + "/policy-objects/fingerprints" $FingerprintListID = Get-SEPMFileFingerprintList -FingerprintListName $FingerprintListName | Select-Object -ExpandProperty id } $URI = $script:BaseURLv1 + "/policy-objects/fingerprints/$FingerprintListID" $params = @{ Method = 'DELETE' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } } #EndRegion '.\Public\Remove-SEPMFileFingerprintList.ps1' 79 #Region '.\Public\Reset-SepmConfiguration.ps1' 0 function Reset-SEPMConfiguration { <# .SYNOPSIS Clears out the user's configuration file and configures this session with all default configuration values. .DESCRIPTION Clears out the user's configuration file and configures this session with all default configuration values. .EXAMPLE Reset-SEPMConfiguration Deletes the local configuration file and loads in all default configuration values. .NOTES This command will not clear your authentication token. Please use Clear-SEPMAuthentication to accomplish that. #> $null = Remove-Item -Path $script:configurationFilePath -Force -ErrorAction SilentlyContinue -ErrorVariable ev if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) { $message = "Reset was unsuccessful. Experienced a problem trying to remove the file [$script:configurationFilePath]." Write-Warning -Message $message } Initialize-SepmConfiguration $message = "This has not cleared your authentication token. Call Clear-SEPMAuthentication to accomplish that." Write-Verbose -Message $message } #EndRegion '.\Public\Reset-SepmConfiguration.ps1' 33 #Region '.\Public\Send-SEPMCommandGetFile.ps1' 0 function Send-SEPMCommandGetFile { <# .SYNOPSIS Sends a commands to request a suspicious file be uploaded back to Symantec Endpoint Protection Manager .DESCRIPTION Sends a commands to request a suspicious file be uploaded back to Symantec Endpoint Protection Manager .PARAMETER ComputerName The list of computers on which to search for the suspicious file. .PARAMETER SHA256 SHA256 hash of the suspicious file. .PARAMETER MD5 MD5 hash of the suspicious file. .PARAMETER SHA1 SHA1 hash of the suspicious file. .PARAMETER Source The source to search for the suspicious file Possible values are: FILESYSTEM (default), QUARANTINE, or BOTH. 12.1.x clients only use FILESYSTEM. .PARAMETER FilePath The file path of the suspicious file. .EXAMPLE PS C:\PSSymantecSEPM> Send-SEPMCommandGetFile -ComputerName MyWorkstation01 -SHA256 1234567890123456789012345678901234567890123456789012345678901234 -FilePath C:\Temp\malware.exe -Source BOTH Sends a command to request the following file C:\Temp\malware.exe be uploaded from MyWorkstation01 to Symantec Endpoint Protection Manager. Requests includes both the file system and quarantine locations to be looked at. #> [CmdletBinding()] param ( # The list of computers on which to search for the suspicious file. [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Alias("Hostname", "DeviceName", "Device", "Computer")] [String] $ComputerName, # SHA256 hash of the suspicious file. [Parameter(ParameterSetName = 'SHA256Hash')] [ValidateScript({ if ($_.Length -ne 64) { throw "SHA256 hash must be 64 characters long" } return $true })] [string] $SHA256, # MD5 hash of the suspicious file. [Parameter(ParameterSetName = 'MD5Hash')] [ValidateScript({ if ($_.Length -ne 32) { throw "MD5 hash must be 32 characters long" } return $true })] [string] $MD5, # SHA1 hash of the suspicious file. [Parameter(ParameterSetName = 'SHA1Hash')] [ValidateScript({ if ($_.Length -ne 40) { throw "SHA1 hash must be 40 characters long" } return $true })] [string] $SHA1, # The source to search for the suspicious file [Parameter()] [ValidateSet('FILESYSTEM ', 'QUARANTINE', 'BOTH')] [string] $Source, # The file path of the suspicious file. [Parameter()] [ValidateScript({ if ($_ -notmatch '^.+\\[^\\]+\.[^\\]+$') { throw "The string must be a file path ending with a file name and an extension" } return $true })] [Alias("Path")] [string] $FilePath ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # Get computer ID(s) from computer name(s) $ComputerIDList = @() foreach ($C in $ComputerName) { $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId $ComputerIDList += $ComputerID } $URI = $script:BaseURLv1 + "/command-queue/files" # URI query strings $QueryStrings = @{ computer_ids = $ComputerIDList file_path = $FilePath source = $Source } # Add correct hash to query strings if ($SHA256) { $QueryStrings.Add("sha256", $SHA256) } elseif ($MD5) { $QueryStrings.Add("md5", $MD5) } elseif ($SHA1) { $QueryStrings.Add("sha1", $SHA1) } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Invoke the request params $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } } #EndRegion '.\Public\Send-SEPMCommandGetFile.ps1' 147 #Region '.\Public\Send-SEPMCommandQuarantine.ps1' 0 function Send-SEPMCommandQuarantine { <# .SYNOPSIS Send a quarantine/unquarantine command to SEP endpoints .DESCRIPTION Send a quarantine/unquarantine command to SEP endpoints .PARAMETER ComputerName The name of the computer to send the command to Cannot be used with GroupName .PARAMETER GroupName The name of the group to send the command to Cannot be used with ComputerName Does not include subgroups .PARAMETER Unquarantine Switch parameter to unquarantine the SEP client .EXAMPLE Send-SEPMCommandQuarantine -ComputerName "Computer1" Sends a command to quarantine Computer1 .EXAMPLE "Computer1", "Computer2" | Send-SEPMCommandQuarantine Sends a command to quarantine Computer1 and Computer2 .EXAMPLE Send-SEPMCommandQuarantine -GroupName "My Company\EMEA\Workstations\Site1" Sends a command to quarantine all computers in "My Company\EMEA\Workstations\Site1" Does not include subgroups #> [CmdletBinding()] param ( # ComputerName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ComputerName' )] [Alias("Hostname", "DeviceName", "Device", "Computer")] [String] $ComputerName, # group name [Parameter( ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GroupName' )] [Alias("Group")] [String] $GroupName, # Unquarantine [Parameter()] [switch] $Unquarantine ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { if ($ComputerName) { # Get computer ID(s) from computer name(s) $ComputerIDList = @() foreach ($C in $ComputerName) { $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId $ComputerIDList += $ComputerID } $URI = $script:BaseURLv1 + "/command-queue/quarantine" # URI query strings $QueryStrings = @{ computer_ids = $ComputerIDList } # Add unquarantine if specified if ($Unquarantine) { $QueryStrings['undo'] = $true } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Invoke the request params $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } # If group name is specified elseif ($GroupName) { # Get group ID from group name $GroupID = Get-SEPMGroups | Where-Object { $_.fullPathName -eq $GroupName } | Select-Object -ExpandProperty id -First 1 $URI = $script:BaseURLv1 + "/command-queue/quarantine" # URI query strings $QueryStrings = @{ group_ids = $GroupID } # Add unquarantine if specified if ($Unquarantine) { $QueryStrings['undo'] = $true } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Invoke the request params $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } } } #EndRegion '.\Public\Send-SEPMCommandQuarantine.ps1' 152 #Region '.\Public\Set-SepmAuthentication.ps1' 0 function Set-SEPMAuthentication { <# .SYNOPSIS Allows the user to configure the SEPM Authentication .DESCRIPTION Allows the user to configure the SEPM Authentication Username and password will be securely stored on the machine for use in all future PowerShell sessions. .PARAMETER Credential If provided, instead of prompting the user for their API Token, it will be extracted from the password field of this credential object. .EXAMPLE Set-SEPMAuthentication Prompts the user for credentials and SEPM server address .EXAMPLE $secureString = ("<Your Access Token>" | ConvertTo-SecureString -AsPlainText -Force) $cred = New-Object System.Management.Automation.PSCredential "username", $secureString Set-SEPMAuthentication -Credential $cred Allows you to specify your username and password as a PSCredential object .EXAMPLE Set-SEPMAuthentication -credential (Get-Credential) Prompts the user for username and password, saves them to disk and in the PS Session .EXAMPLE $creds = Get-Credential Set-SEPMAuthentication -Credential $cred -ServerAddress "SEPMSRV01" .EXAMPLE Set-SEPMAuthentication -Port 8888 Changes the API communication port to 8888. Default is 8446. #> [CmdletBinding()] param( [string] $ServerAddress, [int] $Port = 8446, [PSCredential] $Creds ) switch ($PSBoundParameters.Keys) { 'Creds' { if ([String]::IsNullOrWhiteSpace($Creds.GetNetworkCredential().Password)) { $message = "Password not provided. Provide correct credentials and try again." Write-Error -Message $message throw $message } # Setting script scope variables so that they can be used in other functions $script:Credential = $Creds # Saving credentials to disk $Creds | Export-Clixml -Path $script:credentialsFilePath -Force } 'ServerAddress' { Set-SepmConfiguration -ServerAddress $ServerAddress } 'Port' { Set-SepmConfiguration -Port $Port } } } #EndRegion '.\Public\Set-SepmAuthentication.ps1' 72 #Region '.\Public\Set-SepmConfiguration.ps1' 0 function Set-SepmConfiguration { <# .SYNOPSIS Change the value of a configuration property for the PSSymantecSEPM module .DESCRIPTION Change the value of a configuration property for the PSSymantecSEPM module A single call to this method can set any number or combination of properties. .PARAMETER ServerAddress The hostname of the SEPM instance to communicate with. .EXAMPLE Set-SepmConfiguration ServerAddress "MySEPMServer" Set the SEPM server address to "MySEPMServer" #> [CmdletBinding( PositionalBinding = $false )] param( [string] $ServerAddress, [int] $Port ) $persistedConfig = Read-SepmConfiguration -Path $script:configurationFilePath $properties = Get-Member -InputObject $script:configuration -MemberType NoteProperty | Select-Object -ExpandProperty Name foreach ($name in $properties) { if ($PSBoundParameters.ContainsKey($name)) { $value = $PSBoundParameters.$name if ($value -is [switch]) { $value = $value.ToBool() } $script:configuration.$name = $value Add-Member -InputObject $persistedConfig -Name $name -Value $value -MemberType NoteProperty -Force } } Save-SepmConfiguration -Configuration $persistedConfig -Path $script:configurationFilePath } #EndRegion '.\Public\Set-SepmConfiguration.ps1' 43 #Region '.\Public\Start-SEPMReplication.ps1' 0 function Start-SEPMReplication { <# TODO update help .SYNOPSIS Gets a list of all accessible domains .DESCRIPTION Gets a list of all accessible domains .EXAMPLE PS C:\PSSymantecSEPM> Start-SEPMReplication -partnerSiteName "Remote site Americas" code ---- 0 Initiates replication with the remote site Americas. Response code 0 indicates success. #> [CmdletBinding()] param ( [Parameter()] [string] $partnerSiteName # [bool] # $logs, # [bool] # $ContentAndPackages ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $URI = $script:BaseURLv1 + "/replication/replicatenow" $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # URI query strings $QueryStrings = @{ partnerSiteName = $partnerSiteName logs = $logs ContentAndPackages = $ContentAndPackages } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params return $resp } } #EndRegion '.\Public\Start-SEPMReplication.ps1' 70 #Region '.\Public\Start-SEPScan.ps1' 0 function Start-SEPScan { <# .SYNOPSIS Sends Active Scan command to the specified computer(s) or group(s) .DESCRIPTION Sends a command from SEPM to SEP endpoints to request an active scan on the endpoint(s) .PARAMETER ComputerName Specifies the name of the computer for which you want to send the command. Accepts pipeline input by Value and ByPropertyName .PARAMETER GroupName Specifies the group full path name for which you want to send the command .PARAMETER ActiveScan Specifies the type of scan to send to the endpoint(s) Valid values are ActiveScan and FullScan By default, the ActiveScan switch is used .PARAMETER FullScan Specifies the type of scan to send to the endpoint(s) Valid values are ActiveScan and FullScan .EXAMPLE PS C:\PSSymantecSEPM> Start-SEPScan -ComputerName MyComputer01 -ActiveScan Sends an active scan command to the specified computer MyComputer01 .EXAMPLE "MyComputer1","MyComputer2" | Start-SEPScan Sends an active scan command to the specified computers MyComputer1 & MyComputer2 via pipeline By default, the ActiveScan switch is used .EXAMPLE Start-SEPScan -GroupName "My Company\EMEA\Workstations" -fullscan Sends a fullscan command to all endpoints part of the group "My Company\EMEA\Workstations" #> [CmdletBinding( DefaultParameterSetName = 'ComputerNameActiveScan' )] Param ( # ComputerName [Parameter( ParameterSetName = 'ComputerNameActiveScan', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Parameter( ParameterSetName = 'ComputerNameFullScan', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Alias("Hostname", "DeviceName", "Device", "Computer")] [String] $ComputerName, # group name [Parameter( ParameterSetName = 'GroupNameActiveScan', ValueFromPipelineByPropertyName = $true )] [Parameter( ParameterSetName = 'GroupNameFullScan', ValueFromPipelineByPropertyName = $true )] [Alias("Group")] [String] $GroupName, # ActiveScan [Parameter(ParameterSetName = 'ComputerNameActiveScan')] [Parameter(ParameterSetName = 'GroupNameActiveScan')] [switch] $ActiveScan, # FullScan [Parameter(ParameterSetName = 'ComputerNameFullScan')] [Parameter(ParameterSetName = 'GroupNameFullScan')] [switch] $FullScan ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # If specific computer name(s) are specified if ($ComputerName) { # Get computer ID(s) from computer name(s) $ComputerIDList = @() foreach ($C in $ComputerName) { $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId $ComputerIDList += $ComputerID } if ($ActiveScan) { $URI = $script:BaseURLv1 + "/command-queue/activescan" } if ($FullScan) { $URI = $script:BaseURLv1 + "/command-queue/fullscan" } # URI query strings $QueryStrings = @{ computer_ids = $ComputerIDList } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Invoke the request params $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } # If by groupname elseif ($GroupName) { ####################################### # 1. finds all computers in the group # ####################################### $allComputers = @() $URI = $script:BaseURLv1 + "/computers" # URI query strings $QueryStrings = @{ sort = "COMPUTER_NAME" pageIndex = 1 pageSize = 100 computerName = $ComputerName # empty string value to ensure the URI is constructed correctly & query all computers } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Get computer list do { try { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response $allComputers += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } catch { Write-Warning -Message "Error: $_" } } until ($resp.lastPage -eq $true) # filter list by group name $allComputers = $allComputers | Where-Object { $_.group.name -eq $GroupName } ################################################# # 2. send command to all computers in the group # ################################################# if ($ActiveScan) { $URI = $script:BaseURLv1 + "/command-queue/activescan" } if ($FullScan) { $URI = $script:BaseURLv1 + "/command-queue/fullscan" } $AllResp = @() foreach ($id in $allComputers.uniqueId) { # URI query strings $QueryStrings = @{ computer_ids = $id } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Invoke the request params $params = @{ Method = 'POST' Uri = $URI headers = $headers } # Send command to each computers in the group $resp = Invoke-ABRestMethod -params $params $AllResp += $resp } # return the response return $AllResp } } } #EndRegion '.\Public\Start-SEPScan.ps1' 236 #Region '.\Public\Update-SEPClientDefinitions.ps1' 0 function Update-SEPClientDefinitions { <# .SYNOPSIS Sends a command from SEPM to SEP endpoints to update content .DESCRIPTION Sends a command from SEPM to SEP endpoints to update content .PARAMETER ComputerName The name of the computer to send the command to cannot be used with GroupName .PARAMETER GroupName The name of the group to send the command to cannot be used with ComputerName .EXAMPLE Update-SEPClientDefinitions -ComputerName "Computer1" Sends a command to update content to Computer1 .EXAMPLE "Computer1", "Computer2" | Update-SEPClientDefinitions Sends a command to update content to Computer1 and Computer2 .EXAMPLE Update-SEPClientDefinitions -GroupName "My Company\EMEA\Workstations" Sends a command to update content to all computers in "My Company\EMEA\Workstations" .EXAMPLE Update-SEPClientDefinitions -GroupName "My Company\EMEA\Workstations" -IncludeSubGroups Sends a command to update content to all computers in "My Company\EMEA\Workstations" and all subgroups #> [CmdletBinding()] param ( # ComputerName [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ComputerName' )] [Alias("Hostname", "DeviceName", "Device", "Computer")] [String] $ComputerName, # group name [Parameter( ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GroupName' )] [Alias("Group")] [String] $GroupName, # switch parameter to include subgroups [Parameter( ParameterSetName = 'GroupName' )] [switch] $IncludeSubGroups ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { if ($ComputerName) { # Get computer ID(s) from computer name(s) $ComputerIDList = @() foreach ($C in $ComputerName) { $ComputerID = Get-SEPComputers -ComputerName $C | Select-Object -ExpandProperty uniqueId $ComputerIDList += $ComputerID } $URI = $script:BaseURLv1 + "/command-queue/updatecontent" # URI query strings $QueryStrings = @{ computer_ids = $ComputerIDList } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Invoke the request params $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } # If by groupname elseif ($GroupName) { ####################################### # 1. finds all computers in the group # ####################################### $allComputers = @() $URI = $script:BaseURLv1 + "/computers" # URI query strings $QueryStrings = @{ sort = "COMPUTER_NAME" pageIndex = 1 pageSize = 100 computerName = $ComputerName # empty string value to ensure the URI is constructed correctly & query all computers } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Get computer list do { try { # Invoke the request params $params = @{ Method = 'GET' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params # Process the response $allComputers += $resp.content # Increment the page index & update URI $QueryStrings.pageIndex++ $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() } catch { Write-Warning -Message "Error: $_" } } until ($resp.lastPage -eq $true) # filter list by group name # if IncludeSubGroups is specified, then get all computers from subgroups if ($IncludeSubGroups) { # get all subgroups $allComputers = $allComputers | Where-Object { $_.group.name -like "$GroupName*" } } else { $allComputers = $allComputers | Where-Object { $_.group.name -eq $GroupName } } ################################################# # 2. send command to all computers in the group # ################################################# $URI = $script:BaseURLv1 + "/command-queue/updatecontent" $AllResp = @() foreach ($id in $allComputers.uniqueId) { # URI query strings $QueryStrings = @{ computer_ids = $id } # Construct the URI $builder = New-Object System.UriBuilder($URI) $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query) foreach ($param in $QueryStrings.GetEnumerator()) { $query[$param.Key] = $param.Value } $builder.Query = $query.ToString() $URI = $builder.ToString() # Send command to each computers in the group $params = @{ Method = 'POST' Uri = $URI headers = $headers } $resp = Invoke-ABRestMethod -params $params $AllResp += $resp } # return the response return $AllResp } } } #EndRegion '.\Public\Update-SEPClientDefinitions.ps1' 207 #Region '.\Public\Update-SEPMFileFingerprintList.ps1' 0 function Update-SEPMFileFingerprintList { <# .SYNOPSIS Updates an existing fingerprint list .DESCRIPTION Updates an existing fingerprint list When updating the list, overwrite the entire list with the new list .PARAMETER FingerprintListName The name of the file fingerprint list Cannot be used with FingerprintListID parameter .PARAMETER FingerprintListID The ID of the file fingerprint list Cannot be used with FingerprintListName parameter .PARAMETER name The name of the fingerprint list that will appear on SEPM .PARAMETER domainId The domain id of the domain to add the fingerprint list to Only takes the domain id. Can be found using Get-SEPMDomain .PARAMETER HashType The type of hash to use for the fingerprint list Valid values are SHA256 and MD5 .PARAMETER description The description of the fingerprint list .PARAMETER hashlist The hash list to add to the fingerprint list Can be generated using Get-FileHash or takes a string array of hashes .EXAMPLE $domainId = Get-SEPMDomain | Where-Object { $_.name -eq "Default" } | Select-Object -ExpandProperty id $hashlist = ls -file C:\Users\$env:USERNAME\Downloads\*.exe | Get-FileHash -algorithm SHA256 Update-SEPMFileFingerprintList -FingerprintListName "Downloaded .exe files" -name "Workstations downloaded files" -domainId $DomainId -HashType "SHA256" -description "Contains the list of .exe files downloaded with a specific workstations" -hashlist $hashlist.hash Gets the domain id for the default domain Create a hash list of all the files in the downloads folder of the currently logged in user Updates the fingerprint list "Downloaded .exe files" with the new hash list The fingerprint list needs to be existing before it can be updated #> [CmdletBinding( DefaultParameterSetName = 'Name' )] param ( [Parameter()] [string]$name, [Parameter()] [string]$domainId, [Parameter()] [ValidateSet('SHA256', 'MD5')] [string]$HashType, [Parameter()] [string]$description, [Parameter( ValueFromPipeline = $true )] $hashlist, [Parameter( ParameterSetName = 'Name' )] [string] $FingerprintListName, [Parameter( ParameterSetName = 'ID' )] [string] $FingerprintListID ) begin { # initialize the configuration $test_token = Test-SEPMAccessToken if ($test_token -eq $false) { Get-SEPMAccessToken | Out-Null } $headers = @{ "Authorization" = "Bearer " + $script:accessToken.token "Content" = 'application/json' } } process { # Get the FingerprintListID if the FingerprintListName is provided if ($FingerprintListName) { $URI = $script:BaseURLv1 + "/policy-objects/fingerprints" $FingerprintListID = Get-SEPMFileFingerprintList -FingerprintListName $FingerprintListName | Select-Object -ExpandProperty id } $URI = $script:BaseURLv1 + "/policy-objects/fingerprints/$FingerprintListID" # Construct the body & required fields $body = @{ name = $name domainId = $domainId hashType = $HashType description = $description data = $hashlist } $params = @{ Method = 'POST' Uri = $URI headers = $headers Body = $body | ConvertTo-Json ContentType = 'application/json' } $resp = Invoke-ABRestMethod -params $params # return the response return $resp } } #EndRegion '.\Public\Update-SEPMFileFingerprintList.ps1' 118 #Region '.\Public\zz_Initialize-SEPMConfiguration.ps1' 0 #################################### # Init script for the whole module # #################################### ## This is the initialization script for the module. It is invoked at the end of the module's ## prefix file as "zz_" to load this module at last. This is done to ensure that all other functions are first loaded ## This function should be private but will stay Public for the moment as it needs to be the last function to be loaded in the module ## TODO make this function private # The credentials used to authenticate to the SEPM server. [PSCredential] $script:Credential = $null [PSCustomObject] $script:accessToken = $null # SEPM Server configuration [string] $script:ServerAddress = $null [string] $script:BaseURLv1 = $null [string] $script:BaseURLv2 = $null [bool] $script:SkipCert = $false # Needed for self-signed certificates # The location of the file that we'll store any settings that can/should roam with the user. [string] $script:configurationFilePath = [System.IO.Path]::Combine( [System.Environment]::GetFolderPath('ApplicationData'), 'PSSymantecSEPM', 'config.json') # The location of the file that we'll store credentials [string] $script:credentialsFilePath = [System.IO.Path]::Combine( [System.Environment]::GetFolderPath('ApplicationData'), 'PSSymantecSEPM', 'creds.xml') # The location of the file that we'll store the Access Token SecureString # which cannot/should not roam with the user. [string] $script:accessTokenFilePath = [System.IO.Path]::Combine( [System.Environment]::GetFolderPath('LocalApplicationData'), 'PSSymantecSEPM', 'accessToken.xml') # The session-cached copy of the module's configuration properties [PSCustomObject] $script:configuration = $null function Initialize-SepmConfiguration { <# .SYNOPSIS Populates the configuration of the module for this session, loading in any values that may have been saved to disk. .DESCRIPTION Populates the configuration of the module for this session, loading in any values that may have been saved to disk. .NOTES Internal helper method. This is actually invoked at the END of this file. #> [CmdletBinding()] param() $script:configuration = Import-SepmConfiguration -Path $script:configurationFilePath if ($script:configuration) { if ([string]::IsNullOrEmpty($script:configuration.ServerAddress)) { Set-SEPMAuthentication } $script:BaseURLv1 = "https://" + $script:configuration.ServerAddress + ":" + $script:configuration.port + "/sepm/api/v1" $script:BaseURLv2 = "https://" + $script:configuration.ServerAddress + ":" + $script:configuration.port + "/sepm/api/v2" } if (Test-Path $script:credentialsFilePath) { $script:Credential = Import-Clixml -Path $script:credentialsFilePath -ErrorAction SilentlyContinue } if (Test-Path $script:accessTokenFilePath) { $script:accessToken = Import-Clixml -Path $script:accessTokenFilePath -ErrorAction SilentlyContinue } } # Invoke the initialization method to populate the configuration Initialize-SepmConfiguration #EndRegion '.\Public\zz_Initialize-SEPMConfiguration.ps1' 77 |