functions/Set-WUGDeviceGroup.ps1

<#
.SYNOPSIS
Updates device group settings in WhatsUp Gold - the write counterpart to Get-WUGDeviceGroup.
 
.DESCRIPTION
The Set-WUGDeviceGroup function modifies one or more device groups via the REST API.
It supports updating group definitions (name, description, parent), assigning roles
(brand, OS, primary, sub-role), triggering poll-now or discovery refresh, and
retrieving a deduplicated list of device IDs across selected groups.
 
Accepts an array of GroupIds and automatically cycles through each one.
Pipeline input is supported for easy chaining with Get-WUGDeviceGroup.
 
Parameter sets:
  Properties PUT /device-groups/{id}/definition (rename, reparent, update description)
  Role PUT /device-groups/{id}/roles/{type} (assign brand, os, primary, or sub-role)
  PollNow PUT /device-groups/{id}/poll-now (trigger polling for all devices)
  Refresh PUT /device-groups/{id}/refresh (trigger discovery refresh)
  ListDeviceIds GET /device-groups/{id}/devices/- (deduplicated device list)
 
.PARAMETER GroupId
One or more device group IDs to update. Accepts pipeline input.
 
.PARAMETER Name
New name for the device group. (Properties set)
 
.PARAMETER Description
New description for the device group. (Properties set)
 
.PARAMETER NewParentGroupId
Move the group under a different parent group. Leave blank to keep current parent. (Properties set)
 
.PARAMETER Role
Switch to assign a role to devices in the group. Requires -RoleType and -Body. (Role set)
 
.PARAMETER RoleType
The type of role to assign. Valid values: brand, os, primary, sub-role.
 
.PARAMETER PollNow
Switch to request the polling service to poll all devices in the group immediately.
 
.PARAMETER Refresh
Switch to request the discovery service to refresh all devices in the group immediately.
 
.PARAMETER RefreshOptions
Array of refresh options. Valid values: brand, os, inventory, history, credentials, allAttributes.
Controls what data is reset using newly collected info. Default: empty (disabled).
 
.PARAMETER DropDataOlderThanHours
Remove data discovered prior to x hours ago. Min value 1, less than 0 ignored, 0 uses system default. Default: -1.
 
.PARAMETER RefreshLimit
Number of devices to refresh. Zero or empty gives the server maximum. Default: 0.
 
.PARAMETER ListDeviceIds
Switch to collect and return a deduplicated list of device IDs across all specified groups.
 
.PARAMETER Body
A raw JSON string for custom payloads (Role and Properties sets).
 
.EXAMPLE
# Rename a device group
Set-WUGDeviceGroup -GroupId 101 -Name "Production Servers"
 
.EXAMPLE
# Update name and description across multiple groups
Set-WUGDeviceGroup -GroupId 101, 102 -Description "Managed by automation"
 
.EXAMPLE
# Move a group under a new parent
Set-WUGDeviceGroup -GroupId 101 -NewParentGroupId 50
 
.EXAMPLE
# Assign a primary role to devices in a group
$body = @{ roleId = "role-abc-123" } | ConvertTo-Json
Set-WUGDeviceGroup -GroupId 101 -Role -RoleType primary -Body $body
 
.EXAMPLE
# Assign a brand role to devices across multiple groups
$body = @{ roleId = "brand-xyz" } | ConvertTo-Json
Set-WUGDeviceGroup -GroupId 101, 102, 103 -Role -RoleType brand -Body $body
 
.EXAMPLE
# Poll all devices in a group immediately
Set-WUGDeviceGroup -GroupId 101 -PollNow
 
.EXAMPLE
# Trigger discovery refresh for multiple groups
Set-WUGDeviceGroup -GroupId 101, 102 -Refresh
 
.EXAMPLE
# Get a deduplicated list of device IDs across groups (useful for piping)
$deviceIds = Set-WUGDeviceGroup -GroupId 101, 102, 103 -ListDeviceIds
 
.EXAMPLE
# Pipeline: rename all groups matching a search
Get-WUGDeviceGroup -SearchValue "Old" | Set-WUGDeviceGroup -Name "Updated"
 
