Cayosoft.Graph.psm1

<#
.SYNOPSIS
    Export Dynamic Group rule configuration from Cayosoft Administrator to a CSV file.
.DESCRIPTION
    Export Dynamic Group rule configuration from Cayosoft Administrator to a CSV file.
.PARAMETER FileName
    Specifies the output CSV file name.
.PARAMETER IncludeSchedule
    Specifies if to include rule schedule to the export file.
.PARAMETER ManagedSystem
    Specifies Cayosoft Administrator Dynamic Group rule type to process.
.PARAMETER IncludeRuleOutput
    Specifies if to include Dynamic Group rule output settings.
.PARAMETER CsvSeparator
    Specifies separator symbol for the output CSV file.
.PARAMETER Name
    Specifies one or more Dynamic Group rule names to export. If not set all Dynamic Group rules are exported.
.PARAMETER Container
    Specifies one or more containers with Dynamic Group rules. When not set, all Dynamic Group rules for the specified managed system are exported.
.PARAMETER ShowBanner
    If set cmdlet prints version information.
.EXAMPLE
    C:\PS> Export-CGDynamicGroup -FileName C:\Temp\output.csv -Name "GroupName" -IncludeSchedule
    Description
 
    -----------
     
    This example exports Active Directory Dynamic Group settings with its schedule to the CSV file.
.NOTES
    .
#>

function Export-CGDynamicGroup
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)]
        [string]$FileName,

        [Parameter(Mandatory=$False)]
        [Switch]$IncludeSchedule,

        [Parameter(Mandatory=$False)]
        [ValidateSet("AD", "Microsoft365")]
        [string]$ManagedSystem="AD",

        [Parameter(Mandatory=$False)]
        [Switch]$IncludeRuleOutput,

        [Parameter(Mandatory=$False)]
        [char]$CsvSeparator=',',
         
        [Parameter(Mandatory=$False)]
        [string[]]$Name,
        
        [Parameter(Mandatory=$False)]
        [string[]]$Container,
        
        [Parameter(Mandatory=$False)]
        [switch]$ShowBanner = $False
    )
    
    begin {
        $CGraphModule = "Cayo.Graph.PowerShell.dll"
        $CGraphModulePath = [System.IO.Path]::Combine($PSScriptRoot, $CGraphModule)
        if ([System.IO.File]::Exists($CGraphModulePath) -eq $False) { throw "This cmdlet requires Cayosoft Graph PowerShell Module to be installed on this machine." }
        Import-Module $CGraphModulePath | Out-Null
        [Cayo.Graph.PowerShell.DynamicGroups.ModuleIntializer]::Initialize()
        
        $CAModule = "Cayo.PolicyManager.Common.dll"
        $CAModulePath = [System.IO.Path]::Combine("C:\Program Files\Cayo Software\AdminAssistant", $CAModule)
        if ([System.IO.File]::Exists($CAModulePath) -eq $False) { throw "This cmdlet requires Cayosoft Administrator to be installed on this machine." }
        [Reflection.Assembly]::LoadFrom($CAModulePath) | Out-Null
    }
    
    process {
        if ($ShowBanner -eq $true)
        {
            Write-Host -ForegroundColor Yellow ""
            Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------"
            Write-Host -ForegroundColor Yellow "Version: $($MyInvocation.MyCommand.Module.Version)"
            Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------"
            Write-Host -ForegroundColor Yellow ""
        }
        
        $context =
        @{
            "IncludeSchedule"=$IncludeSchedule;
            "IncludeRuleOutput"=$IncludeRuleOutput;
            "ManagedSystem"=$ManagedSystem;
            "GroupName"=$Name;
            "GroupContainers"=$Container
        }
    
        $rows = @()
        try
        {
            $tgtGroups = LoadDGsAsList
            
            $tgtGroups |
              ?{ ((($context.ManagedSystem -eq "AD" -and $_.TargetExtension -eq "PM.Ext.AD") -or ($context.ManagedSystem -eq "Microsoft365" -and $_.TargetExtension -eq "Office365Ext")) -and ($context.GroupName -eq $null -or ($_.Name -eq ($context.GroupName -eq $_.Name))) -and ($context.GroupContainers -eq $null -or ($null -ne ($_.Labels | ?{ $_ -eq ($context.GroupContainers -eq $_)}))))} |
              %{ (GetDGRows $_ $context) | %{$rows+=$_} }
            $rows | Export-Csv -Path $FileName -NoTypeInformation -Encoding UTF8 -Delimiter $CsvSeparator
        }
        catch
        {
            $err = $_
        }
        
        if ($null -eq $err)
        {
            Log "Export completed successfully. Total exported rules: $($rows.Count)"
        }
        else
        {
            LogError "Export completed with error: $($err)"
        }
    }
}


