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 DestinationPath
        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] $DestinationPath = '.'
    )

    Try {
        $reservedResourceName = @('OMI_ConfigurationDocument')
        $tempPath = New-Item -ItemType Directory -Force -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid()))
        $Configuration = Resolve-Path $Configuration

        if(-not (Test-Path -Path $Configuration -PathType Leaf)) {
            Throw "Invalid mof file path, please specify full file path for dsc configuration in -Configuration parameter."
        }
         
        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')
        $guestConfigModulePath = New-Item -ItemType Directory -Force -Path (Join-Path $modulePath 'GuestConfiguration')
        $latestModule = (Get-Module GuestConfiguration -ListAvailable)[0]
        Copy-Item "$($latestModule.ModuleBase)/*" $guestConfigModulePath -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 $DestinationPath $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
        Create Audit, DeployIfNotExists and Initiative policy definitions on specified DestinationPath.
 
    .Parameter ContentUri
        Public http uri of Guest Configuration content package.
 
    .Parameter DisplayName
        Policy display name.
 
    .Parameter Description
        Policy description.
 
    .Parameter Version
        Policy version.
 
    .Parameter Version
        Destination path.
 
    .Parameter Platform
        Target platform (Windows/Linux) for Guest Configuration policy and content package.
        Windows is the default platform.
 
    .Example
        New-GuestConfigurationPolicy `
                                 -ContentUri https://github.com/azure/auditservice/release/AuditService.zip `
                                 -DisplayName 'Monitor Windows Service Policy.' `
                                 -Description 'Policy to monitor service on Windows machine.' `
                                 -Version 1.0.0.0
                                 -DestinationPath c:\git\custom_policy
#>

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

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

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

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

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

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

    Try {
        $policyDefinitionsPath = $DestinationPath
        $unzippedPkgPath = Join-Path $policyDefinitionsPath 'temp'
        $tempContentPackageFilePath = Join-Path $policyDefinitionsPath 'temp.zip'

        New-Item -ItemType Directory -Force -Path $policyDefinitionsPath | Out-Null

        # Check if ContentUri is a valid web Uri
        $uri = $ContentUri -as [System.URI]
        if(-not ($uri.AbsoluteURI -ne $null -and $uri.Scheme -match '[http|https]')) {
            Throw "Invalid ContentUri : $ContentUri. Please specify a valid http URI in -ContentUri parameter."
        }

        # Generate checksum hash for policy content.
        Invoke-WebRequest -Uri $ContentUri -OutFile $tempContentPackageFilePath
        $tempContentPackageFilePath = Resolve-Path $tempContentPackageFilePath
        $contentHash = (Get-FileHash $tempContentPackageFilePath -Algorithm SHA256).Hash
        Write-Verbose "SHA256 Hash for content '$ContentUri' : $contentHash."

        # Get the policy name from policy content.
        Remove-Item $unzippedPkgPath -Recurse -Force -ErrorAction SilentlyContinue
        New-Item -ItemType Directory -Force -Path $unzippedPkgPath | Out-Null
        $unzippedPkgPath = Resolve-Path $unzippedPkgPath
        [System.IO.Compression.ZipFile]::ExtractToDirectory($tempContentPackageFilePath, $unzippedPkgPath)
        $dscDocument = Get-ChildItem -Path $unzippedPkgPath -Filter *.mof
        if(-not $dscDocument) {
            Throw "Invalid policy package, failed to find dsc document in policy package."
        }
        $policyName = [System.IO.Path]::GetFileNameWithoutExtension($dscDocument)

        $DeployPolicyInfo = @{
            FileName = "DeployIfNotExists.json"
            DisplayName = "[Deploy] $DisplayName"
            Description = $Description 
            ConfigurationName = $policyName
            ConfigurationVersion = $Version
            ContentUri = $ContentUri
            ContentHash = $contentHash
            ReferenceId = "Deploy_$policyName"
        }
        $AuditPolicyInfo = @{
            FileName = "Audit.json"
            DisplayName = "[Audit] $DisplayName"
            Description = $Description 
            ConfigurationName = $policyName
            ReferenceId = "Audit_$policyName"
        }
        $InitiativeInfo = @{
            FileName = "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
    }
    Finally {
        # Remove temporary content package.
        Remove-Item $tempContentPackageFilePath -Force -ErrorAction SilentlyContinue
        Remove-Item $unzippedPkgPath -Recurse -Force -ErrorAction SilentlyContinue
    }
}

