GuestConfiguration.psm1

Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'

<#
    .SYNOPSIS
        Create a Guest Configuration policy package.
 
    .Parameter Name
        Guest Configuration package name.
 
    .Parameter Configuration
        Compiled DSC configuration document full path.
 
    .Parameter DesintationPath
        Output folder path.
        It is an optional parameter. if not specified, package will be created in current directory.
 
    .Parameter FilesToInclude
        Path to include additional files/folder with the package.
 
    .Example
        New-GuestConfigurationPackage -Name WindowsTLS -Configuration c:\custom_policy\WindowsTLS\localhost.mof -Out c:\git\repository\release\policy\WindowsTLS
#>


function New-GuestConfigurationPackage
{
    [CmdletBinding()]
    param (
        [parameter(Position=0, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Name,

        [parameter(Position=1, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Configuration,

        [ValidateNotNullOrEmpty()]
        [string] $FilesToInclude,

        [string] $DesintationPath = '.'
    )

    Try {
        $reservedResourceName = @('OMI_ConfigurationDocument')
        $tempPath = New-Item -ItemType Directory -Force -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid()))
        $Configuration = Resolve-Path $Configuration
         
        Write-Verbose "Creating Guest Configuration package in temporary directory '$tempPath'"

        # Verify that only supported resources are used in DSC configuration.
        Test-GuestConfigurationMofResourceDependencies -Path $Configuration

        # Save DSC configuration to the temporary package path.
        Save-GuestConfigurationMofDocument -Name $Name -SourcePath $Configuration -DestinationPath (Join-Path $tempPath "$Name.mof")

        # Copy GuestConfiguration module.
        Write-Verbose "Copy DSC resources ..."
        $modulePath = New-Item -ItemType Directory -Force -Path (Join-Path $tempPath 'Modules')
        Copy-Item (Get-Module GuestConfiguration -ListAvailable).ModuleBase $modulePath -Recurse -Force

        # 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') {
                Copy-Item $_.ParentPath $nativeResourcePath -Recurse -Force
            }
        }

        # Copy FilesToInclude.
        $missingDependencies = @()
        $resourcesInMofDocument = $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($Configuration, 4)
        $resourcesInMofDocument | ForEach-Object {
            if($_.CimClass.CimClassName -eq 'MSFT_ChefInSpecResource') {
                if([string]::IsNullOrEmpty($FilesToInclude)) {
                    Throw "Failed to find Chef Inspec profile for '$($_.CimInstanceProperties['Name'].Value)'. Please use FilesToInclude parameter to specify profile path."
                }

                $includeFiles = Join-Path $FilesToInclude $_.CimInstanceProperties['Name'].Value
                if(-not (Test-Path $includeFiles)) {
                    $missingDependencies += $_.CimInstanceProperties['Name'].Value
                }
            }
        }
        if($missingDependencies.Length) {
            Throw "Failed to find Chef Inspec profile for '$($missingDependencies -join ',')'. Please make sure profile is present on $FilesToInclude path."
        }
        else {
            if(-not [string]::IsNullOrEmpty($FilesToInclude)) {
                if(Test-Path $FilesToInclude -PathType Leaf) {
                    Copy-Item "$FilesToInclude" $modulePath -Force -ErrorAction SilentlyContinue
                }
                else {
                    Copy-Item "$FilesToInclude\*" $modulePath -Recurse -Force -ErrorAction SilentlyContinue
                }
            }
        }

        # Create Guest Configuration Package.
        $packagePath = Join-Path $DesintationPath $Name
        Remove-Item $packagePath -Force -Recurse -ErrorAction SilentlyContinue
        New-Item -ItemType Directory -Force -Path $packagePath | Out-Null
        $packagePath = Resolve-Path $packagePath
        $packageFilePath = join-path $packagePath "$Name.zip"

        Write-Verbose "Creating Guest Configuration package : $packageFilePath."
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::CreateFromDirectory($tempPath, $packageFilePath)
    }
    Finally {
        # Remove temporary package folder.
        Remove-Item $tempPath -Force -Recurse -ErrorAction SilentlyContinue
    }
}

<#
    .SYNOPSIS
        Publish Audit, DeployIfNotExists and Initiative policy definistions in Azure Policy Center.
 
    .Parameter Name
        Policy name.
 
    .Parameter Version
        Policy version.
 
    .Parameter ContentUri
        Public http uri of Guest Configuration content package.
 
    .Parameter DisplayName
        Policy display name.
 
    .Parameter Description
        Policy description.
 
    .Parameter Platform
        Target platform (Windows/Linux) for Guest Configuration policy and content package.
        Windows is the default platform.
 
    .Example
        New-GuestConfigurationPolicy -Name AuditService `
                                 -Version 1.0.0.0 `
                                 -ContentUri https://github.com/azure/auditservice/release/AuditService.zip `
                                 -DisplayName 'Monitor Windows Service Policy.' `
                                 -Description 'Policy to monitor service on Windows machine.' `