<#
.SYNOPSIS
    Imports Dynamic Group rule configuration from a CSV file to the Cayosoft Administrator.
.DESCRIPTION
    Imports Dynamic Group rule configuration from a CSV file to the Cayosoft Administrator.
.PARAMETER FileName
    Specifies the input CSV file name.
.PARAMETER IncludeSchedule
    Specifies if to include rule schedule from the import file.
.PARAMETER ManagedSystem
    Specifies Cayosoft Administrator Dynamic Group rule type to process.
.PARAMETER IncludeRuleOutput
    Specifies if to include Dynamic Group rule output settings.
.PARAMETER DefaultParameters
    Specifies membership command parameters that will be overwritten on import.
.PARAMETER CsvSeparator
    Specifies separator symbol for the output CSV file.
.PARAMETER CompareMembers
    If set, future membership calculated by the imported Dynamic Group rule will be compared to the current group membership, and the difference would be reported to the command output.
.PARAMETER ImportMode
    Specifies how to merge Dynamic Group membership commands. For Append mode imported membership commands will be added to the current list of membership commands. For Replace mode imported membership commands will overwrite the existing membership commands.
.PARAMETER Credential
    Specifies connection credentials. Active Directory or Microsoft 365 credentials can be supplied, depending on the ManagedSystem parameter value.
.PARAMETER DC
    Specifies Domain Controller DNS name or Microsoft 365 tenant name.
.PARAMETER MoveToContainer
    Specifies container to move all imported Dynamic Groups.
.PARAMETER ReportFileName
    Specifies file name to store report with the results of the import.
.PARAMETER StopOnScopeResolutionError
    Stop processing the import file if Include or Exclude Explicitly rule scope object could not be resolved.
.PARAMETER ShowBanner
    If set cmdlet prints version information.
.EXAMPLE
    C:\PS> Import-CGDynamicGroup -ImportMode Append -FileName C:\Temp\output.csv -DC "DCName" -Credential $credential
    Description
 
    -----------
     
    This example adds new membership rules from the CSV file to the existing Active Directory Dynamic Group.
.NOTES
    .
#>