.NOTES
Author: Jason Alberino (jason@wug.ninja)
Reference: https://docs.ipswitch.com/NM/WhatsUpGold2024/02_Guides/rest_api/index.html#tag/DeviceGroup
#>

function Set-WUGDeviceGroup {
    [CmdletBinding(DefaultParameterSetName = 'Properties', SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('id', 'ConfigGroupId')]
        [int[]]$GroupId,

        # -- Properties set (definition) --
        [Parameter(ParameterSetName = 'Properties')]
        [string]$Name,

        [Parameter(ParameterSetName = 'Properties')]
        [string]$Description,

        [Parameter(ParameterSetName = 'Properties')]
        [string]$NewParentGroupId,

        # -- Role set --
        [Parameter(Mandatory = $true, ParameterSetName = 'Role')]
        [switch]$Role,

        [Parameter(Mandatory = $true, ParameterSetName = 'Role')]
        [ValidateSet('brand', 'os', 'primary', 'sub-role')]
        [string]$RoleType,

        [Parameter(Mandatory = $true, ParameterSetName = 'Role')]
        [string]$Body,

        # -- PollNow set --
        [Parameter(Mandatory = $true, ParameterSetName = 'PollNow')]
        [switch]$PollNow,

        [Parameter(ParameterSetName = 'PollNow')]
        [int]$PollNowLimit,

        # -- PollNow / Refresh shared params --
        [Parameter(ParameterSetName = 'PollNow')]
        [Parameter(ParameterSetName = 'Refresh')]
        [switch]$ImmediateChildren,

        [Parameter(ParameterSetName = 'PollNow')]
        [Parameter(ParameterSetName = 'Refresh')]
        [string]$Search,

        # -- Refresh set --
        [Parameter(Mandatory = $true, ParameterSetName = 'Refresh')]
        [switch]$Refresh,

        [Parameter(ParameterSetName = 'Refresh')]
        [switch]$UpdateNamesForInterfaceActiveMonitor,

        [Parameter(ParameterSetName = 'Refresh')]
        [ValidateSet('brand', 'os', 'inventory', 'history', 'credentials', 'allAttributes')]
        [string[]]$RefreshOptions,

        [Parameter(ParameterSetName = 'Refresh')]
        [int]$DropDataOlderThanHours,

        [Parameter(ParameterSetName = 'Refresh')]
        [int]$RefreshLimit,

        # -- ListDeviceIds set --
        [Parameter(Mandatory = $true, ParameterSetName = 'ListDeviceIds')]
        [switch]$ListDeviceIds
    )

    begin {
        Write-Debug "Starting Set-WUGDeviceGroup [$($PSCmdlet.ParameterSetName)]"
        $baseUri  = "${global:WhatsUpServerBaseURI}/api/v1/device-groups"
        $allGroupIds  = [System.Collections.Generic.List[int]]::new()
        $finalOutput  = [System.Collections.Generic.List[object]]::new()
    }

    process {
        foreach ($gid in $GroupId) { $allGroupIds.Add($gid) }
    }

    end {
        # Validate Properties set has at least one property to change
        if ($PSCmdlet.ParameterSetName -eq 'Properties') {
            if (-not $PSBoundParameters.ContainsKey('Name') -and
                -not $PSBoundParameters.ContainsKey('Description') -and
                -not $PSBoundParameters.ContainsKey('NewParentGroupId')) {
                Write-Error "Specify at least one of -Name, -Description, or -NewParentGroupId."
                return
            }
        }

        $total = $allGroupIds.Count
        $current = 0

        # -- ListDeviceIds: collect and deduplicate device IDs across groups --
        if ($PSCmdlet.ParameterSetName -eq 'ListDeviceIds') {
            $deviceIdSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
            foreach ($gid in $allGroupIds) {
                $current++
                Write-Progress -Activity 'Collecting device IDs' -Status "Group $gid ($current of $total)" -PercentComplete (($current / $total) * 100)
                try {
                    $uri    = "${baseUri}/${gid}/devices/-"
                    $result = Get-WUGAPIResponse -Uri $uri -Method 'GET'
                    $devices = if ($result.data.devices) { $result.data.devices } elseif ($result.data) { $result.data } else { @() }
                    foreach ($d in $devices) {
                        $did = if ($d.id) { "$($d.id)" } else { "$d" }
                        [void]$deviceIdSet.Add($did)
                    }
                }
                catch {
                    Write-Error "Error fetching devices for group ${gid}: $_"
                }
            }
            Write-Progress -Activity 'Collecting device IDs' -Completed
            Write-Verbose "Resolved $($deviceIdSet.Count) unique device(s) across $total group(s)."
            return @($deviceIdSet)
        }

        # -- Write operations: loop through groups --
        foreach ($gid in $allGroupIds) {
            $current++
            $pct = [Math]::Round(($current / $total) * 100)

            switch ($PSCmdlet.ParameterSetName) {

                'Properties' {
                    $uri     = "${baseUri}/${gid}/definition"
                    $method  = 'PUT'
                    $action  = 'Update group definition'
                    $bodyHash = @{}
                    if ($PSBoundParameters.ContainsKey('Name'))             { $bodyHash.name          = $Name }
                    if ($PSBoundParameters.ContainsKey('Description'))      { $bodyHash.description   = $Description }
                    if ($PSBoundParameters.ContainsKey('NewParentGroupId')) { $bodyHash.parentGroupId = "$NewParentGroupId" }
                    $requestBody = $bodyHash | ConvertTo-Json -Depth 5
                }

                'Role' {
                    $uri     = "${baseUri}/${gid}/roles/${RoleType}"
                    $method  = 'PUT'
                    $action  = "Assign role ($RoleType)"
                    $requestBody = $Body
                }

                'PollNow' {
                    $pollQueryParams = @()
                    if ($ImmediateChildren) { $pollQueryParams += "immediateChildren=true" }
                    if ($Search) { $pollQueryParams += "search=$([uri]::EscapeDataString($Search))" }
                    if ($PSBoundParameters.ContainsKey('PollNowLimit')) { $pollQueryParams += "limit=$PollNowLimit" }
                    $pollQs = if ($pollQueryParams.Count) { '?' + ($pollQueryParams -join '&') } else { '' }
                    $uri     = "${baseUri}/${gid}/poll-now${pollQs}"
                    $method  = 'PUT'
                    $action  = 'Poll now'
                    $requestBody = $null
                }

                'Refresh' {
                    $refreshQueryParams = @()
                    if ($ImmediateChildren) { $refreshQueryParams += "immediateChildren=true" }
                    if ($Search) { $refreshQueryParams += "search=$([uri]::EscapeDataString($Search))" }
                    if ($UpdateNamesForInterfaceActiveMonitor) { $refreshQueryParams += "updateNamesForInterfaceActiveMonitor=true" }
                    if ($PSBoundParameters.ContainsKey('RefreshOptions')) {
                        foreach ($opt in $RefreshOptions) { $refreshQueryParams += "options=$opt" }
                    }
                    if ($PSBoundParameters.ContainsKey('DropDataOlderThanHours')) {
                        $refreshQueryParams += "dropDataOlderThanHours=$DropDataOlderThanHours"
                    }
                    if ($PSBoundParameters.ContainsKey('RefreshLimit')) {
                        $refreshQueryParams += "limit=$RefreshLimit"
                    }
                    $refreshQs = if ($refreshQueryParams.Count) { '?' + ($refreshQueryParams -join '&') } else { '' }
                    $uri     = "${baseUri}/${gid}/refresh${refreshQs}"
                    $method  = 'PUT'
                    $action  = 'Discovery refresh'
                    $requestBody = $null
                }
            }

            Write-Debug "${action} on group ${gid}. URI: $uri"
            Write-Progress -Activity $action -Status "Group $gid ($current of $total)" -PercentComplete $pct

            if (-not $PSCmdlet.ShouldProcess("Group $gid", $action)) { continue }

            try {
                if ($requestBody) {
                    $result = Get-WUGAPIResponse -Uri $uri -Method $method -Body $requestBody
                }
                else {
                    $result = Get-WUGAPIResponse -Uri $uri -Method $method
                }
                $data   = if ($result.data) { $result.data } else { $result }
                $finalOutput.Add($data)
            }
            catch {
                Write-Error "Error in Set-WUGDeviceGroup (${action}) for group ${gid}: $_"
            }
        }

        Write-Progress -Activity 'Set-WUGDeviceGroup' -Completed
        Write-Debug "Completed Set-WUGDeviceGroup. Results: $($finalOutput.Count)"
        return $finalOutput
    }
}