#>

function New-GuestConfigurationPolicy
{
    [CmdletBinding()]
    param (
        [parameter(Position=0, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Name,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [version] $Version,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $ContentUri,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $DisplayName,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Description,

        [ValidateNotNullOrEmpty()]
        [string] $DesintationPath,

        [Parameter()]
        [ValidateSet('Windows', 'Linux')]
        [string]
        $Platform = 'Windows'
    )

    Try {
        $policyDefinitionsPath = $DesintationPath
        if([string]::IsNullOrEmpty($DesintationPath)) {
            $policyDefinitionsPath = New-Item -ItemType Directory -Force -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid()))
        }

        # Generate checksum hash for policy content.
        $tempContentPackageFilePath = Join-Path $policyDefinitionsPath 'temp.zip'
        Invoke-WebRequest -Uri $ContentUri -OutFile $tempContentPackageFilePath
        $contentHash = (Get-FileHash $tempContentPackageFilePath -Algorithm SHA256).Hash

        $DeployPolicyInfo = @{
            FileName = "$Name-Deploy.json"
            DisplayName = "[Deploy] $DisplayName"
            Description = $Description 
            ConfigurationName = $Name
            ConfigurationVersion = $Version
            ContentUri = $ContentUri
            ContentHash = $contentHash
            ReferenceId = "Deploy_$Name"
        }
        $AuditPolicyInfo = @{
            FileName = "$Name-Audit.json"
            DisplayName = "[Audit] $DisplayName"
            Description = $Description 
            ConfigurationName = $Name
            ReferenceId = "Audit_$Name"
        }
        $InitiativeInfo = @{
            FileName = "$Name-Initiative.json"
            DisplayName = "[Initiative] $DisplayName"
            Description = $Description 
        }

        Write-Verbose "Creating policy definitions at $policyDefinitionsPath path."
        New-CustomGuestConfigPolicy -PolicyFolderPath $policyDefinitionsPath -DeployPolicyInfo $DeployPolicyInfo -AuditPolicyInfo $AuditPolicyInfo -InitiativeInfo $InitiativeInfo -Platform $Platform

        $rmContext = Get-AzureRmContext
        Write-Verbose "Publishing $Name policy using '$($rmContext.Name)' AzureRmContext."
        $rmContext = Get-AzureRmContext

        # Publish policies
        $subscriptionId = $rmContext.Subscription.Id
        foreach ($policy in @("$Name-Audit.json", "$Name-Deploy.json")){
            $policyFile = join-path $policyDefinitionsPath $policy
            $jsonDefinition = Get-Content $policyFile | ConvertFrom-Json | ForEach-Object {$_}
            $definitionContent = $jsonDefinition.Properties

            Write-Verbose "Publishing '$($jsonDefinition.properties.displayName)' ..."
            New-AzureRmPolicyDefinition -Name $jsonDefinition.name `
                -DisplayName $($definitionContent.DisplayName | ConvertTo-Json -Depth 20) `
                -Description $($definitionContent.Description | ConvertTo-Json -Depth 20).replace('"','') `
                -Policy $($definitionContent.policyRule | ConvertTo-Json -Depth 20) `
                -Metadata $($definitionContent.Metadata | ConvertTo-Json -Depth 20) `
                -Verbose
        }

        # Process initiative
        $initiativeFile = join-path $policyDefinitionsPath "$Name-Initiative.json"
        $jsonDefinition = Get-Content $initiativeFile | ConvertFrom-Json | ForEach-Object {$_}

        # Update with subscriptionId
        foreach($definitions in $jsonDefinition.properties.policyDefinitions){
            $definitions.policyDefinitionId = "/subscriptions/$subscriptionId" + $definitions.policyDefinitionId
        }

        Write-Verbose "Publishing '$($jsonDefinition.properties.displayName)' ..."
        $initiativeContent = $jsonDefinition.Properties
        New-AzureRmPolicySetDefinition -Name $jsonDefinition.name `
            -DisplayName $($initiativeContent.DisplayName | ConvertTo-Json -Depth 20) `
            -Description $($initiativeContent.Description | ConvertTo-Json -Depth 20).replace('"','') `
            -PolicyDefinition $($initiativeContent.policyDefinitions | ConvertTo-Json -Depth 20) `
            -Metadata $($initiativeContent.Metadata | ConvertTo-Json -Depth 20) `
            -Verbose
    }
    Finally {
        # Remove temporary content package.
        Remove-Item $tempContentPackageFilePath -Force -ErrorAction SilentlyContinue

        # Remove temporary folder.
        if([string]::IsNullOrEmpty($DesintationPath)) {
            Remove-Item $policyDefinitionsPath -Force -Recurse -ErrorAction SilentlyContinue
        }
    }
}

<#
    Private functions.
#>

function Test-GuestConfigurationMofResourceDependencies
{
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Path
    )

    $resourcesInMofDocument = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($Path, 4)

    # Verify that only supported resources are used in DSC configuration.
    $notSupportedResources = @()
    $resourcesInMofDocument | ForEach-Object {
        $className = $_.CimClass.CimClassName
        $friendlyName = $className

        # Get the resource friendly name.
        if($_.CimInstanceProperties.Name -contains 'ResourceID') {
            $friendlyName = ($_.ResourceID -split ']')[0].Substring(1)
        }

        # Full resource name
        if($_.CimInstanceProperties.Name -contains 'ModuleName' -and $_.CimInstanceProperties.Name -contains 'ModuleVersion') {
            $friendlyName = $friendlyName + ' <' + $_.ModuleName + ' ' + $_.ModuleVersion + '>'
        }

        if($_.CimInstanceProperties.Name -contains 'ModuleName' -and $_.ModuleName -ieq 'GuestConfiguration') {
            # Only 'GuestConfiguration' module resources are allowed.
        }
        elseif($reservedResourceName -inotcontains $className)
        {
            $notSupportedResources += $friendlyName
        }
    }

    if($notSupportedResources.Length) {
        Throw "Resource(s) '$($notSupportedResources -join ',')', not supported by Guest Configuration. Please run 'Get-DscResource -Module GuestConfiguration' to see the list of supported resources."
    }
}