function Import-CGDynamicGroup
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)]
        [string]$FileName,

        [Parameter(Mandatory=$False)]
        [Switch]$IncludeSchedule,

        [Parameter(Mandatory=$False)]
        [ValidateSet("AD", "Microsoft365")]
        [string]$ManagedSystem="AD",

        [Parameter(Mandatory=$False)]
        [Switch]$IncludeRuleOutput,

        [Parameter(Mandatory=$False)]
        [Hashtable]$DefaultParameters,

        [Parameter(Mandatory=$False)]
        [char]$CsvSeparator=',',
        
        [Parameter(Mandatory=$False)]
        [Switch]$CompareMembers,
        
        [Parameter(Mandatory=$False)]
        [ValidateSet("Append", "Replace")]
        [string]$ImportMode="Append",
        
        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]$Credential,
        
        [Parameter(Mandatory=$False)]
        [Alias("Tenant")]
        [string]$DC,
        
        [Parameter(Mandatory=$False)]
        [string]$MoveToContainer,
        
        [Parameter(Mandatory=$False)]
        [string]$ReportFileName,
        
        [Parameter(Mandatory=$False)]
        [switch]$StopOnScopeResolutionError,
        
        [Parameter(Mandatory=$False)]
        [switch]$ShowBanner = $False
    )
    
    begin {
        $CGraphModule = "Cayo.Graph.PowerShell.dll"
        $CGraphModulePath = [System.IO.Path]::Combine($PSScriptRoot, $CGraphModule)
        if ([System.IO.File]::Exists($CGraphModulePath) -eq $False) { throw "This cmdlet requires Cayosoft Graph PowerShell Module to be installed on this machine." }
        Import-Module $CGraphModulePath | Out-Null
        [Cayo.Graph.PowerShell.DynamicGroups.ModuleIntializer]::Initialize()
        
        $CAModule = "Cayo.PolicyManager.Common.dll"
        $CAModulePath = [System.IO.Path]::Combine("C:\Program Files\Cayo Software\AdminAssistant", $CAModule)
        if ([System.IO.File]::Exists($CAModulePath) -eq $False) { throw "This cmdlet requires Cayosoft Administrator to be installed on this machine." }
        [Reflection.Assembly]::LoadFrom($CAModulePath) | Out-Null
    }
    
    process {
        if ($ShowBanner -eq $true)
        {
            Write-Host -ForegroundColor Yellow ""
            Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------"
            Write-Host -ForegroundColor Yellow "Version: $($MyInvocation.MyCommand.Module.Version)"
            Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------"
            Write-Host -ForegroundColor Yellow ""
        }
        
        EnsureEncoding $FileName
        EnsureExtension $ManagedSystem
    
        $context =
        @{
            "DnCache"=@{};
            "ObjectCache"=@{};
            "Credential"=$Credential;
            "DC"=$DC;
            "IncludeSchedule"=$IncludeSchedule;
            "IncludeRuleOutput"=$IncludeRuleOutput;
            "ImportMode"=$ImportMode;
            "ManagedSystem"=$ManagedSystem;
            "DefaultParameters"=$DefaultParameters;
            "MoveToContainer"=$MoveToContainer;
            "StopIfIncExpResolveScopeError"=$StopOnScopeResolutionError;
            "ImportParseReport"=@();
        }
    
        $isAD = ($context.ManagedSystem -eq "AD")
        
        $notUpdated = $null
        $groupsUpdated = 0
        
        $err = $null
        try
        {
            if ($isAD -eq $False -and $context.Credential -eq $null){throw "Credentials must be specified."}
            if ($isAD -eq $False -and ([string]::IsNullOrWhiteSpace($context.DC) -eq $True)){throw "Tenant name must be specified."}
          
            $tgtGroups = LoadDGs
            
            $cayoGroups = (ParseDGRowsFromCsv $FileName $context)
            if ($null -ne $cayoGroups) { $groupsUpdated = $cayoGroups.Count }

            $notUpdated = UpdateDGs $cayoGroups $tgtGroups $context
            if ($null -ne $notUpdated) { $groupsUpdated -= $notUpdated.Count }
        }
        catch
        {
            $err = $_
        }
        
        if ([string]::IsNullOrWhiteSpace($ReportFileName) -eq $false)
        {
            DumpImportParseReport $context.ImportParseReport $ReportFileName
            Log "Report location: $($ReportFileName)"
        }
        
        if ($notUpdated -ne $null)
        {
            $notUpdated | %{
                if($_ -eq $null){return}
                LogError "Dynamic Group rule was not updated: multiple rules for the same group were found. Group ID: $($_.TargetGroup.Id) Group name: $($_.TargetGroup.DisplayName)"
            }
        }
        
        if ($null -eq $err)
        {
            Log "Import completed successfully. Total rules updated: $($groupsUpdated)"
        }
        else
        {
            LogError "Import completed with error: $($err)"
        }
        
        if ($null -eq $err -and $CompareMembers -eq $True)
        {
            Log "Membership comparison started"
            if ($isAD -eq $True){CompareAdMembership $cayoGroups $context}
            else{CompareO365Membership $cayoGroups $context}
            Log "Membership comparison completed"
        }
    }
}

