helpers/GuestConfigurationPolicy.psm1
Set-StrictMode -Version latest $ErrorActionPreference = 'Stop' Import-Module $PSScriptRoot/DscOperations.psm1 -Force function Update-PolicyParameter { [CmdletBinding()] param ( [parameter(Mandatory = $false)] [Hashtable[]] $Parameter ) $updatedParameterInfo = @() foreach ($parmInfo in $Parameter) { $param = @{ } $param['Type'] = 'string' if ($parmInfo.Contains('Name')) { $param['ReferenceName'] = $parmInfo.Name } else { Throw "Policy parameter is missing a mandatory property 'Name'. Please make sure that parameter name is specified in Policy parameter." } if ($parmInfo.Contains('DisplayName')) { $param['DisplayName'] = $parmInfo.DisplayName } else { Throw "Policy parameter is missing a mandatory property 'DisplayName'. Please make sure that parameter display name is specified in Policy parameter." } if ($parmInfo.Contains('Description')) { $param['Description'] = $parmInfo.Description } if (-not $parmInfo.Contains('ResourceType')) { Throw "Policy parameter is missing a mandatory property 'ResourceType'. Please make sure that configuration resource type is specified in Policy parameter." } elseif (-not $parmInfo.Contains('ResourceId')) { Throw "Policy parameter is missing a mandatory property 'ResourceId'. Please make sure that configuration resource Id is specified in Policy parameter." } else { $param['MofResourceReference'] = "[$($parmInfo.ResourceType)]$($parmInfo.ResourceId)" } if ($parmInfo.Contains('ResourcePropertyName')) { $param['MofParameterName'] = $parmInfo.ResourcePropertyName } else { Throw "Policy parameter is missing a mandatory property 'ResourcePropertyName'. Please make sure that configuration resource property name is specified in Policy parameter." } if ($parmInfo.Contains('DefaultValue')) { $param['DefaultValue'] = $parmInfo.DefaultValue } if ($parmInfo.Contains('AllowedValues')) { $param['AllowedValues'] = $parmInfo.AllowedValues } $updatedParameterInfo += $param; } return $updatedParameterInfo } function Test-GuestConfigurationMofResourceDependencies { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Path ) $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($Path, 4) $externalResources = @() for ($i = 0; $i -lt $resourcesInMofDocument.Count; $i++) { if ($resourcesInMofDocument[$i].CimInstanceProperties.Name -contains 'ModuleName' -and $resourcesInMofDocument[$i].ModuleName -ne 'GuestConfiguration') { if ($resourcesInMofDocument[$i].ModuleName -ieq 'PsDesiredStateConfiguration') { Throw "'PsDesiredStateConfiguration' module is not supported by GuestConfiguration. Please use 'PSDscResources' module instead of 'PsDesiredStateConfiguration' module in DSC configuration." } $configurationName = $resourcesInMofDocument[$i].ConfigurationName Write-Warning -Message "The configuration '$configurationName' is using one or more resources outside of the GuestConfiguration module. Please make sure these resources work with PowerShell Core" break } } } function Copy-DscResources { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $MofDocumentPath, [Parameter(Mandatory = $true)] [String] $Destination ) $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($MofDocumentPath, 4) Write-Verbose "Copy DSC resources ..." $modulePath = New-Item -ItemType Directory -Force -Path (Join-Path $Destination 'Modules') $guestConfigModulePath = New-Item -ItemType Directory -Force -Path (Join-Path $modulePath 'GuestConfiguration') try { $latestModule = @() $latestModule += Get-Module GuestConfiguration $latestModule += Get-Module GuestConfiguration -ListAvailable $latestModule = ($latestModule | Sort-Object Version)[0] } catch { write-error 'unable to find the GuestConfiguration module either as an imported module or in $env:PSModulePath' } Copy-Item "$($latestModule.ModuleBase)/*" $guestConfigModulePath -Recurse -Force $modulesToCopy = @{ } $resourcesInMofDocument | ForEach-Object { # if resource is not a GuestConfiguration module resource. if ($_.CimInstanceProperties.Name -contains 'ModuleName' -and $_.CimInstanceProperties.Name -contains 'ModuleVersion') { $modulesToCopy[$_.CimClass.CimClassName] = @{ModuleName = $_.ModuleName; ModuleVersion = $_.ModuleVersion } } } $modulesToCopy.Values | ForEach-Object { $moduleToCopy = Get-Module -FullyQualifiedName @{ModuleName = $_.ModuleName; RequiredVersion = $_.ModuleVersion } -ListAvailable if ($null -ne $moduleToCopy) { $moduleToCopyPath = New-Item -ItemType Directory -Force -Path (Join-Path $modulePath $_.ModuleName) Copy-Item "$($moduleToCopy.ModuleBase)/*" $moduleToCopyPath -Recurse -Force } else { Write-Error "Module $($_.ModuleName) version $($_.ModuleVersion) could not be found in `$env:PSModulePath" } } # Copy binary resources. $nativeResourcePath = New-Item -ItemType Directory -Force -Path (Join-Path $modulePath 'DscNativeResources') $resources = Get-DscResource -Module GuestConfiguration $resources | ForEach-Object { if ($_.ImplementedAs -eq 'Binary') { $binaryResourcePath = Join-Path (Join-Path $latestModule.ModuleBase 'DscResources') $_.ResourceType Get-ChildItem $binaryResourcePath/* -Include *.sh | ForEach-Object { Convert-FileToUnixLineEndings -FilePath $_ } Copy-Item $binaryResourcePath $nativeResourcePath -Recurse -Force } } # Remove DSC binaries from package. $binaryPath = Join-Path $guestConfigModulePath 'bin' Remove-Item -Path $binaryPath -Force -Recurse -ErrorAction 'SilentlyContinue' | Out-Null } function Copy-ChefInspecDependencies { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $PackagePath, [Parameter(Mandatory = $true)] [String] $Configuration, [string] $ChefInspecProfilePath ) # Copy Chef resource and profiles. $modulePath = Join-Path $PackagePath 'Modules' $nativeResourcePath = New-Item -ItemType Directory -Force -Path (Join-Path $modulePath 'DscNativeResources') $missingDependencies = @() $chefInspecProfiles = @() $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($Configuration, 4) $usingChefResource = $false $resourcesInMofDocument | ForEach-Object { if ($_.CimClass.CimClassName -eq 'MSFT_ChefInSpecResource') { $usingChefResource = $true if ([string]::IsNullOrEmpty($ChefInspecProfilePath)) { Throw "Failed to find Chef Inspec profile(s) '$($_.CimInstanceProperties['Name'].Value)'. Please use ChefInspecProfilePath parameter to specify profile path." } $inspecProfilePath = Join-Path $ChefInspecProfilePath $_.CimInstanceProperties['Name'].Value if (-not (Test-Path $inspecProfilePath)) { $missingDependencies += $_.CimInstanceProperties['Name'].Value } else { $chefInspecProfiles += $inspecProfilePath } $chefResourcePath = Join-Path $nativeResourcePath 'MSFT_ChefInSpecResource' Copy-Item $chefResourcePath/install_inspec.sh $modulePath -Force -ErrorAction SilentlyContinue } } if ($usingChefResource) { if ($missingDependencies.Length) { Throw "Failed to find Chef Inspec profile for '$($missingDependencies -join ',')'. Please make sure profile is present on $ChefInspecProfilePath path." } else { $chefInspecProfiles | ForEach-Object { Copy-Item $_ $modulePath -Recurse -Force -ErrorAction SilentlyContinue } } } else { if (-not [string]::IsNullOrEmpty($ChefInspecProfilePath)) { Throw "ChefInspecProfilePath parameter is supported only for Linux packages." } } } function Convert-FileToUnixLineEndings { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $FilePath ) $fileContent = Get-Content -Path $FilePath -Raw $fileContentWithLinuxLineEndings = $fileContent.Replace("`r`n", "`n") $null = Set-Content -Path $FilePath -Value $fileContentWithLinuxLineEndings -Force Write-Verbose -Message "Converted the file at the path '$FilePath' to Unix line endings." } function Update-MofDocumentParameters { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Path, [parameter(Mandatory = $false)] [Hashtable[]] $Parameter ) if ($Parameter.Count -eq 0) { return } $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($Path, 4) foreach ($parmInfo in $Parameter) { if (-not $parmInfo.Contains('ResourceType')) { Throw "Policy parameter is missing a mandatory property 'ResourceType'. Please make sure that configuration resource type is specified in configuration parameter." } if (-not $parmInfo.Contains('ResourceId')) { Throw "Policy parameter is missing a mandatory property 'ResourceId'. Please make sure that configuration resource Id is specified in configuration parameter." } if (-not $parmInfo.Contains('ResourcePropertyName')) { Throw "Policy parameter is missing a mandatory property 'ResourcePropertyName'. Please make sure that configuration resource property name is specified in configuration parameter." } if (-not $parmInfo.Contains('ResourcePropertyValue')) { Throw "Policy parameter is missing a mandatory property 'ResourcePropertyValue'. Please make sure that configuration resource property value is specified in configuration parameter." } $resourceId = "[$($parmInfo.ResourceType)]$($parmInfo.ResourceId)" if (($resourcesInMofDocument | Where-Object { ` ($_.CimInstanceProperties.Name -contains 'ResourceID') ` -and ($_.CimInstanceProperties['ResourceID'].Value -eq $resourceId) ` -and ($_.CimInstanceProperties.Name -contains $parmInfo.ResourcePropertyName) ` }) -eq $null) { Throw "Failed to find parameter reference in the configuration '$Path'. Please make sure parameter with ResourceType:'$($parmInfo.ResourceType)', ResourceId:'$($parmInfo.ResourceId)' and ResourcePropertyName:'$($parmInfo.ResourcePropertyName)' exist in the configuration." } Write-Verbose "Updating configuration parameter for $resourceId ..." $resourcesInMofDocument | ForEach-Object { if (($_.CimInstanceProperties.Name -contains 'ResourceID') -and ($_.CimInstanceProperties['ResourceID'].Value -eq $resourceId)) { $item = $_.CimInstanceProperties.Item($parmInfo.ResourcePropertyName) $item.Value = $parmInfo.ResourcePropertyValue } } } Write-Verbose "Saving configuration file '$Path' with updated parameters ..." $content = "" for ($i = 0; $i -lt $resourcesInMofDocument.Count; $i++) { $resourceClassName = $resourcesInMofDocument[$i].CimSystemProperties.ClassName $content += "instance of $resourceClassName" if ($resourceClassName -ne 'OMI_ConfigurationDocument') { $content += ' as $' + "$resourceClassName$i" } $content += "`n{`n" $resourcesInMofDocument[$i].CimInstanceProperties | ForEach-Object { $content += " $($_.Name)" if ($_.CimType -eq 'StringArray') { $content += " = {""$($_.Value -replace '[""\\]','\$&')""}; `n" } else { $content += " = ""$($_.Value -replace '[""\\]','\$&')""; `n" } } $content += "};`n" ; } $content | Out-File $Path } function Get-GuestConfigurationMofContent { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Name, [Parameter(Mandatory = $true)] [String] $Path ) Write-Verbose "Parsing Configuration document '$Path'" $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($Path, 4) # Set the profile path for Chef resource $resourcesInMofDocument | ForEach-Object { if ($_.CimClass.CimClassName -eq 'MSFT_ChefInSpecResource') { $profilePath = "$Name/Modules/$($_.Name)" $item = $_.CimInstanceProperties.Item('GithubPath') if ($item -eq $null) { $item = [Microsoft.Management.Infrastructure.CimProperty]::Create('GithubPath', $profilePath, [Microsoft.Management.Infrastructure.CimFlags]::Property) $_.CimInstanceProperties.Add($item) } else { $item.Value = $profilePath } } } return $resourcesInMofDocument } function Save-GuestConfigurationMofDocument { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Name, [Parameter(Mandatory = $true)] [String] $SourcePath, [Parameter(Mandatory = $true)] [String] $DestinationPath ) $resourcesInMofDocument = Get-GuestConfigurationMofContent -Name $Name -Path $SourcePath # if mof contains Chef resource if ($resourcesInMofDocument.CimSystemProperties.ClassName -contains 'MSFT_ChefInSpecResource') { Write-Verbose "Serialize DSC document to $DestinationPath path ..." $content = "" for ($i = 0; $i -lt $resourcesInMofDocument.Count; $i++) { $resourceClassName = $resourcesInMofDocument[$i].CimSystemProperties.ClassName $content += "instance of $resourceClassName" if ($resourceClassName -ne 'OMI_ConfigurationDocument') { $content += ' as $' + "$resourceClassName$i" } $content += "`n{`n" $resourcesInMofDocument[$i].CimInstanceProperties | ForEach-Object { $content += " $($_.Name)" if ($_.CimType -eq 'StringArray') { $content += " = {""$($_.Value -replace '[""\\]','\$&')""}; `n" } else { $content += " = ""$($_.Value -replace '[""\\]','\$&')""; `n" } } $content += "};`n" ; } $content | Out-File $DestinationPath } else { Write-Verbose "Copy DSC document to $DestinationPath path ..." Copy-Item $SourcePath $DestinationPath } } function Format-Json { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory = $true)] [String] $Json ) $indent = 0 $jsonLines = $Json -Split '\n' $formattedLines = @() $previousLine = '' foreach ($line in $jsonLines) { $skipAddingLine = $false if ($line -match '^\s*\}\s*' -or $line -match '^\s*\]\s*') { # This line contains ] or }, decrement the indentation level $indent-- } $formattedLine = (' ' * $indent * 4) + $line.TrimStart().Replace(': ', ': ') if ($line -match '\s*".*"\s*:\s*\[' -or $line -match '\s*".*"\s*:\s*\{' -or $line -match '^\s*\{\s*' -or $line -match '^\s*\[\s*') { # This line contains [ or {, increment the indentation level $indent++ } if ($previousLine.Trim().EndsWith("{")) { if ($formattedLine.Trim() -in @("}", "},")) { $newLine = "$($previousLine.TrimEnd())$($formattedLine.Trim())" #Write-Verbose -Message "FOUND SHORTENED LINE: $newLine" $formattedLines[($formattedLines.Count - 1)] = $newLine $previousLine = $newLine $skipAddingLine = $true } } if ($previousLine.Trim().EndsWith("[")) { if ($formattedLine.Trim() -in @("]", "],")) { $newLine = "$($previousLine.TrimEnd())$($formattedLine.Trim())" #Write-Verbose -Message "FOUND SHORTENED LINE: $newLine" $formattedLines[($formattedLines.Count - 1)] = $newLine $previousLine = $newLine $skipAddingLine = $true } } if (-not $skipAddingLine -and -not [String]::IsNullOrWhiteSpace($formattedLine)) { $previousLine = $formattedLine $formattedLines += $formattedLine } } $formattedJson = $formattedLines -join "`n" return $formattedJson } function New-GuestConfigurationDeployPolicyDefinition { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $FileName, [Parameter(Mandatory = $true)] [String] $FolderPath, [Parameter(Mandatory = $true)] [String] $DisplayName, [Parameter(Mandatory = $true)] [String] $Description, [Parameter(Mandatory = $true)] [String] $ConfigurationName, [Parameter(Mandatory = $true)] [version] $ConfigurationVersion, [Parameter(Mandatory = $true)] [String] $ContentUri, [Parameter(Mandatory = $true)] [String] $ContentHash, [Parameter(Mandatory = $true)] [String] $ReferenceId, [Parameter()] [Hashtable[]] $ParameterInfo, [Parameter()] [String] $Guid, [Parameter()] [ValidateSet('Windows', 'Linux')] [String] $Platform = 'Windows', [Parameter()] [ValidateSet('Microsoft.Compute', 'Microsoft.HybridCompute')] [String] $RPName = 'Microsoft.Compute', [Parameter()] [ValidateSet('virtualMachines', 'machines')] [String] $ResourceName = 'virtualMachines', [Parameter()] [bool] $UseCertificateValidation = $false, [Parameter()] [String] $Category = 'Guest Configuration' ) if (-not [String]::IsNullOrEmpty($Guid)) { $deployPolicyGuid = $Guid } else { $deployPolicyGuid = [Guid]::NewGuid() } $filePath = Join-Path -Path $FolderPath -ChildPath $FileName $deployPolicyContentHashtable = [Ordered]@{ properties = [Ordered]@{ displayName = $DisplayName policyType = 'Custom' mode = 'Indexed' description = $Description metadata = [Ordered]@{ category = $Category requiredProviders = @( 'Microsoft.GuestConfiguration' ) } } } $policyRuleHashtable = [Ordered]@{ if = [Ordered]@{ allOf = @( [Ordered]@{ field = 'type' equals = $RPName + '/' + $ResourceName } ) } then = [Ordered]@{ effect = 'deployIfNotExists' details = [Ordered]@{ type = 'Microsoft.GuestConfiguration/guestConfigurationAssignments' name = $ConfigurationName roleDefinitionIds = @('/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c') } } } $deploymentHashtable = [Ordered]@{ properties = [Ordered]@{ mode = 'incremental' parameters = [Ordered]@{ vmName = [Ordered]@{ value = "[field('name')]" } location = [Ordered]@{ value = "[field('location')]" } configurationName = [Ordered]@{ value = $ConfigurationName } contentUri = [Ordered]@{ value = $ContentUri } contentHash = [Ordered]@{ value = $ContentHash } } template = [Ordered]@{ '$schema' = 'https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#' contentVersion = '1.0.0.0' parameters = [Ordered]@{ vmName = [Ordered]@{ type = 'string' } location = [Ordered]@{ type = 'string' } configurationName = [Ordered]@{ type = 'string' } contentUri = [Ordered]@{ type = 'string' } contentHash = [Ordered]@{ type = 'string' } } resources = @() } } } $guestConfigurationAssignmentHashtable = [Ordered]@{ apiVersion = '2018-11-20' type = $RPName + '/' + $ResourceName + '/providers/guestConfigurationAssignments' name = "[concat(parameters('vmName'), '/Microsoft.GuestConfiguration/', parameters('configurationName'))]" location = "[parameters('location')]" properties = [Ordered]@{ guestConfiguration = [Ordered]@{ name = "[parameters('configurationName')]" contentUri = "[parameters('contentUri')]" contentHash = "[parameters('contentHash')]" version = $ConfigurationVersion.ToString() } } } if ($Platform -ieq 'Windows') { $policyRuleHashtable['if']['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' in = @( 'esri', 'incredibuild', 'MicrosoftDynamicsAX', 'MicrosoftSharepoint', 'MicrosoftVisualStudio', 'MicrosoftWindowsDesktop', 'MicrosoftWindowsServerHPCPack' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'MicrosoftWindowsServer' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '2008*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'MicrosoftSQLServer' }, [Ordered]@{ field = $RPName + '/imageSKU' notEquals = 'SQL2008R2SP3-WS2008R2SP1' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-dsvm' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'dsvm-windows' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-ads' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'standard-data-science-vm', 'windows-data-science-vm' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'batch' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'rendering-windows2016' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'center-for-internet-security-inc' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'cis-windows-server-201*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'pivotal' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'bosh-windows-server*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'cloud-infrastructure-services' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'ad*' } ) } ) } ) $guestConfigurationExtensionHashtable = [Ordered]@{ apiVersion = '2015-05-01-preview' name = "[concat(parameters('vmName'), '/AzurePolicyforWindows')]" type = 'Microsoft.Compute/virtualMachines/extensions' location = "[parameters('location')]" properties = [Ordered]@{ publisher = 'Microsoft.GuestConfiguration' type = 'ConfigurationforWindows' typeHandlerVersion = '1.1' autoUpgradeMinorVersion = $true settings = @{ } protectedSettings = @{ } } dependsOn = @( "[concat('Microsoft.Compute/virtualMachines/',parameters('vmName'),'/providers/Microsoft.GuestConfiguration/guestConfigurationAssignments/',parameters('configurationName'))]" ) } } elseif ($Platform -ieq 'Linux') { $policyRuleHashtable['if']['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' in = @( 'microsoft-aks', 'AzureDatabricks', 'qubole-inc', 'datastax', 'couchbase', 'scalegrid', 'checkpoint', 'paloaltonetworks' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'OpenLogic' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'CentOS*' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'RHEL' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'osa' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'credativ' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'Debian' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '7*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'Suse' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'SLES*' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '11*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'Canonical' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'UbuntuServer' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '12*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-dsvm' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'linux-data-science-vm-ubuntu', 'azureml' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'cloudera' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'cloudera-centos-os' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'cloudera' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'cloudera-altus-centos-os' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-ads' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'linux*' } ) } ) } ) $guestConfigurationExtensionHashtable = [Ordered]@{ apiVersion = '2015-05-01-preview' name = "[concat(parameters('vmName'), '/AzurePolicyforLinux')]" type = 'Microsoft.Compute/virtualMachines/extensions' location = "[parameters('location')]" properties = [Ordered]@{ publisher = 'Microsoft.GuestConfiguration' type = 'ConfigurationforLinux' typeHandlerVersion = '1.0' autoUpgradeMinorVersion = $true } dependsOn = @( "[concat('Microsoft.Compute/virtualMachines/',parameters('vmName'),'/providers/Microsoft.GuestConfiguration/guestConfigurationAssignments/',parameters('configurationName'))]" ) } } else { throw "The specified platform '$Platform' is not currently supported by this script." } $existenceConditionList = @() # Handle adding parameters if needed if ($null -ne $ParameterInfo -and $ParameterInfo.Count -gt 0) { $parameterValueConceatenatedStringList = @() if (-not $deployPolicyContentHashtable['properties'].Contains('parameters')) { $deployPolicyContentHashtable['properties']['parameters'] = [Ordered]@{ } } if (-not $guestConfigurationAssignmentHashtable['properties']['guestConfiguration'].Contains('configurationParameter')) { $guestConfigurationAssignmentHashtable['properties']['guestConfiguration']['configurationParameter'] = @() } foreach ($currentParameterInfo in $ParameterInfo) { $deployPolicyContentHashtable['properties']['parameters'] += [Ordered]@{ $currentParameterInfo.ReferenceName = [Ordered]@{ type = $currentParameterInfo.Type metadata = [Ordered]@{ displayName = $currentParameterInfo.DisplayName } } } if ($currentParameterInfo.ContainsKey('Description')) { $deployPolicyContentHashtable['properties']['parameters'][$currentParameterInfo.ReferenceName]['metadata']['description'] = $currentParameterInfo['Description'] } if ($currentParameterInfo.ContainsKey('DefaultValue')) { $deployPolicyContentHashtable['properties']['parameters'][$currentParameterInfo.ReferenceName] += [Ordered]@{ defaultValue = $currentParameterInfo.DefaultValue } } if ($currentParameterInfo.ContainsKey('AllowedValues')) { $deployPolicyContentHashtable['properties']['parameters'][$currentParameterInfo.ReferenceName] += [Ordered]@{ allowedValues = $currentParameterInfo.AllowedValues } } if ($currentParameterInfo.ContainsKey('DeploymentValue')) { $deploymentHashtable['properties']['parameters'] += [Ordered]@{ $currentParameterInfo.ReferenceName = [Ordered]@{ value = $currentParameterInfo.DeploymentValue } } } else { $deploymentHashtable['properties']['parameters'] += [Ordered]@{ $currentParameterInfo.ReferenceName = [Ordered]@{ value = "[parameters('$($currentParameterInfo.ReferenceName)')]" } } } $deploymentHashtable['properties']['template']['parameters'] += [Ordered]@{ $currentParameterInfo.ReferenceName = [Ordered]@{ type = $currentParameterInfo.Type } } $configurationParameterName = "$($currentParameterInfo.MofResourceReference);$($currentParameterInfo.MofParameterName)" if ($currentParameterInfo.ContainsKey('ConfigurationValue')) { $configurationParameterValue = $currentParameterInfo.ConfigurationValue if ($currentParameterInfo.ConfigurationValue.StartsWith('[') -and $currentParameterInfo.ConfigurationValue.EndsWith(']')) { $configurationParameterStringValue = $currentParameterInfo.ConfigurationValue.Substring(1, $currentParameterInfo.ConfigurationValue.Length - 2) } else { $configurationParameterStringValue = "'$($currentParameterInfo.ConfigurationValue)'" } } else { $configurationParameterValue = "[parameters('$($currentParameterInfo.ReferenceName)')]" $configurationParameterStringValue = "parameters('$($currentParameterInfo.ReferenceName)')" } $guestConfigurationAssignmentHashtable['properties']['guestConfiguration']['configurationParameter'] += [Ordered]@{ name = $configurationParameterName value = $configurationParameterValue } $currentParameterValueConcatenatedString = "'$configurationParameterName', '=', $configurationParameterStringValue" $parameterValueConceatenatedStringList += $currentParameterValueConcatenatedString } $allParameterValueConcantenatedString = $parameterValueConceatenatedStringList -join ", ',', " $parameterExistenceConditionEqualsValue = "[base64(concat($allParameterValueConcantenatedString))]" $existenceConditionList += [Ordered]@{ field = 'Microsoft.GuestConfiguration/guestConfigurationAssignments/parameterHash' equals = $parameterExistenceConditionEqualsValue } } $existenceConditionList += [Ordered]@{ field = 'Microsoft.GuestConfiguration/guestConfigurationAssignments/contentHash' equals = "$ContentHash" } $policyRuleHashtable['then']['details']['existenceCondition'] = [Ordered]@{ allOf = $existenceConditionList } $policyRuleHashtable['then']['details']['deployment'] = $deploymentHashtable $policyRuleHashtable['then']['details']['deployment']['properties']['template']['resources'] += $guestConfigurationAssignmentHashtable if ($RPName -eq 'Microsoft.Compute') { $systemAssignedHashtable = [Ordered]@{ apiVersion = '2017-03-30' type = 'Microsoft.Compute/virtualMachines' identity = [Ordered]@{ type = 'SystemAssigned' } name = "[parameters('vmName')]" location = "[parameters('location')]" } $policyRuleHashtable['then']['details']['deployment']['properties']['template']['resources'] += $systemAssignedHashtable $policyRuleHashtable['then']['details']['deployment']['properties']['template']['resources'] += $guestConfigurationExtensionHashtable } $deployPolicyContentHashtable['properties']['policyRule'] = $policyRuleHashtable $deployPolicyContentHashtable += [Ordered]@{ id = "/providers/Microsoft.Authorization/policyDefinitions/$deployPolicyGuid" name = $deployPolicyGuid } $deployPolicyContent = ConvertTo-Json -InputObject $deployPolicyContentHashtable -Depth 100 | ForEach-Object { [System.Text.RegularExpressions.Regex]::Unescape($_) } $formattedDeployPolicyContent = Format-Json -Json $deployPolicyContent if (Test-Path -Path $filePath) { Write-Error -Message "A file at the policy destination path '$filePath' already exists. Please remove this file or specify a different destination path." } else { $null = New-Item -Path $filePath -ItemType 'File' -Value $formattedDeployPolicyContent } return $deployPolicyGuid } <# .SYNOPSIS Creates a new audit policy definition for a guest configuration policy definition set. #> function New-GuestConfigurationAuditPolicyDefinition { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $FileName, [Parameter(Mandatory = $true)] [String] $FolderPath, [Parameter(Mandatory = $true)] [String] $DisplayName, [Parameter(Mandatory = $true)] [String] $Description, [Parameter(Mandatory = $true)] [String] $ConfigurationName, [Parameter(Mandatory = $true)] [String] $ReferenceId, [Parameter()] [String] $Guid, [Parameter()] [ValidateSet('Windows', 'Linux')] [String] $Platform = 'Windows', [Parameter()] [ValidateSet('Microsoft.Compute', 'Microsoft.HybridCompute')] [String] $RPName = 'Microsoft.Compute', [Parameter()] [ValidateSet('virtualMachines', 'machines')] [String] $ResourceName = 'virtualMachines', [Parameter()] [String] $Category = 'Guest Configuration' ) if (-not [String]::IsNullOrEmpty($Guid)) { $auditPolicyGuid = $Guid } else { $auditPolicyGuid = [Guid]::NewGuid() } $filePath = Join-Path -Path $FolderPath -ChildPath $FileName $auditPolicyContentHashtable = [Ordered]@{ properties = [Ordered]@{ displayName = $DisplayName policyType = 'Custom' mode = 'All' description = $Description metadata = [Ordered]@{ category = $Category } } id = "/providers/Microsoft.Authorization/policyDefinitions/$auditPolicyGuid" name = $auditPolicyGuid } $policyRuleHashtable = [Ordered]@{ if = [Ordered]@{ allOf = @( [Ordered]@{ field = 'type' equals = $RPName + '/' + $ResourceName } ) } then = [Ordered]@{ effect = 'auditIfNotExists' details = [Ordered]@{ type = 'Microsoft.GuestConfiguration/guestConfigurationAssignments' name = $ConfigurationName } } } if ($Platform -ieq 'Windows') { $policyRuleHashtable['if']['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' in = @( 'esri', 'incredibuild', 'MicrosoftDynamicsAX', 'MicrosoftSharepoint', 'MicrosoftVisualStudio', 'MicrosoftWindowsDesktop', 'MicrosoftWindowsServerHPCPack' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'MicrosoftWindowsServer' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '2008*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'MicrosoftSQLServer' }, [Ordered]@{ field = $RPName + '/imageSKU' notEquals = 'SQL2008R2SP3-WS2008R2SP1' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-dsvm' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'dsvm-windows' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-ads' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'standard-data-science-vm', 'windows-data-science-vm' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'batch' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'rendering-windows2016' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'center-for-internet-security-inc' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'cis-windows-server-201*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'pivotal' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'bosh-windows-server*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'cloud-infrastructure-services' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'ad*' } ) } ) } ) } elseif ($Platform -ieq 'Linux') { $policyRuleHashtable['if']['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' in = @( 'microsoft-aks', 'AzureDatabricks', 'qubole-inc', 'datastax', 'couchbase', 'scalegrid', 'checkpoint', 'paloaltonetworks' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'OpenLogic' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'CentOS*' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'RHEL' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'osa' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'credativ' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'Debian' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '7*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'Suse' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'SLES*' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '11*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'Canonical' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'UbuntuServer' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '12*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-dsvm' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'linux-data-science-vm-ubuntu', 'azureml' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'cloudera' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'cloudera-centos-os' }, [Ordered]@{ field = $RPName + '/imageSKU' notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'cloudera' }, [Ordered]@{ field = $RPName + '/imageOffer' equals = 'cloudera-altus-centos-os' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'microsoft-ads' }, [Ordered]@{ field = $RPName + '/imageOffer' like = 'linux*' } ) } ) } ) } else { throw "The specified platform '$Platform' is not currently supported by this script." } $existenceConditionList = [Ordered]@{ field = 'Microsoft.GuestConfiguration/guestConfigurationAssignments/complianceStatus' equals = 'Compliant' } $policyRuleHashtable['then']['details']['existenceCondition'] = $existenceConditionList $auditPolicyContentHashtable['properties']['policyRule'] = $policyRuleHashtable $auditPolicyContent = ConvertTo-Json -InputObject $auditPolicyContentHashtable -Depth 100 | ForEach-Object { [System.Text.RegularExpressions.Regex]::Unescape($_) } $formattedAuditPolicyContent = Format-Json -Json $auditPolicyContent if (Test-Path -Path $filePath) { Write-Error -Message "A file at the policy destination path '$filePath' already exists. Please remove this file or specify a different destination path." } else { $null = New-Item -Path $filePath -ItemType 'File' -Value $formattedAuditPolicyContent } return $auditPolicyGuid } <# .SYNOPSIS Creates a new policy initiative definition for a guest configuration policy definition set. #> function New-GuestConfigurationPolicyInitiativeDefinition { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $FileName, [Parameter(Mandatory = $true)] [String] $FolderPath, [Parameter(Mandatory = $true)] [Hashtable[]] $DeployPolicyInfo, [Parameter(Mandatory = $true)] [Hashtable[]] $AuditPolicyInfo, [Parameter(Mandatory = $true)] [String] $DisplayName, [Parameter(Mandatory = $true)] [String] $Description, [Parameter()] [String] $Guid ) if (-not [String]::IsNullOrEmpty($Guid)) { $initiativeGuid = $Guid } else { $initiativeGuid = [Guid]::NewGuid() } $filePath = Join-Path -Path $FolderPath -ChildPath $FileName $policyDefinitions = @() $initiativeContentHashtable = [Ordered]@{ properties = [Ordered]@{ displayName = $DisplayName policyType = 'Custom' description = $Description metadata = [Ordered]@{ category = 'Guest Configuration' } } } foreach ($currentDeployPolicyInfo in $DeployPolicyInfo) { $deployPolicyContentHash = [Ordered]@{ policyDefinitionId = "/providers/Microsoft.Authorization/policyDefinitions/$($currentDeployPolicyInfo.Guid)" policyDefinitionReferenceId = $currentDeployPolicyInfo.ReferenceId } if ($currentDeployPolicyInfo.ContainsKey('ParameterInfo')) { if (-not $initiativeContentHashtable['properties'].Contains('parameters')) { $initiativeContentHashtable['properties']['parameters'] = [Ordered]@{ } } if (-not $deployPolicyContentHash.Contains('parameters')) { $deployPolicyContentHash['parameters'] = [Ordered]@{ } } foreach ($currentParameterInfo in $currentDeployPolicyInfo.ParameterInfo) { $initiativeContentHashtable['properties']['parameters'] += [Ordered]@{ $currentParameterInfo.ReferenceName = [Ordered]@{ type = $currentParameterInfo.Type metadata = [Ordered]@{ displayName = $currentParameterInfo.DisplayName } } } if ($currentParameterInfo.ContainsKey('Description')) { $initiativeContentHashtable['properties']['parameters'][$currentParameterInfo.ReferenceName]['metadata']['description'] = $currentParameterInfo['Description'] } if ($currentParameterInfo.ContainsKey('DefaultValue')) { $initiativeContentHashtable['properties']['parameters'][$currentParameterInfo.ReferenceName] += [Ordered]@{ defaultValue = $currentParameterInfo.DefaultValue } } if ($currentParameterInfo.ContainsKey('AllowedValues')) { $initiativeContentHashtable['properties']['parameters'][$currentParameterInfo.ReferenceName] += [Ordered]@{ allowedValues = $currentParameterInfo.AllowedValues } } $deployPolicyContentHash['parameters'] += [Ordered]@{ $currentParameterInfo.ReferenceName = [Ordered]@{ value = "[parameters('$($currentParameterInfo.ReferenceName)')]" } } } } $policyDefinitions += $deployPolicyContentHash } foreach ($currentAuditPolicyInfo in $AuditPolicyInfo) { $auditPolicyContentHash = [Ordered]@{ policyDefinitionId = "/providers/Microsoft.Authorization/policyDefinitions/$($currentAuditPolicyInfo.Guid)" policyDefinitionReferenceId = $currentAuditPolicyInfo.ReferenceId } $policyDefinitions += $auditPolicyContentHash } $initiativeContentHashtable['properties']['policyDefinitions'] = $policyDefinitions $initiativeContentHashtable += [Ordered]@{ id = "/providers/Microsoft.Authorization/policySetDefinitions/$initiativeGuid" name = $initiativeGuid } $initiativeContent = ConvertTo-Json -InputObject $initiativeContentHashtable -Depth 100 | ForEach-Object { [System.Text.RegularExpressions.Regex]::Unescape($_) } $formattedInitiativeContent = Format-Json -Json $initiativeContent if (Test-Path -Path $filePath) { Write-Error -Message "A file at the initiative destination path '$filePath' already exists. Please remove this file or specify a different destination path." } else { $null = New-Item -Path $filePath -ItemType 'File' -Value $formattedInitiativeContent } return $initiativeGuid } <# .SYNOPSIS Creates a new policy set for guest configuration. This set should include at least one audit policy definition, at least one deploy policy definition, and only one policy initiative definition. #> function New-GuestConfigurationPolicyDefinitionSet { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $PolicyFolderPath, [Parameter(Mandatory = $true)] [Hashtable[]] $DeployPolicyInfo, [Parameter(Mandatory = $true)] [Hashtable[]] $AuditPolicyInfo, [Parameter(Mandatory = $true)] [Hashtable] $InitiativeInfo, [Parameter()] [ValidateSet('Windows', 'Linux')] [String] $Platform = 'Windows' ) if (Test-Path -Path $PolicyFolderPath) { $null = Remove-Item -Path $PolicyFolderPath -Force -Recurse -ErrorAction 'SilentlyContinue' } $null = New-Item -Path $PolicyFolderPath -ItemType 'Directory' foreach ($currentDeployPolicyInfo in $DeployPolicyInfo) { $currentDeployPolicyInfo['FolderPath'] = $PolicyFolderPath $deployPolicyGuid = New-GuestConfigurationDeployPolicyDefinition @currentDeployPolicyInfo -Platform $Platform $currentDeployPolicyInfo['Guid'] = $deployPolicyGuid } foreach ($currentAuditPolicyInfo in $AuditPolicyInfo) { $currentAuditPolicyInfo['FolderPath'] = $PolicyFolderPath $auditPolicyGuid = New-GuestConfigurationAuditPolicyDefinition @currentAuditPolicyInfo -Platform $Platform $currentAuditPolicyInfo['Guid'] = $auditPolicyGuid } $InitiativeInfo['FolderPath'] = $PolicyFolderPath $InitiativeInfo['DeployPolicyInfo'] = $DeployPolicyInfo $InitiativeInfo['AuditPolicyInfo'] = $AuditPolicyInfo $initiativeGuid = New-GuestConfigurationPolicyInitiativeDefinition @InitiativeInfo return $initiativeGuid } function New-CustomGuestConfigPolicy { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $PolicyFolderPath, [Parameter(Mandatory = $true)] [Hashtable] $DeployPolicyInfo, [Parameter(Mandatory = $true)] [Hashtable] $AuditPolicyInfo, [Parameter(Mandatory = $true)] [Hashtable] $InitiativeInfo, [Parameter()] [ValidateSet('Windows', 'Linux')] [String] $Platform = 'Windows', [Parameter(Mandatory = $false)] [string] $Category = 'Guest Configuration' ) $existingPolicies = Get-AzPolicyDefinition $existingDeployPolicy = $existingPolicies | Where-Object { ($_.Properties.PSObject.Properties.Name -contains 'displayName') -and ($_.Properties.displayName -eq $DeployPolicyInfo.DisplayName) } if ($null -ne $existingDeployPolicy) { Write-Verbose -Message "Found policy with name '$($existingDeployPolicy.Properties.displayName)' and guid '$($existingDeployPolicy.Name)'..." $DeployPolicyInfo['Guid'] = $existingDeployPolicy.Name.ToString() } $existingAuditPolicy = $existingPolicies | Where-Object { ($_.Properties.PSObject.Properties.Name -contains 'displayName') -and ($_.Properties.displayName -eq $AuditPolicyInfo.DisplayName) } if ($null -ne $existingAuditPolicy) { Write-Verbose -Message "Found policy with name '$($existingAuditPolicy.Properties.displayName)' and guid '$($existingAuditPolicy.Name)'..." $AuditPolicyInfo['Guid'] = $existingAuditPolicy.Name.ToString() } $existingInitiative = Get-AzPolicySetDefinition | Where-Object { ($_.Properties.PSObject.Properties.Name -contains 'displayName') -and ($_.Properties.displayName -eq $InitiativeInfo.DisplayName) } if ($null -ne $existingInitiative) { Write-Verbose -Message "Found initiative with name '$($existingInitiative.Properties.displayName)' and guid '$($existingInitiative.Name)'..." $InitiativeInfo['Guid'] = $existingInitiative.Name.ToString() } New-GuestConfigurationPolicyDefinitionSet @PSBoundParameters } # SIG # Begin signature block # MIIjhgYJKoZIhvcNAQcCoIIjdzCCI3MCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAU8Zn08UwkN+qB # MjiVpPGwLDatozyAMjstQXORHXdEc6CCDYEwggX/MIID56ADAgECAhMzAAABUZ6N # j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1 # oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os # +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU # 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg # B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ # IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A # rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS # se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf # NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL # crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs # 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF # sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD # 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK # YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j # 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu # Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY # HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWzCCFVcCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgUpf5EiPn # 7rb0STqm/2ZLa/1eJULcBUW3b0hzHtUy0xcwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQA2q7nOGCXP/B6LGUVIaG1ElwtjDEi8g+uKu8x2m8tk # iCDYyLbA3NkmEMD8M0+s4vd6q1MkrkQTovVg7WgQSdIbleBBKjuuHuFMw6rD+Kw6 # utxmv/efcC9HwEomekknGSBOL9rg3/W3buuEpaxvaCRvAZISC6ap8kI7UuLwDZMD # 3adbaIpjtZOUECo2eLF+KP7ZQqxVc3tMicjXppTmFAExk3cw5/1SmhVsIjL8unHH # ja+4vGkSu/qvACiuxk4Dl0YV+ycPnOnz6ZKj2etc+x/J9/aa2kkBnh+IOuH5VDe+ # fpnnD0O4zVKMe4X5hLtKjqmgISzXqCBkjpWuHXGnD45+oYIS5TCCEuEGCisGAQQB # gjcDAwExghLRMIISzQYJKoZIhvcNAQcCoIISvjCCEroCAQMxDzANBglghkgBZQME # AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIEnYy3kkbbjZ/53L/fX65lvDkXSARxsEi3oLe4fG # O+7hAgZee8CjcJQYEzIwMjAwMzMwMjIzNjAyLjA2OFowBIACAfSggdCkgc0wgcox # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p # Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg # RVNOOjFBOEYtRTNDMy1ENjlEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIOPDCCBPEwggPZoAMCAQICEzMAAAEb4iDN3Iqvi9kAAAAAARsw # DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN # MTkxMTEzMjE0MDM4WhcNMjEwMjExMjE0MDM4WjCByjELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MUE4Ri1FM0MzLUQ2 # OUQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVg+d+2AlystedNjBh83Qtgx0uGOgZ # dh3ycdZrWtPGHyrNWUKtId8HJObobjene6p0jaZ4YmueLMHWIy1ZGef5j6BgyABx # TK1VqElOuqUtMx/fLjeuFwJrigO1y2DsBKtpdoKXHbsUwbKWsnUE56GWC/eQWYBa # uMtDyhHMtezccLmI/wE9c6OcA0puJlWihQFOS1jis3vFd1a+P8hzCB/mfFoOo7Nu # pMiVVVmv5c41tcxeQcRutO2anqWLmLCD/YmM2Bvcr2D7aCOctjdb+P40DgQNfiOu # ohJogqJoQ9RVM08y4uPslUHtRe0sbBga3bo7UNkd4oMSt3rVUUy3UxqdAgMBAAGj # ggEbMIIBFzAdBgNVHQ4EFgQUGuWTykJrvILcJx3Wk2jZDy7BGvkwHwYDVR0jBBgw # FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov # L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx # MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN # BgkqhkiG9w0BAQsFAAOCAQEAXDX0hGelkALDqn6RyjHJ9Zk1AT8XUPghnIgMIe8Q # +JNNEmDD4NmozL16UaNCKxatexPjjVecC/ddey5Zxk5spK/ko7vlqpGIIau/z8hh # g/N9w9sqLEcN11JxoJdrAvShbgBKFnHdgt9TxNWQCQpBbPfRN79/7HcV2wTitb8R # iRAMui7HdbuVtw4lbhLgmwYEgWblC6aaX2bdu6GCxRN8F8JgYRu11T0Z/aX8q9bq # 9fd7f3Dr3jNCxGteRPBJoegD+699iFmr/fWFRpjpsxi35t2p1z7JAIbAQJN/pZmL # TP7hRiXANo8IZ+ePHg/7L2boey3suYKwh7hf9Rsezy/3DDCCBnEwggRZoAMCAQIC # CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp # ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx # NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF # ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD # DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx # z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1 # rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc # sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB # 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF # bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud # EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD # VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv # cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB # BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j # ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB # kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe # MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA # LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx # vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS # inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1 # L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO # M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4 # pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45 # V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x # 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe # gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn # QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp # 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT # X4/edIhJEqGCAs4wggI3AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP # cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxQThGLUUzQzMtRDY5 # RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG # BSsOAwIaAxUAng+r7yKot4zNePiVSSzkJDucVMaggYMwgYCkfjB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOIs1oYwIhgPMjAy # MDAzMzEwNDM1MThaGA8yMDIwMDQwMTA0MzUxOFowdzA9BgorBgEEAYRZCgQBMS8w # LTAKAgUA4izWhgIBADAKAgEAAgIgvwIB/zAHAgEAAgIRvjAKAgUA4i4oBgIBADA2 # BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIB # AAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJBbIzG7riheEnYf60zsZnHP9FkzShbk # 16eX6a0v5SxoHrOq5+jrryLtcutDeyJdy5HkQQo9JnI5j7C0mKoWlpzXwBQaWtwr # JA2uNY2Zqpe2r/i56Wdh5X1iNz14Xb9N8tBJCyFYU4G0DhWAbk3BG4L4cPpBp5yO # 5mazovthP1ZEMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAEb4iDN3Iqvi9kAAAAAARswDQYJYIZIAWUDBAIBBQCgggFKMBoG # CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgxqAfpxDw # gCjyO/cj4RMmzVYIdsJn48Qd/4cngE2/bS8wgfoGCyqGSIb3DQEJEAIvMYHqMIHn # MIHkMIG9BCDgkW3xosRxZlNslK/003ytaqeVKfkD7Y30HDaKaSvbdzCBmDCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABG+IgzdyKr4vZAAAA # AAEbMCIEIEs5D8fjs5xcGocYiDrH2oiYWWumR2nk+gfZ5wEeYpiTMA0GCSqGSIb3 # DQEBCwUABIIBACmX2rJxT58kstLQOJq7/1UugyUZYWqWC0AW29p0rOavjRdyNKwK # VDUKL/mC5bD7+OPGzRlBeAEglOKblMz25JBQ8NQyzeRVX8eolsSJBmU5sQ9Iy+Yh # 7wO+0BAOEkTtpUA+7hyJjL8rLWogRcQuleajLl6JWTFbK+zJg9y+2Nt4d5CdvAns # yvLjcfXoeQk6IXgXlYNSUXAxgZHIsOvmEUZ2nJFyKjR5NTcssrZwUgDCeGz+Ot4F # McYzsLdPRkuM2qLUH8PKzr9R3GAKF87nO2/BubUsnDHHzlWhelgA6D00TH56IlQU # Kt6oRSekJtEcStyLtM4NkgTQpjg5UuENrxc= # SIG # End signature block |