function Get-GuestConfigurationMofContent
{
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Name,

        [Parameter(Mandatory = $true)]
        [String]
        $Path
    )

    Write-Verbose "Parsing Configuration document '$Configuration'"
    $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
{
    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 | % {
                $content += " $($_.Name)"
                if($_.CimType -eq 'StringArray') {
                    $content += " = {""$($_.Value)""}; `n"
                }
                else {
                    $content += " = ""$($_.Value)""; `n"
                }
            }
            $content += "};`n" ;
        }

        $content | Out-File $DestinationPath
    }
    else {
        Write-Verbose "Copy DSC document to $DestinationPath path ..."
        Copy-Item $SourcePath $DestinationPath
    }
}

function Format-Json
{
    [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-InGuestDeployPolicy
{
    [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'
    )

    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 = 'Guest Configuration'
                requiredProviders = @(
                    'Microsoft.GuestConfiguration'
                )
            }
        }
    }

    $policyRuleHashtable = [Ordered]@{
        if = [Ordered]@{
            allOf = @(
                [Ordered]@{
                    field = 'type'
                    equals = 'Microsoft.Compute/virtualMachines'
                }
            )
        }
        then = [Ordered]@{
            effect = 'deployIfNotExists'
            details = [Ordered]@{
                type = 'Microsoft.GuestConfiguration/guestConfigurationAssignments'
                name = $ConfigurationName
                roleDefinitionIds = @('/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c')
                deployment = [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 = '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()
            }
        }
    }

    if ($Platform -ieq 'Windows')
    {
        $policyRuleHashtable['if']['allOf'] += @(
            [Ordered]@{
                anyOf = @(
                    [Ordered]@{
                        field = 'Microsoft.Compute/imagePublisher'
                        in = @(
                            'MicrosoftDynamicsAX',
                            'MicrosoftWindowsDesktop',
                            'MicrosoftVisualStudio',
                            'incredibuild',
                            'MicrosoftWindowsServerHPCPack',
                            'esri'
                        )
                    },
                    [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/imageSKU'
                                notEquals = 'SQL2008R2SP3-WS2008R2SP1'
                            }
                        )
                    },
                    [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*'
                            }
                        )
                    }
                )
            }
        )

        $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 = 'Microsoft.Compute/imagePublisher'
                        in = @(
                            'microsoft-aks',
                            'AzureDatabricks',
                            '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 = 'RedHat'
                            },
                            [Ordered]@{ 
                                field = 'Microsoft.Compute/imageOffer'
                                equals = 'RHEL'
                            },
                            [Ordered]@{
                                field = 'Microsoft.Compute/imageSKU'
                                notLike = '6*'
                            }
                        )
                    },
                    [Ordered]@{
                        allOf = @(
                            [Ordered]@{ 
                                field = 'Microsoft.Compute/imagePublisher'
                                equals = 'RedHat'
                            },
                            [Ordered]@{ 
                                field = 'Microsoft.Compute/imageOffer'
                                equals = 'osa'
                            }
                        )
                    },
                    [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*'
                            }
                        )
                    }
                )
            }
        )

        $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."
    }

    # Handle adding parameters if needed
    if ($null -ne $ParameterInfo -and $ParameterInfo.Count -gt 0)
    {
        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
                        description = $currentParameterInfo.Description
                    }
                }
            }

            $policyRuleHashtable['then']['details']['deployment']['properties']['parameters'] += [Ordered]@{
                $currentParameterInfo.ReferenceName = [Ordered]@{
                    value = "[parameters('$($currentParameterInfo.ReferenceName)')]"
                }
            }

            $policyRuleHashtable['then']['details']['deployment']['properties']['template']['parameters'] += [Ordered]@{
                $currentParameterInfo.ReferenceName = [Ordered]@{
                    type = $currentParameterInfo.Type
                }
            }

            if ($currentParameterInfo.ContainsKey('Value'))
            {
                $guestConfigurationAssignmentHashtable['properties']['guestConfiguration']['configurationParameter'] += [Ordered]@{
                    name = "$($currentParameterInfo.MofResourceReference);$($currentParameterInfo.MofParameterName)"
                    value = $currentParameterInfo.Value
                }
            }
            else
            {
                $guestConfigurationAssignmentHashtable['properties']['guestConfiguration']['configurationParameter'] += [Ordered]@{
                    name = "$($currentParameterInfo.MofResourceReference);$($currentParameterInfo.MofParameterName)"
                    value = "[parameters('$($currentParameterInfo.ReferenceName)')]"
                }
            }
        }
    }

    $policyRuleHashtable['then']['details']['deployment']['properties']['template']['resources'] += $guestConfigurationAssignmentHashtable
    $policyRuleHashtable['then']['details']['deployment']['properties']['template']['resources'] += [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'] += $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
}