<#
.SYNOPSIS
    Deletes Dynamic Group rule configuration from the Cayosoft Administrator based on input CSV file.
.DESCRIPTION
    Deletes Dynamic Group rule configuration from the Cayosoft Administrator based on input CSV file.
.PARAMETER FileName
    Specifies input CSV file name contains Dynamic Group rules to delete. Supports Cayosoft Dynamic Group CSV format only.
.PARAMETER CsvSeparator
    Specifies separator symbol for the output CSV file.
.PARAMETER ShowBanner
    If set cmdlet prints version information.
.EXAMPLE
    C:\PS> Remove-CGDynamicGroup -FileName C:\Temp\output.csv
    Description
 
    -----------
     
    This example deletes Dynamic Group rules specified in the CSV file.
.NOTES
    .
#>

function Remove-CGDynamicGroup
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)]
        [string]$FileName,

        [Parameter(Mandatory=$False)]
        [char]$CsvSeparator=',',
    
        [Parameter(Mandatory=$False)]
        [switch]$ShowBanner = $False
    )
    
    begin {
        $CGraphModule = "Cayo.Graph.PowerShell.dll"
        $CGraphModulePath = [System.IO.Path]::Combine($PSScriptRoot, $CGraphModule)
        if ([System.IO.File]::Exists($CGraphModulePath) -eq $False) { throw "This cmdlet requires Cayosoft Graph PowerShell Module to be installed on this machine." }
        Import-Module $CGraphModulePath | Out-Null
        [Cayo.Graph.PowerShell.DynamicGroups.ModuleIntializer]::Initialize()
        
        $CAModule = "Cayo.PolicyManager.Common.dll"
        $CAModulePath = [System.IO.Path]::Combine("C:\Program Files\Cayo Software\AdminAssistant", $CAModule)
        if ([System.IO.File]::Exists($CAModulePath) -eq $False) { throw "This cmdlet requires Cayosoft Administrator to be installed on this machine." }
        [Reflection.Assembly]::LoadFrom($CAModulePath) | Out-Null
    }
    
    process {
        if ($ShowBanner -eq $true)
        {
            Write-Host -ForegroundColor Yellow ""
            Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------"
            Write-Host -ForegroundColor Yellow "Version: $($MyInvocation.MyCommand.Module.Version)"
            Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------"
            Write-Host -ForegroundColor Yellow ""
        }
        
        $context = @{ }

        $notUpdated = $null
        $groupsUpdated = 0
        
        EnsureEncoding $FileName
       
        try
        {
            $cayoGroups = LoadDGsAsList
            
            $csvGroups = (ParseDGRowsToDeleteFromCsv $FileName $context)
            
            $toDelete = @{}
            if ($null -ne $csvGroups)
            {
                $csvGroups.GetEnumerator() | %{
                    if ($null -eq $_) { return }
                    $csvGroup = $_
                    $cayoGroup = $cayoGroups | ?{ $null -ne $_.TargetGroup -and $_.TargetGroup.Id -eq $csvGroup.Key }
                    if ($null -ne $cayoGroup)
                    {
                        $toDelete[$cayoGroup.RuleId] = $null
                    }
                    else
                    {
                        Log "Group not found: $($_.Key)"
                    }
                }
            }
            
            $groupsUpdated = $toDelete.Count
            $notUpdated = DeleteDGs ($toDelete.GetEnumerator() | %{ $_.Key }) $context
            if ($null -ne $notUpdated)
            {
                $groupsUpdated -= $notUpdated.Count
            }
        }
        catch
        {
            $err = $_
        }
        
        if ($null -eq $err)
        {
            Log "Delete completed successfully. Total rules deleted: $($groupsUpdated)"
        }
        else
        {
            LogError "Deletion completed with error: $($err)"
        }
    }
}

