helpers/GuestConfigurationPolicy.psm1
Set-StrictMode -Version latest $ErrorActionPreference = 'Stop' Import-Module $PSScriptRoot/DscOperations.psm1 -Force function Update-PolicyParameter { [CmdletBinding()] param ( [Parameter()] [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)/DscResources/" "$guestConfigModulePath/DscResources/" -Recurse Copy-Item "$($latestModule.ModuleBase)/helpers/" "$guestConfigModulePath/helpers/" -Recurse Copy-Item "$($latestModule.ModuleBase)/GuestConfiguration.psd1" "$guestConfigModulePath/GuestConfiguration.psd1" Copy-Item "$($latestModule.ModuleBase)/GuestConfiguration.psm1" "$guestConfigModulePath/GuestConfiguration.psm1" # Copies DSC resource modules $modulesToCopy = @{ } $resourcesInMofDocument | ForEach-Object { if ($_.CimInstanceProperties.Name -contains 'ModuleName' -and $_.CimInstanceProperties.Name -contains 'ModuleVersion') { if ($_.ModuleName -ne 'GuestConfiguration') { $modulesToCopy[$_.CimClass.CimClassName] = @{ModuleName = $_.ModuleName; ModuleVersion = $_.ModuleVersion } } } } # PowerShell modules required by DSC resource module $powershellModulesToCopy = @{ } $modulesToCopy.Values | ForEach-Object { if ($_.ModuleName -ne 'GuestConfiguration') { $requiredModule = Get-Module -FullyQualifiedName @{ModuleName = $_.ModuleName; RequiredVersion = $_.ModuleVersion } -ListAvailable if (($requiredModule | Get-Member -MemberType 'Property' | ForEach-Object { $_.Name }) -contains 'RequiredModules') { $requiredModule.RequiredModules | ForEach-Object { if ($null -ne $_.Version) { $powershellModulesToCopy[$_.Name] = @{ModuleName = $_.Name; ModuleVersion = $_.Version } Write-Verbose "$($_.Name) is a required PowerShell module" } else { Write-Error "Unable to add required PowerShell module $($_.Name). No version was specified in the module manifest RequiredModules property. Please use module specification '@{ModuleName=;ModuleVersion=}'." } } } } } $modulesToCopy += $powershellModulesToCopy $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 (just a safeguard). $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' Convert-FileToUnixLineEndings -FilePath $chefResourcePath/install_inspec.sh 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()] [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()] [bool] $UseCertificateValidation = $false, [Parameter()] [String] $Category = 'Guest Configuration', [Parameter()] [Hashtable[]] $Tag ) 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]@{ anyOf = @( [Ordered]@{ allOf = @( [Ordered]@{ field = 'type' equals = "Microsoft.Compute/virtualMachines" } ) }, [Ordered]@{ allOf = @(, [Ordered]@{ field = "type" equals = "Microsoft.HybridCompute/machines" } ) } ) } 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')]" } type = [Ordered]@{ value = "[field('type')]" } 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' } type = [Ordered]@{ type = 'string' } configurationName = [Ordered]@{ type = 'string' } contentUri = [Ordered]@{ type = 'string' } contentHash = [Ordered]@{ type = 'string' } } resources = @() } } } $guestConfigurationAssignmentHashtable = @( [Ordered]@{ apiVersion = '2018-11-20' type = 'Microsoft.Compute/virtualMachines/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() } } condition = "[equals(toLower(parameters('type')), toLower('Microsoft.Compute/virtualMachines'))]" }, [Ordered]@{ apiVersion = '2018-11-20' type = 'Microsoft.HybridCompute/machines/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() } } condition = "[equals(toLower(parameters('type')), toLower('microsoft.hybridcompute/machines'))]" } ) if ($Platform -ieq 'Windows') { $policyRuleHashtable['if']['anyOf'][0]['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" in = @( 'esri', 'incredibuild', 'MicrosoftDynamicsAX', 'MicrosoftSharepoint', 'MicrosoftVisualStudio', 'MicrosoftWindowsDesktop', 'MicrosoftWindowsServerHPCPack' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'MicrosoftWindowsServer' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '2008*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'MicrosoftSQLServer' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" notLike = 'SQL2008*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-dsvm' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'dsvm-windows' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-ads' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" in = @( 'standard-data-science-vm', 'windows-data-science-vm' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'batch' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'rendering-windows2016' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'center-for-internet-security-inc' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'cis-windows-server-201*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'pivotal' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'bosh-windows-server*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'cloud-infrastructure-services' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'ad*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration" exists = 'true' }, [Ordered]@{ field = "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType" like = 'Windows*' } ) }, [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/imageSKU" exists = 'false' }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '2008*' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" notLike = 'SQL2008*' } ) } ) } ) } ) } ) $policyRuleHashtable['if']['anyOf'][1]['allOf'] += @( [Ordered]@{ field = "Microsoft.HybridCompute/imageOffer" like = "windows*" } ) $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'))]" ) condition = "[equals(toLower(parameters('type')), toLower('Microsoft.Compute/virtualMachines'))]" } } elseif ($Platform -ieq 'Linux') { $policyRuleHashtable['if']['anyOf'][0]['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" in = @( 'microsoft-aks', 'qubole-inc', 'datastax', 'couchbase', 'scalegrid', 'checkpoint', 'paloaltonetworks' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'OpenLogic' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'CentOS*' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'Oracle' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'Oracle-Linux' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'RHEL', 'RHEL-HA' 'RHEL-SAP', 'RHEL-SAP-APPS', 'RHEL-SAP-HA', 'RHEL-SAP-HANA' ) }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'osa', 'rhel-byos' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'center-for-internet-security-inc' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" in = @( 'cis-centos-7-l1', 'cis-centos-7-v2-1-1-l1' 'cis-centos-8-l1', 'cis-debian-linux-8-l1', 'cis-debian-linux-9-l1', 'cis-nginx-centos-7-v1-1-0-l1', 'cis-oracle-linux-7-v2-0-0-l1', 'cis-oracle-linux-8-l1', 'cis-postgresql-11-centos-linux-7-level-1', 'cis-rhel-7-l2', 'cis-rhel-7-v2-2-0-l1', 'cis-rhel-8-l1', 'cis-suse-linux-12-v2-0-0-l1', 'cis-ubuntu-linux-1604-v1-0-0-l1', 'cis-ubuntu-linux-1804-l1' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'credativ' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'Debian' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '7*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'Suse' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'SLES*' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '11*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'Canonical' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'UbuntuServer' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '12*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-dsvm' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" in = @( 'linux-data-science-vm-ubuntu', 'azureml' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'cloudera' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'cloudera-centos-os' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'cloudera' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'cloudera-altus-centos-os' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-ads' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'linux*' } ) } ) } ) $policyRuleHashtable['if']['anyOf'][1]['allOf'] += @( [Ordered]@{ field = "Microsoft.HybridCompute/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'))]" ) condition = "[equals(toLower(parameters('type')), toLower('Microsoft.Compute/virtualMachines'))]" } } else { throw "The specified platform '$Platform' is not currently supported by this script." } # if there is atleast one tag if ($PSBoundParameters.ContainsKey('Tag') -AND $null -ne $Tag) { # capture existing 'anyOf' section $anyOf = $policyRuleHashtable['if'] # replace with new 'allOf' at top order $policyRuleHashtable['if'] = [Ordered]@{ allOf = @( ) } # add tags section under new 'allOf' $policyRuleHashtable['if']['allOf'] += [Ordered]@{ allOf = @( ) } # re-insert 'anyOf' under new 'allOf' after tags 'allOf' $policyRuleHashtable['if']['allOf'] += $anyOf # add each tag individually to tags 'allOf' for($i = 0; $i -lt $Tag.count; $i++) { # if there is atleast one tag if (-not [string]::IsNullOrEmpty($Tag[$i].Keys)) { $policyRuleHashtable['if']['allOf'][0]['allOf'] += [Ordered]@{ field = "tags.$($Tag[$i].Keys)" equals = "$($Tag[$i].Values)" } } } } $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 $systemAssignedHashtable = [Ordered]@{ apiVersion = '2019-07-01' type = 'Microsoft.Compute/virtualMachines' identity = [Ordered]@{ type = 'SystemAssigned' } name = "[parameters('vmName')]" location = "[parameters('location')]" condition = "[equals(toLower(parameters('type')), toLower('Microsoft.Compute/virtualMachines'))]" } $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()] [String] $Category = 'Guest Configuration', [Parameter()] [Hashtable[]] $Tag ) 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]@{ anyOf = @( [Ordered]@{ allOf = @( [Ordered]@{ field = 'type' equals = "Microsoft.Compute/virtualMachines" } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "type" equals = "Microsoft.HybridCompute/machines" } ) } ) } then = [Ordered]@{ effect = 'auditIfNotExists' details = [Ordered]@{ type = 'Microsoft.GuestConfiguration/guestConfigurationAssignments' name = $ConfigurationName } } } if ($Platform -ieq 'Windows') { $policyRuleHashtable['if']['anyOf'][0]['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" in = @( 'esri', 'incredibuild', 'MicrosoftDynamicsAX', 'MicrosoftSharepoint', 'MicrosoftVisualStudio', 'MicrosoftWindowsDesktop', 'MicrosoftWindowsServerHPCPack' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'MicrosoftWindowsServer' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '2008*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'MicrosoftSQLServer' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" notLike = 'SQL2008*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-dsvm' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'dsvm-windows' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-ads' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" in = @( 'standard-data-science-vm', 'windows-data-science-vm' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'batch' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'rendering-windows2016' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'center-for-internet-security-inc' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'cis-windows-server-201*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'pivotal' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'bosh-windows-server*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'cloud-infrastructure-services' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'ad*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration" exists = 'true' }, [Ordered]@{ field = "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType" like = 'Windows*' } ) }, [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/imageSKU" exists = 'false' }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '2008*' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" notLike = 'SQL2008*' } ) } ) } ) } ) } ) $policyRuleHashtable['if']['anyOf'][1]['allOf'] += @( [Ordered]@{ field = "Microsoft.HybridCompute/imageOffer" like = "windows*" } ) } elseif ($Platform -ieq 'Linux') { $policyRuleHashtable['if']['anyOf'][0]['allOf'] += @( [Ordered]@{ anyOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" in = @( 'microsoft-aks', 'qubole-inc', 'datastax', 'couchbase', 'scalegrid', 'checkpoint', 'paloaltonetworks' ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'OpenLogic' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'CentOS*' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'Oracle' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'Oracle-Linux' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = $RPName + '/imagePublisher' equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'RHEL', 'RHEL-HA' 'RHEL-SAP', 'RHEL-SAP-APPS', 'RHEL-SAP-HA', 'RHEL-SAP-HANA' ) }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'RedHat' }, [Ordered]@{ field = $RPName + '/imageOffer' in = @( 'osa', 'rhel-byos' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'center-for-internet-security-inc' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" in = @( 'cis-centos-7-l1', 'cis-centos-7-v2-1-1-l1' 'cis-centos-8-l1', 'cis-debian-linux-8-l1', 'cis-debian-linux-9-l1', 'cis-nginx-centos-7-v1-1-0-l1', 'cis-oracle-linux-7-v2-0-0-l1', 'cis-oracle-linux-8-l1', 'cis-postgresql-11-centos-linux-7-level-1', 'cis-rhel-7-l2', 'cis-rhel-7-v2-2-0-l1', 'cis-rhel-8-l1', 'cis-suse-linux-12-v2-0-0-l1', 'cis-ubuntu-linux-1604-v1-0-0-l1', 'cis-ubuntu-linux-1804-l1' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'credativ' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'Debian' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '7*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'Suse' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'SLES*' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '11*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'Canonical' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'UbuntuServer' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '12*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-dsvm' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" in = @( 'linux-data-science-vm-ubuntu', 'azureml' ) } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'cloudera' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'cloudera-centos-os' }, [Ordered]@{ field = "Microsoft.Compute/imageSKU" notLike = '6*' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'cloudera' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" equals = 'cloudera-altus-centos-os' } ) }, [Ordered]@{ allOf = @( [Ordered]@{ field = "Microsoft.Compute/imagePublisher" equals = 'microsoft-ads' }, [Ordered]@{ field = "Microsoft.Compute/imageOffer" like = 'linux*' } ) } ) } ) $policyRuleHashtable['if']['anyOf'][1]['allOf'] += @( [Ordered]@{ field = "Microsoft.HybridCompute/imageOffer" like = "linux*" } ) } else { throw "The specified platform '$Platform' is not currently supported by this script." } # if there is atleast one tag if ($PSBoundParameters.ContainsKey('Tag') -AND $null -ne $Tag) { # capture existing 'anyOf' section $anyOf = $policyRuleHashtable['if'] # replace with new 'allOf' at top order $policyRuleHashtable['if'] = [Ordered]@{ allOf = @( ) } # add tags section under new 'allOf' $policyRuleHashtable['if']['allOf'] += [Ordered]@{ allOf = @( ) } # re-insert 'anyOf' under new 'allOf' after tags 'allOf' $policyRuleHashtable['if']['allOf'] += $anyOf # add each tag individually to tags 'allOf' for($i = 0; $i -lt $Tag.count; $i++) { # if there is atleast one tag if (-not [string]::IsNullOrEmpty($Tag[$i].Keys)) { $policyRuleHashtable['if']['allOf'][0]['allOf'] += [Ordered]@{ field = "tags.$($Tag[$i].Keys)" equals = "$($Tag[$i].Values)" } } } } $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] $Category, [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 = $Category } } } 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()] [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 # MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBuHyROZAffIQs/ # UTMWsj9loLyEK677vgIyLMJtEaDTOKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX # chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB # znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH # sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d # weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ # itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV # Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy # S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K # NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV # BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr # qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx # zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe # yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g # yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf # AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI # 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 # GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea # jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS # 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/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgUJXc/hOy # a6ZxlYmZxZrplb8O515tL0Td2+8b6m7sIqkwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQBZiIuuxzft2lQ98XsbS/+bSLFF6VWnFNQ67dqtTB4O # icLWER7ucI2UQh0t7Pv4OlgSGtnCK3zfMkmF9iqfwCkbxZm4hEpUIjA9izgZ1wsK # YgmrNSvy4+KJ8GG17QMpvgXd6tWUcXnQ5XxFVVbKCGfSHCByKeNv7QChu79sA+DO # 04jx3LY8TYe5a7qw6mXC7SYgqA9oh1H4DCc6/1nRVa/th+Jl66Ok29dRQ0xLoamr # ifU/mlVRMX7GDpO4Iof6sehUm50heoUh8PTyFYURGAQZnnUFAzhkYQcDGLzb/bTf # ONKmJWRJEEtsv+KFBuQcoqQp0v5RsS9e7iQrsx9TGFSBoYIS8TCCEu0GCisGAQQB # gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIIpH/CSvSKOihTWr/ADA18IjODm0FeZV0mkLiONo # ZqpeAgZepxHmvQ8YEzIwMjAwNDMwMDE0NjMxLjI0OVowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjpGODdBLUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABL7GnF3lWlBeHAAAA # AAEvMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTE5MTIxOTAxMTUwNloXDTIxMDMxNzAxMTUwNlowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGODdB # LUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKh8VkvVIwXD8sn0zT2n # EdyEZ9UNHY7ACbOZA4obAHvD1hauw9K1Z2lRWG+m8Ars9l35GoMXdPgshM3hZKQW # fhrLnF9/GDZoilhc2LhMqNPXs06rAJ8YODB6i0Cg1CFCYnyOYvywXKY3xGJN09Dg # PXWfczEm2P/a3rmrXMrK5EFc3ahxrC51c+UuAMKV9xJyzJVLShPwPBJl+CjdMDPJ # f24DZXIYec3gCN2xean1DFCI0gaqJprMeL4Om1KY2AZMIgBPEkoY1N7AI5e7ybkI # L8+Mz3inijb4rDTkXk86ztUwy4bdc1MyKe2j2odT+QIDA2+M8cMTIGlKn7EyD2NN # XU8CAwEAAaOCARswggEXMB0GA1UdDgQWBBSml/VRpBNFkAMDiqcoqWi85j/qljAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB4q6ilv2SlGvJD/7dbfoIKZBO2i6YA # wckw57TpCrt2+SAx2dcF7JvRMCPhLCSgqjyNcJRs40cEXPbLdzZMJHzcv73AF7L6 # mWZXg2aBjG1Sc5qM4jjE/nwIX+C6/odm5/asU4JIlFCuUZjzqdir18HkRVQve2Hw # V0lCXHQs+V3m9DyyA9b6LSIk3GOFZu7F11Wyx/5dVXisPPTPwh9JXfMD9W173M1+ # ZZycmO03lUc4G1FilgpxWNdgWn/DO9ZhoW5yN6+BUddnJ4cCcCjcg8sB5rktPP8p # VZAQ7aUqkAeqo+FuCkAUAdJRESCpR5wgSPtVvFPMjONE36DbKtfzkfiHMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpG # ODdBLUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUAM/CZCUpclQ9qfr/r3y9osIIPSmSggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOJUM1AwIhgPMjAyMDA0MjkyMTA5MzZaGA8yMDIwMDQzMDIxMDkzNlowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA4lQzUAIBADAKAgEAAgIceQIB/zAHAgEAAgIRJzAK # AgUA4lWE0AIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAAp54NiNEHQiKNTz # OgcplTbgRrPgHL2kqKLuKlxWE40UDqh26F1wRYm70lqrWjelnVZ5WUnOCHxB0LSc # 24Plhi6H4RJkvaJYOD1Vw1VF3scsxEpl9IPuY8hvsX9V58gZW6vAk2/MczWcoH9a # V2QJMfuRjdoDhnH6wDsbKrTLadI1MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAEvsacXeVaUF4cAAAAAAS8wDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQgDJKOE47nTp+MpRSep17YRYySU74U6a23E0CpuZLPV3IwgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCBC5RecGZvugnvVXg80zlrGv1uV35LNk+H9dBj3 # ChFPvTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # L7GnF3lWlBeHAAAAAAEvMCIEIFx46qP1vMDyfsQgc2W2N0DNMaC/NWriQG4aT3Mh # PTftMA0GCSqGSIb3DQEBCwUABIIBACZip/JtbBne5zfynhY+GYLZjfkrUTkFQwTg # /iVmaK6YoWktYxmykBf4uZKD4zq+9PrKijMXiT2h0mjADoSMP6ADe4+VJSPVoIyR # 6VQHWUPj+2hZVRfur6GG8WO47enUWS+i3adsscrMXdOapFb76y5LuYeoK3XxcIl1 # 7R4QeJA7/Yt/QBFlvUuYClMVRqFwLoJQ+lybJqDv8d4etv8xdmSIC5fhNCIF2/8x # jECRv2fPfTblNyM/q2vF1RuBZUqpkl8A4mUf7dN/+NdPR2He4LxRuVqWdrJKiatf # ti+V7Pyr2IWvIalz39TJS3CX/wGq47KOjctxPHTVn3cAclkaCzo= # SIG # End signature block |