function New-InGuestAuditPolicy
{
    [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
    )

    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 = 'Guest Configuration'
            }
            policyRule = [Ordered]@{
                if = [Ordered]@{
                    allOf = @(
                        [Ordered]@{
                            field = 'type'
                            equals = 'Microsoft.GuestConfiguration/guestConfigurationAssignments'
                        },
                        [Ordered]@{
                            field = 'name'
                            equals = $configurationName
                        },
                        [Ordered]@{
                            field = 'Microsoft.GuestConfiguration/guestConfigurationAssignments/complianceStatus'
                            notEquals = 'Compliant'
                        }
                    )
                }
                then = [Ordered]@{
                    effect = 'audit'
                }
            }
        }
        id = "/providers/Microsoft.Authorization/policyDefinitions/$auditPolicyGuid"
        name = $auditPolicyGuid
    }

    $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
}

function New-InGuestPolicyInitiative
{
    [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
                            description = $currentParameterInfo.Description
                        }
                    }
                }

                $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
}

function New-InGuestPolicy
{
    [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-InGuestDeployPolicy @currentDeployPolicyInfo -Platform $Platform
        $currentDeployPolicyInfo['Guid'] = $deployPolicyGuid
    }

    foreach ($currentAuditPolicyInfo in $AuditPolicyInfo)
    {
        $currentAuditPolicyInfo['FolderPath'] = $PolicyFolderPath
        $auditPolicyGuid = New-InGuestAuditPolicy @currentAuditPolicyInfo
        $currentAuditPolicyInfo['Guid'] = $auditPolicyGuid
    }

    $InitiativeInfo['FolderPath'] = $PolicyFolderPath
    $InitiativeInfo['DeployPolicyInfo'] = $DeployPolicyInfo
    $InitiativeInfo['AuditPolicyInfo'] = $AuditPolicyInfo

    $initiativeGuid = New-InGuestPolicyInitiative @InitiativeInfo
}

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'
    )

    $existingDeployPolicy = Get-AzureRmPolicyDefinition | Where-Object {$_.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
    }

    $existingAuditPolicy = Get-AzureRmPolicyDefinition | Where-Object {$_.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
    }

    $existingInitiative = Get-AzureRmPolicySetDefinition  | Where-Object {$_.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
    }

    New-InGuestPolicy @PSBoundParameters
}

Export-ModuleMember -Function @('New-GuestConfigurationPackage', 'New-GuestConfigurationPolicy')