# SIG # Begin signature block
# MIIVlwYJKoZIhvcNAQcCoIIViDCCFYQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBtbZnikwsGxqEs
# xWpFkqvhPDnks8EQYKGg5ITbQy2yoaCCEdMwggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYaMIIEAqADAgECAhBiHW0M
# UgGeO5B5FSCJIRwKMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD
# VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv
# ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5
# NTlaMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp
# BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0G
# CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjI
# ztNsfvxYB5UXeWUzCxEeAEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NV
# DgFigOMYzB2OKhdqfWGVoYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/3
# 6F09fy1tsB8je/RV0mIk8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05Zw
# mRmTnAO5/arnY83jeNzhP06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm
# +qxp4VqpB3MV/h53yl41aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUe
# dyz8rNyfQJy/aOs5b4s+ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz4
# 4MPZ1f9+YEQIQty/NQd/2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBM
# dlyh2n5HirY4jKnFH/9gRvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQY
# MBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritU
# pimqF6TNDDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNV
# HSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsG
# A1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1
# YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsG
# AQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2Rl
# U2lnbmluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0
# aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURh
# w1aVcdGRP4Wh60BAscjW4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0Zd
# OaWTsyNyBBsMLHqafvIhrCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajj
# cw5+w/KeFvPYfLF/ldYpmlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNc
# WbWDRF/3sBp6fWXhz7DcML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalO
# hOfCipnx8CaLZeVme5yELg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJs
# zkyeiaerlphwoKx1uHRzNyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z7
# 6mKnzAfZxCl/3dq3dUNw4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5J
# KdGvspbOrTfOXyXvmPL6E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHH
# j95Ejza63zdrEcxWLDX6xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2
# Bev6SivBBOHY+uqiirZtg0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/
# L9Uo2bC5a4CH2RwwggY+MIIEpqADAgECAhAHnODk0RR/hc05c892LTfrMA0GCSqG
# SIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0
# ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYw
# HhcNMjYwMjA5MDAwMDAwWhcNMjkwNDIxMjM1OTU5WjBVMQswCQYDVQQGEwJVUzEU
# MBIGA1UECAwLQ29ubmVjdGljdXQxFzAVBgNVBAoMDkphc29uIEFsYmVyaW5vMRcw
# FQYDVQQDDA5KYXNvbiBBbGJlcmlubzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAPN6aN4B1yYWkI5b5TBj3I0VV/peETrHb6EY4BHGxt8Ap+eT+WpEpJyE
# tRYPxEmNJL3A38Bkg7mwzPE3/1NK570ZBCuBjSAn4mSDIgIuXZnvyBO9W1OQs5d6
# 7MlJLUAEufl18tOr3ST1DeO9gSjQSAE5Nql0QDxPnm93OZBon+Fz3CmE+z3MwAe2
# h4KdtRAnCqwM+/V7iBdbw+JOxolpx+7RVjGyProTENIG3pe/hKvPb501lf8uBAAD
# LdjZr5ip8vIWbf857Yw1Bu10nVI7HW3eE8Cl5//d1ribHlzTzQLfttW+k+DaFsKZ
# BBL56l4YAlIVRsrOiE1kdHYYx6IGrEA809R7+TZA9DzGqyFiv9qmJAbL4fDwetDe
# yIq+Oztz1LvEdy8Rcd0JBY+J4S0eDEFIA3X0N8VcLeAwabKb9AjulKXwUeqCJLvN
# 79CJ90UTZb2+I+tamj0dn+IKMEsJ4v4Ggx72sxFr9+6XziodtTg5Luf2xd6+Phha
# mOxF2px9LObhBLLEMyRsCHZIzVZOFKu9BpHQH7ufGB+Sa80Tli0/6LEyn9+bMYWi
# 2ttn6lLOPThXMiQaooRUq6q2u3+F4SaPlxVFLI7OJVMhar6nW6joBvELTJPmANSM
# jDSRFDfHRCdGbZsL/keELJNy+jZctF6VvxQEjFM8/bazu6qYhrA7AgMBAAGjggGJ
# MIIBhTAfBgNVHSMEGDAWgBQPKssghyi47G9IritUpimqF6TNDDAdBgNVHQ4EFgQU
# 6YF0o0D5AVhKHbVocr8GaSIBibAwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQC
# MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIB
# AwIwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EM
# AQQBMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2Vj
# dGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3JsMHkGCCsGAQUFBwEBBG0wazBE
# BggrBgEFBQcwAoY4aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGlj
# Q29kZVNpZ25pbmdDQVIzNi5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNl
# Y3RpZ28uY29tMA0GCSqGSIb3DQEBDAUAA4IBgQAEIsm4xnOd/tZMVrKwi3doAXvC
# wOA/RYQnFJD7R/bSQRu3wXEK4o9SIefye18B/q4fhBkhNAJuEvTQAGfqbbpxow03
# J5PrDTp1WPCWbXKX8Oz9vGWJFyJxRGftkdzZ57JE00synEMS8XCwLO9P32MyR9Z9
# URrpiLPJ9rQjfHMb1BUdvaNayomm7aWLAnD+X7jm6o8sNT5An1cwEAob7obWDM6s
# X93wphwJNBJAstH9Ozs6LwISOX6sKS7CKm9N3Kp8hOUue0ZHAtZdFl6o5u12wy+z
# zieGEI50fKnN77FfNKFOWKlS6OJwlArcbFegB5K89LcE5iNSmaM3VMB2ADV1FEcj
# GSHw4lTg1Wx+WMAMdl/7nbvfFxJ9uu5tNiT54B0s+lZO/HztwXYQUczdsFon3pjs
# Nrsk9ZlalBi5SHkIu+F6g7tWiEv3rtVApmJRnLkUr2Xq2a4nbslUCt4jKs5UX4V1
# nSX8OM++AXoyVGO+iTj7z+pl6XE9Gw/Td6WKKKsxggMaMIIDFgIBATBoMFQxCzAJ
# BgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNl
# Y3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYCEAec4OTRFH+FzTlzz3Yt
# N+swDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZ
# BgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYB
# BAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgKLIY8lDvcEQirRM4vJSwyHs/CJ+Q4xDL
# GK+wo+bVrQgwDQYJKoZIhvcNAQEBBQAEggIASc0EQYqtAYo69FsNyoxelq2eewpb
# lkbJy18ARha+DYuHuDtMvaLN1G2SMts98onY4Pe81HzH4Gs//Ghjvk41sDl3rTyf
# sGRWYgLWe3jgl6RJboDAGk3QqUvx4Y8X3UclQqv0Gt5NBxxYB+g+488hZ/Ix60l+
# J1Zqs9VNGGg6DXGSYxNN3rcSDP9e5X6QDxwU/sib4PrDTzebe5nhhn92qqQeHEu5
# L2aBHoC/bLL7aJOr3EsAmbW8qczZPyzZWqK/fS70KhYREtFtV3wWxGZbRxGfIlgx
# qgxaX3i9BDzwygAR2LjklcQ2E6rfqL7v2N4gY83nvj8lJeYtDegVU9bLJYr4tX5x
# BQVV+KmK6pfqmLNl6KvjzekXo8hX+c1//atA4KnkB3Zek37ZNkZ1lzirGHiVjA04
# FTuyqSWTmt1gXLnfIp1c/R9KF/BUSksPBBoWhGUDVzVx4qras6zwj1DfApNlAU4V
# sLnmiSauXkoCR6DAyKmKVlVGS01F58gdu6fhxIfY0SD6nXvnXEcDzIHhVYjJ4KbH
# /LIPdW+gM3HO7GN9oFMpTPRTIFcxnfynG85V8yD/wCm42+Uh9hPm133iM0qtHMHn
# OVY7NsYFk7HhLVcTFlC3NVf0nFgaDkYU0xJgqHh4aWORxM87K7dnmur79EclT2Df
# gMy4aBQ0oM0/w9I=
# SIG # End signature block