SophosFirewall.SophosCentral.psm1
|
#requires -Version 5.1 #requires -Modules @{ ModuleName = 'SophosFirewall.Core'; ModuleVersion = '1.4.1' } <# .SYNOPSIS Manages Sophos Central cloud management and registration on a Sophos Firewall. .DESCRIPTION Functions for the SYSTEM > Sophos Central area of the Sophos Firewall: the cloud central management switches - centralized reporting, management from Sophos Central, and configuration backup - that sit under EnableCloudCentralManagement in the XML API, and the Synchronized Security settings - Security Heartbeat and Synchronized Application Control - read and written through the web admin interface's own JSON REST endpoint. Registering a firewall with a Sophos Central tenant is deliberately not part of this module. The API offers exactly one way to do it, and that way sends the account name and password of a Sophos Central administrator in the request body. A tenant secured with a passkey, or with any other passwordless sign-in, cannot supply those, and the one-time password the web admin console accepts instead has no API equivalent. Register the firewall through the web admin console, then use this module for the switches. Total Functions: 4 - see README.md for the full cmdlet table. Connect once with Connect-SfosFirewall, then call the cmdlets in this module without repeating the connection parameters. .EXAMPLE Connect-SfosFirewall -Firewall '192.0.2.1' -Credential (Get-Credential) -SkipCertificateCheck Get-SfosCentralManagement Connects to the firewall and reads the current cloud central management settings. .EXAMPLE Set-SfosCentralManagement -UseCentralReporting Disable -Confirm:$false Get-SfosCentralManagement Switches Sophos Central reporting off, leaving management, backup and join method untouched, then confirms the new state. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/ .LINK Connect-SfosFirewall #> #region CentralManagement <# .SYNOPSIS Retrieves the Sophos Central cloud management settings from a Sophos Firewall. .DESCRIPTION Reads the four settings under EnableCloudCentralManagement: whether the firewall is managed from Sophos Central, uses Sophos Central for centralized reporting, sends configuration backups there, and how it joined the tenant. Read-only; needs a connection from Connect-SfosFirewall. Despite the wire element's name this is a settings object, not a command that triggers anything. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs read permission for the Sophos Central settings. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .PARAMETER AsXml Optional. Returns the raw XML element sent by the firewall instead of a PowerShell object. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. One object with the properties FWBackup, JoinMethod, UseCentralReporting and CMStatus. UseCentralReporting and CMStatus normally read Enable or Disable, but can also read the undocumented value WaitingForApproval while a service that was just switched on is waiting for a super admin to confirm it in the Sophos Central console. Returns System.Xml.XmlElement when -AsXml is used. .NOTES Does not reveal whether the firewall is actually registered with Sophos Central - these switches read independently of the registration state, so CMStatus can read Enable without a registration ever having been confirmed. Check the Sophos Central status in the web admin console instead. .EXAMPLE Get-SfosCentralManagement Returns the current Sophos Central management settings of the firewall of the current connection. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/ .LINK Set-SfosCentralManagement #> function Get-SfosCentralManagement { [CmdletBinding()] param( # Connection parameters (optional - use stored context if not provided) [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session, # Output parameters [switch]$AsXml ) $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $inner = '<Get><EnableCloudCentralManagement></EnableCloudCentralManagement></Get>' try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error retrieving CentralManagement: $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'EnableCloudCentralManagement' -Action 'get' $node = $XmlResponse.SelectSingleNode('/Response/EnableCloudCentralManagement') if (-not $node) { throw 'CentralManagement could not be retrieved from the firewall.' } if ($AsXml) { return $node } return [PSCustomObject]@{ FWBackup = [string]$node.FWBackup JoinMethod = [string]$node.JoinMethod UseCentralReporting = [string]$node.UseCentralReporting CMStatus = [string]$node.CMStatus } } <# .SYNOPSIS Updates the Sophos Central cloud management settings on a Sophos Firewall. .DESCRIPTION Updates the four EnableCloudCentralManagement switches. Reads the current settings first and resends every field, so anything not passed keeps its value. Needs a connection from Connect-SfosFirewall and an account permitted to change Sophos Central settings. .PARAMETER FWBackup Optional. Whether configuration backups are sent to Sophos Central: BackupEnable or BackupDisable. Corresponds to "Send configuration backup to Sophos Central" in the web admin console. Requires CMStatus to resolve to Enable - see the notes. If omitted, the current value is kept. .PARAMETER JoinMethod Optional. How the firewall joined Sophos Central: Manual or ZeroTouch. This switch has no equivalent in the web admin console; it reflects how the join happened rather than being a setting an administrator toggles directly. If omitted, the current value is kept. .PARAMETER UseCentralReporting Optional. Whether centralized reporting in Sophos Central is on: Enable or Disable. Corresponds to "Use Sophos Central reporting" in the web admin console. Switching this to Enable is accepted by the firewall but not completed by this cmdlet alone - see the notes. If omitted, the current value is kept. .PARAMETER CMStatus Optional. Whether the firewall is managed from Sophos Central: Enable or Disable. Corresponds to "Use Sophos Central management" in the web admin console. Switching this to Enable is accepted by the firewall but not completed by this cmdlet alone - see the notes. If omitted, the current value is kept. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs permission to change Sophos Central settings. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS None. The cmdlet writes no output. It throws if the firewall rejects the update, and also if the firewall reports success but a requested change to UseCentralReporting or CMStatus was not applied - see the notes. .NOTES Switching a service off takes effect immediately. Switching one back on is reported as success but stays pending until a super admin runs Accept services in the Sophos Central console - the cmdlet reads the settings back and throws when a field still shows its old value, and warns on the undocumented value WaitingForApproval. FWBackup requires CMStatus Enable; the cmdlet refuses the combination the web admin console cannot produce either. Registration is out of scope - this cmdlet neither registers nor unregisters the firewall, and CMStatus Disable does not end an existing registration. .EXAMPLE Set-SfosCentralManagement -CMStatus Disable -WhatIf Shows what the call would change without sending it to the firewall. .EXAMPLE Set-SfosCentralManagement -UseCentralReporting Disable -Confirm:$false Switches only centralized reporting off, leaving management, backup and join method untouched, without asking for confirmation. Use this form only in scripts where the value has already been reviewed. .EXAMPLE Set-SfosCentralManagement -FWBackup BackupEnable -CMStatus Disable -Confirm:$false Throws before sending anything to the firewall, because FWBackup cannot be BackupEnable while CMStatus is Disable. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/ .LINK Get-SfosCentralManagement #> function Set-SfosCentralManagement { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [ValidateSet('BackupEnable', 'BackupDisable')] [string]$FWBackup, [ValidateSet('Manual', 'ZeroTouch')] [string]$JoinMethod, [ValidateSet('Enable', 'Disable')] [string]$UseCentralReporting, [ValidateSet('Enable', 'Disable')] [string]$CMStatus, # Connection parameters (optional - use stored context if not provided) [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $bp = $PSBoundParameters # Cheap short-circuit: if the caller passed both FWBackup and CMStatus explicitly, the # conflict is already fully known and is reported before the firewall is contacted at # all - not just before anything is written. if ($bp.ContainsKey('FWBackup') -and $bp.ContainsKey('CMStatus') -and $FWBackup -eq 'BackupEnable' -and $CMStatus -eq 'Disable') { throw "Cannot update CentralManagement: FWBackup cannot be 'BackupEnable' together with CMStatus 'Disable' - configuration backup to Sophos Central depends on Sophos Central management being on, matching the web admin console. Set CMStatus to 'Enable' as well, or leave FWBackup at 'BackupDisable'." } $existing = Get-SfosCentralManagement -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -SkipCertificateCheck:$params.SkipCertificateCheck $targetFWBackup = if ($bp.ContainsKey('FWBackup')) { $FWBackup } else { $existing.FWBackup } $targetJoinMethod = if ($bp.ContainsKey('JoinMethod')) { $JoinMethod } else { $existing.JoinMethod } $targetUseCentralReporting = if ($bp.ContainsKey('UseCentralReporting')) { $UseCentralReporting } else { $existing.UseCentralReporting } $targetCMStatus = if ($bp.ContainsKey('CMStatus')) { $CMStatus } else { $existing.CMStatus } if ($targetFWBackup -eq 'BackupEnable' -and $targetCMStatus -eq 'Disable') { throw "Cannot update CentralManagement: FWBackup cannot be 'BackupEnable' while CMStatus resolves to 'Disable' (the current value on the firewall, since it was not passed) - configuration backup to Sophos Central depends on Sophos Central management being on, matching the web admin console. Set CMStatus to 'Enable' as well, or leave FWBackup at 'BackupDisable'." } if (-not $PSCmdlet.ShouldProcess("CentralManagement on $($params.Firewall)", 'Update')) { return } $inner = @" <Set operation="update"> <EnableCloudCentralManagement> <FWBackup>$(ConvertTo-SfosXmlEscaped -Text $targetFWBackup)</FWBackup> <JoinMethod>$(ConvertTo-SfosXmlEscaped -Text $targetJoinMethod)</JoinMethod> <UseCentralReporting>$(ConvertTo-SfosXmlEscaped -Text $targetUseCentralReporting)</UseCentralReporting> <CMStatus>$(ConvertTo-SfosXmlEscaped -Text $targetCMStatus)</CMStatus> </EnableCloudCentralManagement> </Set> "@ try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error updating CentralManagement: $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'EnableCloudCentralManagement' -Action 'update' # The firewall answers 200 for a write it does not apply. Turning a Sophos Central # service off works; turning one back on is accepted, reported successful and silently # ignored, because a service has to be accepted in the Sophos Central console before it # runs. A cmdlet whose name promises the change therefore reads the object back and # throws instead of leaving the caller with a success that never happened. $applied = Get-SfosCentralManagement -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -SkipCertificateCheck:$params.SkipCertificateCheck $mismatch = @( @{ Field = 'FWBackup'; Wanted = $targetFWBackup; Actual = [string]$applied.FWBackup } @{ Field = 'JoinMethod'; Wanted = $targetJoinMethod; Actual = [string]$applied.JoinMethod } @{ Field = 'UseCentralReporting'; Wanted = $targetUseCentralReporting; Actual = [string]$applied.UseCentralReporting } @{ Field = 'CMStatus'; Wanted = $targetCMStatus; Actual = [string]$applied.CMStatus } ) | Where-Object { $_.Wanted -ne $_.Actual } # A service switched on lands on the undocumented value 'WaitingForApproval' first: the # request was accepted and now waits for a super admin to click 'Accept services' in the # Sophos Central console. That is a pending request, not a failed write, so it warns. # Anything else that did not take effect is the silent-no-op case and throws. $pending = @($mismatch | Where-Object { $_.Actual -eq 'WaitingForApproval' }) $failed = @($mismatch | Where-Object { $_.Actual -ne 'WaitingForApproval' }) foreach ($field in $pending) { Write-Warning "$($field.Field) was requested as '$($field.Wanted)' and now reads 'WaitingForApproval'. The service starts once a super admin confirms it in the Sophos Central console with 'Accept services'." } if ($failed) { $detail = ($failed | ForEach-Object { "$($_.Field): requested '$($_.Wanted)', firewall reports '$($_.Actual)'" }) -join '; ' throw "The firewall reported success for the CentralManagement update but did not apply it - $detail. Switching a Sophos Central service on cannot be completed through the API alone; confirm it in the Sophos Central console instead." } } #endregion #region SynchronizedSecurity <# .SYNOPSIS Retrieves the Synchronized Security settings of a Sophos Firewall. .DESCRIPTION Reads the Security Heartbeat and Synchronized Application Control settings under SYSTEM > Sophos Central > Synchronized Security: whether each is on, the zones excluded from the heartbeat check, and how many months of application usage data the firewall keeps before purging it. Read-only; needs a connection from Connect-SfosFirewall and an account with administrative permission. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the web admin login. The account needs administrative permission. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the web admin login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .PARAMETER AcceptLoginDisclaimer Optional. Confirms a login disclaimer configured on the appliance, on behalf of the account logging in. Without this switch, this cmdlet fails on an appliance that shows a login disclaimer, with an error describing the disclaimer. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. One object with the properties HeartbeatEnabled (bool), RestrictedZone (string array - the zones excluded from the heartbeat check, empty when none are configured), SyncAppControlEnabled (bool), AppsCleanupTimeframe (int - months of application usage data retained; 0 means the automatic cleanup is off) and UpdatedAt (datetime, UTC). .NOTES Not part of the documented XML management API. Read through the web admin interface's own JSON REST endpoint (/synchronized-security/settings), the same call the web admin console itself makes; this interface is undocumented by the vendor and can change or break on a firmware update without notice. .EXAMPLE Get-SfosSynchronizedSecurity Returns the current Synchronized Security settings of the firewall of the current connection. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/ .LINK Set-SfosSynchronizedSecurity #> function Get-SfosSynchronizedSecurity { [CmdletBinding()] [OutputType([PSCustomObject])] param( [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session, [switch]$AcceptLoginDisclaimer ) $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $webAdmin = Connect-SfosWebAdmin -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -SkipCertificateCheck:$params.SkipCertificateCheck -AcceptLoginDisclaimer:$AcceptLoginDisclaimer -ErrorAction Stop try { $response = Invoke-SfosWebAdminRestRequest -WebAdminSession $webAdmin -Path '/synchronized-security/settings' -Method Get -ErrorAction Stop } catch { throw "Failed to read Synchronized Security settings from $($params.Firewall): $($_.Exception.Message)" } if (-not $response -or $response.PSObject.Properties.Match('heartbeatEnabled').Count -eq 0) { throw "Failed to read Synchronized Security settings from $($params.Firewall): the web admin interface answered without the expected data. The interface may have changed." } $zoneNames = @() if ($response.PSObject.Properties.Match('restrictedZones').Count -gt 0 -and $response.restrictedZones) { $zoneNames = @($response.restrictedZones | ForEach-Object { [string]$_.name }) } # updatedAt arrives as ISO-8601 with a trailing Z (UTC). ConvertFrom-Json behaves # differently between PowerShell versions here [measured]: PowerShell 7 parses it into a # [datetime] (Kind=Utc) automatically, PowerShell 5.1 leaves it as a [string]. Casting an # already-parsed [datetime] to [string] and re-parsing it is culture-dependent and unsafe - # observed to render as "02/07/2026" under one culture and "07.02.2026" under another for # the same value - so the type is checked first instead of assuming either shape. $updatedAt = $null if ($response.PSObject.Properties.Match('updatedAt').Count -gt 0 -and $response.updatedAt) { if ($response.updatedAt -is [datetime]) { $updatedAt = [datetime]::SpecifyKind($response.updatedAt, [DateTimeKind]::Utc) } else { $updatedAt = [datetime]::Parse( [string]$response.updatedAt, [Globalization.CultureInfo]::InvariantCulture, ([Globalization.DateTimeStyles]::AssumeUniversal -bor [Globalization.DateTimeStyles]::AdjustToUniversal)) } } [PSCustomObject]@{ HeartbeatEnabled = [bool]$response.heartbeatEnabled RestrictedZone = $zoneNames SyncAppControlEnabled = [bool]$response.syncAppControlEnabled AppsCleanupTimeframe = [int]$response.appsCleanupTimeframe UpdatedAt = $updatedAt } } <# .SYNOPSIS Updates the Synchronized Security settings of a Sophos Firewall. .DESCRIPTION Updates the Security Heartbeat and Synchronized Application Control settings under SYSTEM > Sophos Central > Synchronized Security. Reads the current settings first and resends the complete object, so anything not passed keeps its value; at least one functional parameter is required. Needs a connection from Connect-SfosFirewall and an account with administrative permission. .PARAMETER HeartbeatEnabled Optional. Whether Security Heartbeat is on. If omitted, the current value is kept. .PARAMETER RestrictedZone Optional. The zones excluded from the heartbeat check, replacing the whole list. Pass an empty array to clear it. If omitted, the current list is kept. .PARAMETER SyncAppControlEnabled Optional. Whether Synchronized Application Control is on. If omitted, the current value is kept. .PARAMETER AppsCleanupTimeframe Optional. Months of application usage data to retain before it is purged. The web admin console offers 1, 3, 6, 9 or 12 months, and uses 0 to mean the cleanup is off; other values were not observed and are refused. If omitted, the current value is kept. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the web admin login. The account needs administrative permission. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the web admin login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .PARAMETER AcceptLoginDisclaimer Optional. Confirms a login disclaimer configured on the appliance, on behalf of the account logging in. Without this switch, this cmdlet fails on an appliance that shows a login disclaimer, with an error describing the disclaimer. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS None. The cmdlet writes no output. It throws if the firewall rejects the update, and also if the firewall reports success but a requested value was not applied. .NOTES Not part of the documented XML management API. Written through the web admin interface's own JSON REST endpoint (/synchronized-security/settings), the same call the web admin console itself makes; this interface is undocumented by the vendor and can change or break on a firmware update without notice. HeartbeatEnabled controls whether the firewall exchanges health status with Sophos Central-managed endpoints for use in firewall rules; switching it off removes that signal from every rule that depends on it. SyncAppControlEnabled controls Synchronized Application Control, which classifies traffic using data reported by managed endpoints; switching it off reverts affected traffic to standard application detection. After the write, the cmdlet reads the settings back and throws if a requested value did not take effect. .EXAMPLE Set-SfosSynchronizedSecurity -SyncAppControlEnabled $true -WhatIf Shows what the call would change without sending it to the firewall. .EXAMPLE Set-SfosSynchronizedSecurity -AppsCleanupTimeframe 6 -Confirm:$false Sets the application data retention period to 6 months, leaving Security Heartbeat, the restricted zone list and Synchronized Application Control untouched. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/ .LINK Get-SfosSynchronizedSecurity #> function Set-SfosSynchronizedSecurity { [CmdletBinding(SupportsShouldProcess)] param( [bool]$HeartbeatEnabled, [string[]]$RestrictedZone, [bool]$SyncAppControlEnabled, [ValidateSet(0, 1, 3, 6, 9, 12)] [int]$AppsCleanupTimeframe, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session, [switch]$AcceptLoginDisclaimer ) $bp = $PSBoundParameters $functionalParams = @('HeartbeatEnabled', 'RestrictedZone', 'SyncAppControlEnabled', 'AppsCleanupTimeframe') if (-not @($functionalParams | Where-Object { $bp.ContainsKey($_) })) { throw 'Nothing to change was specified. Provide at least one of -HeartbeatEnabled, -RestrictedZone, -SyncAppControlEnabled or -AppsCleanupTimeframe.' } $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $webAdmin = Connect-SfosWebAdmin -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -SkipCertificateCheck:$params.SkipCertificateCheck -AcceptLoginDisclaimer:$AcceptLoginDisclaimer -ErrorAction Stop $existing = Get-SfosSynchronizedSecurity -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -SkipCertificateCheck:$params.SkipCertificateCheck -AcceptLoginDisclaimer:$AcceptLoginDisclaimer $targetHeartbeat = if ($bp.ContainsKey('HeartbeatEnabled')) { $HeartbeatEnabled } else { $existing.HeartbeatEnabled } # Wrap the whole if/else, not just each branch: an if/else result with zero or one # elements unwraps from an array to a scalar (or $null) on assignment - the same trap # documented for other modules in the project rules. $targetZones = @(if ($bp.ContainsKey('RestrictedZone')) { @($RestrictedZone) } else { @($existing.RestrictedZone) }) $targetSyncAppControl = if ($bp.ContainsKey('SyncAppControlEnabled')) { $SyncAppControlEnabled } else { $existing.SyncAppControlEnabled } $targetCleanup = if ($bp.ContainsKey('AppsCleanupTimeframe')) { $AppsCleanupTimeframe } else { $existing.AppsCleanupTimeframe } if (-not $PSCmdlet.ShouldProcess("Synchronized Security settings on $($params.Firewall)", 'Update')) { return } $body = [ordered]@{ heartbeatEnabled = $targetHeartbeat restrictedZones = @($targetZones | ForEach-Object { @{ name = $_ } }) syncAppControlEnabled = $targetSyncAppControl appsCleanupTimeframe = $targetCleanup } try { $result = Invoke-SfosWebAdminRestRequest -WebAdminSession $webAdmin -Path '/synchronized-security/settings' -Method Patch -Body $body -ErrorAction Stop } catch { throw "Failed to update Synchronized Security settings on $($params.Firewall): $($_.Exception.Message)" } if (-not $result -or $result.PSObject.Properties.Match('heartbeatEnabled').Count -eq 0) { throw "Update of Synchronized Security settings on $($params.Firewall) did not report the expected result: the web admin interface answered without the expected data." } $resultZones = @() if ($result.PSObject.Properties.Match('restrictedZones').Count -gt 0 -and $result.restrictedZones) { $resultZones = @($result.restrictedZones | ForEach-Object { [string]$_.name }) } $mismatch = @() if ([bool]$result.heartbeatEnabled -ne $targetHeartbeat) { $mismatch += 'HeartbeatEnabled' } if ([bool]$result.syncAppControlEnabled -ne $targetSyncAppControl) { $mismatch += 'SyncAppControlEnabled' } if ([int]$result.appsCleanupTimeframe -ne $targetCleanup) { $mismatch += 'AppsCleanupTimeframe' } $expectedZoneSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$targetZones, [StringComparer]::OrdinalIgnoreCase) $resultZoneSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$resultZones, [StringComparer]::OrdinalIgnoreCase) if (-not $expectedZoneSet.SetEquals($resultZoneSet)) { $mismatch += 'RestrictedZone' } if ($mismatch.Count -gt 0) { throw "Synchronized Security settings on $($params.Firewall) did not take effect for: $($mismatch -join ', ')." } } #endregion |