<#
    .SYNOPSIS
        Publish Guest Configuration policy in Azure Policy Center.
 
    .Parameter Path
        Guest Configuration policy path.
 
    .Example
        Publish-GuestConfigurationPolicy -Path c:\git\custom_policy
#>

function Publish-GuestConfigurationPolicy
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Path
    )

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

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

        Write-Verbose "Publishing '$($jsonDefinition.properties.displayName)' ..."
        New-AzPolicyDefinition -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 $Path "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-AzPolicySetDefinition -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
}

<#
    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'))]",
                "[concat('Microsoft.Compute/virtualMachines/',parameters('vmName'),'/extensions','/EnableHashValidation')]"
            )
        }

        $guestConfigurationEnableCustomPolicyExtensionHashtable = [Ordered]@{
            apiVersion = '2018-06-01'
            name = "[concat(parameters('vmName'),'/EnableHashValidation')]"
            type = 'Microsoft.Compute/virtualMachines/extensions'
            location = "[parameters('location')]"
            properties = [Ordered]@{
                publisher = 'Microsoft.Compute'
                type = 'CustomScriptExtension'
                typeHandlerVersion = '1.9'
                settings = @{}
                protectedSettings = @{
                    commandToExecute = 'powershell -c \" & {if(!(Test-Path HKLM:/Software/Microsoft/Windows/DSC)){New-Item -Path HKLM:/Software/Microsoft/Windows/DSC -Force; New-ItemProperty -Path HKLM:/Software/Microsoft/Windows/DSC -Name EnableCustomPolicyHashValidation -Value 1 -PropertyType DWORD -Force}else {New-ItemProperty -Path HKLM:/Software/Microsoft/Windows/DSC -Name EnableCustomPolicyHashValidation -Value 1 -PropertyType DWORD -Force}} \" '
                }
            }
        }
    }
    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'))]"
            )
        }

        $guestConfigurationEnableCustomPolicyExtensionHashtable = [Ordered]@{
            apiVersion = '2015-06-15'
            name = "[concat(parameters('vmName'),'/EnableHashValidation')]"
            type = 'Microsoft.Compute/virtualMachines/extensions'
            location = "[parameters('location')]"
            properties = [Ordered]@{
                publisher = 'Microsoft.Azure.Extensions'
                type = 'CustomScript'
                typeHandlerVersion = '2.0'
                settings = @{}
                protectedSettings = @{
                    script = 'IyEvdXNyL2Jpbi9lbnYgcHl0aG9uCmZyb20gX19mdXR1cmVfXyBpbXBvcnQgcHJpbnRfZnVuY3Rpb24KaW1wb3J0IG9zLHN5cwppbXBvcnQganNvbgppbXBvcnQgc3VicHJvY2VzcwoKZGVmIHByaW50X2Vycm9yKCphcmdzLCAqKmt3YXJncyk6CiAgICBwcmludCgqYXJncywgZmlsZT1zeXMuc3RkZXJyLCAqKmt3YXJncykKICAgIApwYXRoID0gJy92YXIvbGliL3dhYWdlbnQnCmd1ZXN0X2NvbmZpZ19kaXJzID0gb3MubGlzdGRpcihwYXRoKQpmb3IgZ3Vlc3RfY29uZmlnX2RpciBpbiBndWVzdF9jb25maWdfZGlyczoKICAgIGZ1bGxfcGF0aCA9IG9zLnBhdGguam9pbihwYXRoLCBndWVzdF9jb25maWdfZGlyKQogICAgaWYgb3MucGF0aC5pc2RpcihmdWxsX3BhdGgpOgogICAgICAgIGlmICdHdWVzdENvbmZpZ3VyYXRpb24uQ29uZmlndXJhdGlvbmZvckxpbnV4JyBpbiBmdWxsX3BhdGg6CiAgICAgICAgICAgIGRzY19mb2xkZXJfZnVsbF9wYXRoID0gb3MucGF0aC5qb2luKGZ1bGxfcGF0aCwgJ0dDQWdlbnQvRFNDJykKICAgICAgICAgICAgaWYob3MucGF0aC5leGlzdHMoZHNjX2ZvbGRlcl9mdWxsX3BhdGgpKToKICAgICAgICAgICAgICAgIGRzY19jb25maWdfZnVsbF9wYXRoID0gb3MucGF0aC5qb2luKGRzY19mb2xkZXJfZnVsbF9wYXRoLCAnZHNjLmNvbmZpZycpCiAgICAgICAgICAgICAgICBkYXRhID0ge30KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgaWYob3MucGF0aC5leGlzdHMoZHNjX2NvbmZpZ19mdWxsX3BhdGgpKToKICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4oZHNjX2NvbmZpZ19mdWxsX3BhdGgsICdyJykgYXMganNvbkZpbGU6CiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBqc29uLmxvYWQoanNvbkZpbGUpCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgZGF0YVsnUG9saWN5J10gPSB7fQogICAgICAgICAgICAgICAgZGF0YVsnUG9saWN5J11bJ0VuYWJsZUhhc2hWYWxpZGF0aW9uJ10gPSBUcnVlCiAgICAgICAgICAgICAgICB3aXRoIG9wZW4oZHNjX2NvbmZpZ19mdWxsX3BhdGgsICd3JykgYXMgb3V0ZmlsZTogIAogICAgICAgICAgICAgICAgICBqc29uLmR1bXAoZGF0YSwgb3V0ZmlsZSkKICAgICAgICAgICAgICAgIHByaW50KCdSZXN0YXJ0aW5nIGRzYyBzZXJ2aWNlIC4uLicpCiAgICAgICAgICAgICAgICBiYXNoQ29tbWFuZCA9ICJzZXJ2aWNlIGRzY2QgcmVzdGFydCIKICAgICAgICAgICAgICAgIHByb2Nlc3MgPSBzdWJwcm9jZXNzLlBvcGVuKGJhc2hDb21tYW5kLnNwbGl0KCksIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUpCiAgICAgICAgICAgICAgICBvdXRwdXQsIGVycm9yID0gcHJvY2Vzcy5jb21tdW5pY2F0ZSgpCiAgICAgICAgICAgICAgICBwcmludChvdXRwdXQpCiAgICAgICAgICAgICAgICBzeXMuZXhpdCgwKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcHJpbnRfZXJyb3Ioc3RyKCdDb3VsZCBub3QgZmluZCAnKSArIGRzY19mb2xkZXJfZnVsbF9wYXRoICsgJyBwYXRoLiBQbGVhc2UgbWFrZSBzdXJlIEd1ZXN0Q29uZmlndXJhdGlvbiBBZ2VudCBpcyBpbnN0YWxsZWQuJykKICAgICAgICAgICAgICAgIHN5cy5leGl0KDEpCnByaW50X2Vycm9yKHN0cignR3Vlc3RDb25maWd1cmF0aW9uIEFnZW50IGlzIG5vdCBpbnN0YWxsZWQuJykpCnN5cy5leGl0KDEp'
                }
            }
            dependsOn = @(
                "[concat('Microsoft.Compute/virtualMachines/',parameters('vmName'),'/extensions','/AzurePolicyforLinux')]"
            )
        }
    }
    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
    $policyRuleHashtable['then']['details']['deployment']['properties']['template']['resources'] += $guestConfigurationEnableCustomPolicyExtensionHashtable

    $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-AzPolicyDefinition | 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-AzPolicyDefinition | 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-AzPolicySetDefinition  | 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', 'Publish-GuestConfigurationPolicy')