# SIG # Begin signature block
# MIIgKQYJKoZIhvcNAQcCoIIgGjCCIBYCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBVmRkHCVP0Ge1G
# lGV+G0mO9MK+5LRo5M6RJnp8cQRQDqCCGxEwggT+MIID5qADAgECAhANQkrgvjqI
# /2BAIc4UAPDdMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwHhcN
# MjEwMTAxMDAwMDAwWhcNMzEwMTA2MDAwMDAwWjBIMQswCQYDVQQGEwJVUzEXMBUG
# A1UEChMORGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFt
# cCAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwuZhhGfFivUN
# CKRFymNrUdc6EUK9CnV1TZS0DFC1JhD+HchvkWsMlucaXEjvROW/m2HNFZFiWrj/
# ZwucY/02aoH6KfjdK3CF3gIY83htvH35x20JPb5qdofpir34hF0edsnkxnZ2OlPR
# 0dNaNo/Go+EvGzq3YdZz7E5tM4p8XUUtS7FQ5kE6N1aG3JMjjfdQJehk5t3Tjy9X
# tYcg6w6OLNUj2vRNeEbjA4MxKUpcDDGKSoyIxfcwWvkUrxVfbENJCf0mI1P2jWPo
# GqtbsR0wwptpgrTb/FZUvB+hh6u+elsKIC9LCcmVp42y+tZji06lchzun3oBc/gZ
# 1v4NSYS9AQIDAQABo4IBuDCCAbQwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQC
# MAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwQQYDVR0gBDowODA2BglghkgBhv1s
# BwEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMB8G
# A1UdIwQYMBaAFPS24SAd/imu0uRhpbKiJbLIFzVuMB0GA1UdDgQWBBQ2RIaOpLqw
# Zr68KC0dRDbd42p6vDBxBgNVHR8EajBoMDKgMKAuhixodHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLXRzLmNybDAyoDCgLoYsaHR0cDovL2NybDQu
# ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkw
# dzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME8GCCsGAQUF
# BzAChkNodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNz
# dXJlZElEVGltZXN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQBIHNy1
# 6ZojvOca5yAOjmdG/UJyUXQKI0ejq5LSJcRwWb4UoOUngaVNFBUZB3nw0QTDhtk7
# vf5EAmZN7WmkD/a4cM9i6PVRSnh5Nnont/PnUp+Tp+1DnnvntN1BIon7h6JGA078
# 9P63ZHdjXyNSaYOC+hpT7ZDMjaEXcw3082U5cEvznNZ6e9oMvD0y0BvL9WH8dQgA
# dryBDvjA4VzPxBFy5xtkSdgimnUVQvUtMjiB2vRgorq0Uvtc4GEkJU+y38kpqHND
# Udq9Y9YfW5v3LhtPEx33Sg1xfpe39D+E68Hjo0mh+s6nv1bPull2YYlffqe0jmd4
# +TaY4cso2luHpoovMIIFMTCCBBmgAwIBAgIQCqEl1tYyG35B5AXaNpfCFTANBgkq
# hkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB
# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMTYwMTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAw
# WjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy
# ZWQgSUQgVGltZXN0YW1waW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAvdAy7kvNj3/dqbqCmcU5VChXtiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI
# 5Je/YyGQmL8TvFfTw+F+CNZqFAA49y4eO+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+
# wKL1oODeIj8O/36V+/OjuiI+GKwR5PCZA207hXwJ0+5dyJoLVOOoCXFr4M8iEA91
# z3FyTgqt30A6XLdR4aF5FMZNJCMwXbzsPGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmE
# UeaC50ZQ/ZQqLKfkdT66mA+Ef58xFNat1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9
# olMqT4UdxB08r8/arBD13ays6Vb/kwIDAQABo4IBzjCCAcowHQYDVR0OBBYEFPS2
# 4SAd/imu0uRhpbKiJbLIFzVuMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3z
# bcgPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDov
# L29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8E
# ejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1
# cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9
# bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
# MAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAQEAcZUS6VGHVmnN793afKpj
# erN4zwY3QITvS4S/ys8DAv3Fp8MOIEIsr3fzKx8MIVoqtwU0HWqumfgnoma/Capg
# 33akOpMP+LLR2HwZYuhegiUexLoceywh4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQ
# GF+JOGFNYkYkh2OMkVIsrymJ5Xgf1gsUpYDXEkdws3XVk4WTfraSZ/tTYYmo9WuW
# wPRYaQ18yAGxuSh1t5ljhSKMYcp5lH5Z/IwP42+1ASa2bKXuh1Eh5Fhgm7oMLStt
# osR+u8QlK0cCCHxJrhO24XxCQijGGFbPQTS2Zl22dHv1VjMiLyI2skuiSpXY9aaO
# UjCCBVgwggRAoAMCAQICEDmbQN4+QWKelMjw2vBuowIwDQYJKoZIhvcNAQELBQAw
# fDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
# A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQwIgYDVQQD
# ExtTZWN0aWdvIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMjAwNzI4MDAwMDAwWhcN
# MjMwNzI4MjM1OTU5WjCBoTELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTQzMDgyMQ0w
# CwYDVQQIDARPaGlvMRQwEgYDVQQHDAtXZXN0ZXJ2aWxsZTEtMCsGA1UECQwkNDcw
# IE9sZGUgV29ydGhpbmd0b24gUm9hZCwgU3VpdGUgMjAwMRYwFAYDVQQKDA1DYXlv
# c29mdCBJbmMuMRYwFAYDVQQDDA1DYXlvc29mdCBJbmMuMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEAxuvkAYYnxOw86PW0+BUdChz31k1g4KGjE99h2UqV
# ayTI0d/3M5fWUhLGoxDddtVrY0npbSawavn4obqSmML2ZLspPhX10IJ/h7er7mx/
# u2q86A1YXSqLMrXRbioPldbdnTplbVkULvR2QjUXC9wIyC00j7hGhj+RI5frtJXw
# eQXtUGacdfaKoshT5cg8yaat46/l6Vi91Dro2NhWRcK/081iQyJ+lLdqoTsmfiwT
# mrmUEedcyldneA3PthTa9NrfwioGDx3jW5AEvFcsknMByY4NSjJ2I5oZt+80e/tQ
# YaNrMVLDHSVmPYfADGHJlpcKB2zKsFFbzJLx1bH89j+DBQIDAQABo4IBrjCCAaow
# HwYDVR0jBBgwFoAUDuE6qFM6MdWKvsG7rWcaA4WtNA4wHQYDVR0OBBYEFPJ63HfM
# WRV8zYLoKRGjbTW9ozu/MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBKBgNVHSAEQzBB
# MDUGDCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28u
# Y29tL0NQUzAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5z
# ZWN0aWdvLmNvbS9TZWN0aWdvUlNBQ29kZVNpZ25pbmdDQS5jcmwwcwYIKwYBBQUH
# AQEEZzBlMD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3Rp
# Z29SU0FDb2RlU2lnbmluZ0NBLmNydDAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Au
# c2VjdGlnby5jb20wHAYDVR0RBBUwE4ERaW5mb0BjYXlvc29mdC5jb20wDQYJKoZI
# hvcNAQELBQADggEBACDAJy/sZI8y6BqvC9vixj2U3mySLcutLD0KxG3x71z4LJs8
# bMU8qTVo76fYIL8ZbfgpNvRa9Ba8HvfdyPP/Ih5aG1DwC/jbtPW6q813q9oh3TrS
# lY1AkGnSVeK5Md3dB3B+5md7A4CX/cB9l8P98kjn4o2Ydoo63QVDTqwUvy7skXx8
# 9nrPiRsYgDIwFknSrJfd3s6Tr5KPfttXltwt49iPpEyMQKtHpg053sONbr1eMew3
# TfGbaE+qQw1wSRf04YSBcOrA7S2s0yDuGKQWg0o7TYyup6uXEM+WHYWkOJMquJYj
# yF4wz71q3dEt7vh7QzgYVDCyVMN7f8XSfRwGQz4wggWBMIIEaaADAgECAhA5ckQ6
# +SK3UdfTbBDdMTWVMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYD
# VQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNV
# BAoMEUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUg
# U2VydmljZXMwHhcNMTkwMzEyMDAwMDAwWhcNMjgxMjMxMjM1OTU5WjCBiDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBD
# aXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVT
# RVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3
# DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBN
# CS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR
# +OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn
# RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6v
# BZY1H1dat//O+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZT
# mzNg95S+UzeQc0PzMsNT79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M
# 0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1
# XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn
# 4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexD
# JtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZG
# SlCBst6+eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt
# 7y+CDwIDAQABo4HyMIHvMB8GA1UdIwQYMBaAFKARCiM+lvEH7OKvKe+CpX/QMKS0
# MB0GA1UdDgQWBBRTeb9aqitKz1SA4dibwJ3ysgNmyzAOBgNVHQ8BAf8EBAMCAYYw
# DwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwQwYDVR0fBDwwOjA4
# oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2
# aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggEBABiHUdx0IT2ciuAntzPQ
# Lszs8ObLXhHeIm+bdY6ecv7k1v6qH5yWLe8DSn6u9I1vcjxDO8A/67jfXKqpxq7y
# /Njuo3tD9oY2fBTgzfT3P/7euLSK8JGW/v1DZH79zNIBoX19+BkZyUIrE79Yi7qk
# omYEdoiRTgyJFM6iTckys7roFBq8cfFb8EELmAAKIgMQ5Qyx+c2SNxntO/HkOrb5
# RRMmda+7qu8/e3c70sQCkT0ZANMXXDnbP3sYDUXNk4WWL13fWRZPP1G91UUYP+1K
# jugGYXQjFrUNUHMnREd/EF2JKmuFMRTE6KlqTIC8anjPuH+OdnKZDJ3+15EIFqGj
# X5UwggX1MIID3aADAgECAhAdokgwb5smGNCC4JZ9M9NqMA0GCSqGSIb3DQEBDAUA
# MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxML
# SmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwG
# A1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
# ODExMDIwMDAwMDBaFw0zMDEyMzEyMzU5NTlaMHwxCzAJBgNVBAYTAkdCMRswGQYD
# VQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBT
# aWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhiKNMoV6
# GJ9J8JYvYwgeLdx8nxTP4ya2JWYpQIZURnQxYsUQ7bKHJ6aZy5UwwFb1pHXGqQ5Q
# YqVRkRBq4Etirv3w+Bisp//uLjMg+gwZiahse60Aw2Gh3GllbR9uJ5bXl1GGpvQn
# 5Xxqi5UeW2DVftcWkpwAL2j3l+1qcr44O2Pej79uTEFdEiAIWeg5zY/S1s8GtFcF
# tk6hPldrH5i8xGLWGwuNx2YbSp+dgcRyQLXiX+8LRf+jzhemLVWwt7C8VGqdvI1W
# U8bwunlQSSz3A7n+L2U18iLqLAevRtn5RhzcjHxxKPP+p8YU3VWRbooRDd8GJJV9
# D6ehfDrahjVh0wIDAQABo4IBZDCCAWAwHwYDVR0jBBgwFoAUU3m/WqorSs9UgOHY
# m8Cd8rIDZsswHQYDVR0OBBYEFA7hOqhTOjHVir7Bu61nGgOFrTQOMA4GA1UdDwEB
# /wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMD
# BggrBgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/
# aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRp
# b25BdXRob3JpdHkuY3JsMHYGCCsGAQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0
# cDovL2NydC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0
# MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3
# DQEBDAUAA4ICAQBNY1DtRzRKYaTb3moqjJvxAAAeHWJ7Otcywvaz4GOz+2EAiJob
# bRAHBE++uOqJeCLrD0bs80ZeQEaJEvQLd1qcKkE6/Nb06+f3FZUzw6GDKLfeL+SU
# 94Uzgy1KQEi/msJPSrGPJPSzgTfTt2SwpiNqWWhSQl//BOvhdGV5CPWpk95rcUCZ
# lrp48bnI4sMIFrGrY1rIFYBtdF5KdX6luMNstc/fSnmHXMdATWM19jDTz7UKDgsE
# f6BLrrujpdCEAJM+U100pQA1aWy+nyAlEA0Z+1CQYb45j3qOTfafDh7+B1ESZoMm
# GUiVzkrJwX/zOgWb+W/fiH/AI57SHkN6RTHBnE2p8FmyWRnoao0pBAJ3fEtLzXC+
# OrJVWng+vLtvAxAldxU0ivk2zEOS5LpP8WKTKCVXKftRGcehJUBqhFfGsp2xvBwK
# 2nxnfn0u6ShMGH7EezFBcZpLKewLPVdQ0srd/Z4FUeVEeN0B3rF1mA1UJP3wTuPi
# +IO9crrLPTru8F4XkmhtyGH5pvEqCgulufSe7pgyBYWe6/mDKdPGLH29OncuizdC
# oGqC7TtKqpQQpOEN+BfFtlp5MxiS47V1+KHpjgolHuQe8Z9ahyP/n6RRnvs5gBHN
# 27XEp6iAb+VT1ODjosLSWxr6MiYtaldwHDykWC6j81tLB9wyWfOHpxptWDGCBG4w
# ggRqAgEBMIGQMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNo
# ZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRl
# ZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBAhA5m0DePkFi
# npTI8NrwbqMCMA0GCWCGSAFlAwQCAQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJ
# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB
# gjcCARUwLwYJKoZIhvcNAQkEMSIEIDKAQ5nkbX2YbZIl40MLjpz0sIg5HC1twa51
# 2fJkg72gMA0GCSqGSIb3DQEBAQUABIIBAJs+gu3uYXRdzNgynyubf4JCaJH7h8Ui
# C762p5QbxcP5K2HNV97vDSaOmek5Oka8MJCF0M9XRtrqgZvWrPoFar5BmdHitoMT
# Bz5phn6KLv51w3qlWBfgbPuJ4HU8PyADEDGrH94GwBzcB9lp58jYoDJ8/WAL/7wb
# 4O+pwK2ZETCilNb6TEdBMOfPEyLNnWzgVZLvzaXJlDh19I7A1vQjsPks2exJTkgE
# 5HH/sf2sI4YvLs6O+cIFgIVgviO/g3Gk6pcBIeBcuH5YS/9JYezfSubi+KeCjZ75
# NsDqylto8Zsgfj5Djge+WbgKBCVx1Ow0nUpGWFRvhZBVE5Lb9Zh8wyyhggIwMIIC
# LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G
# A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ
# DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMDMwMzA4NDY0N1owLwYJKoZI
# hvcNAQkEMSIEINmOJ4ARDLCVsQMThpC4smjOXbA6O2iSdsxtQNeZkfuvMA0GCSqG
# SIb3DQEBAQUABIIBAHvFXI870YIdPfHEKkPA0CBiJi0BHHAOwekSOn7J63vtVIeE
# m/xSgvEdg3No+cbWat3CueZqlnlIafTqbDRVLJdMPLRANYAAji+Zsj66UOl5awIX
# dUW8mmy9MXM2yDLQhpsMn1EBWjOF0WxCljLHKW2PQolqq15Eh0fqNJnQ2oKJ24hp
# qK5AOR90DElei32YzbjSKnS/bhozNed9nCIRnAY4VNM/bfReAO25W8gj5MMqzT3z
# tKQyncu2f5X3gt3qAJ4u7Ch/0tZbjGhYQY8pkIGKPFsMI6w2CSksk8X7pDKKYIR4
# sijdXdOJS5F9cd278NL7zzbuwrALC4qB0l1+j/U=
# SIG # End signature block