GuestConfiguration.psm1

#Region './prefix.ps1' 0
$currentCulture = [System.Globalization.CultureInfo]::CurrentCulture
if ($currentCulture.Name -eq 'en-US-POSIX')
{
    throw "'$($currentCulture.Name)' culture is not supported, please change to 'en-US'"
}
#EndRegion './prefix.ps1' 6
#Region './Private/ConvertTo-OrderedHashtable.ps1' 0
function ConvertTo-OrderedHashtable
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObject
    )

    if ($null -eq $InputObject)
    {
        $output = $null
    }
    elseif ($InputObject -is [PSCustomObject])
    {
        $output = [Ordered]@{}

        foreach ($property in $InputObject.PSObject.Properties)
        {
            $propertyValue = ConvertTo-OrderedHashtable -InputObject $property.Value
            if ($property.Value -is [System.Collections.IEnumerable] -and $property.Value -isnot [string])
            {
                $output[$property.Name] = @( $propertyValue )
            }
            else
            {
                $output[$property.Name] = $propertyValue
            }
        }
    }
    elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
    {
        $output = @()

        foreach ($object in $InputObject)
        {
            $output += ConvertTo-OrderedHashtable -InputObject $object
        }
    }
    else
    {
        $output = $InputObject
    }

    return $output
}
#EndRegion './Private/ConvertTo-OrderedHashtable.ps1' 47
#Region './Private/Edit-GuestConfigurationPackageMofChefInSpecContent.ps1' 0

function Edit-GuestConfigurationPackageMofChefInSpecContent
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $PackageName,

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

    Write-Verbose -Message "Editing the mof at '$MofPath' to update native InSpec resource parameters"

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

    foreach ($mofInstance in $mofInstances)
    {
        $resourceClassName = $mofInstance.CimClass.CimClassName

        if ($resourceClassName -ieq 'MSFT_ChefInSpecResource')
        {
            $profilePath = "$PackageName/Modules/$($mofInstance.Name)/"

            $gitHubPath = $mofInstance.CimInstanceProperties.Item('GithubPath')
            if ($null -eq $gitHubPath)
            {
                $gitHubPath = [Microsoft.Management.Infrastructure.CimProperty]::Create('GithubPath', $profilePath, [Microsoft.Management.Infrastructure.CimFlags]::Property)
                $mofInstance.CimInstanceProperties.Add($gitHubPath)
            }
            else
            {
                $gitHubPath.Value = $profilePath
            }
        }
    }

    Write-MofContent -MofInstances $mofInstances -OutputPath $MofPath
}
#EndRegion './Private/Edit-GuestConfigurationPackageMofChefInSpecContent.ps1' 43
#Region './Private/Format-PolicyDefinitionJson.ps1' 0
function Format-PolicyDefinitionJson
{
    [CmdletBinding()]
    [OutputType([String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Json
    )

    $indent = 0
    $jsonLines = $Json -Split '\n'
    $formattedLines = @()
    $previousLine = ''

    foreach ($line in $jsonLines)
    {
        $skipAddingLine = $false
        if ($line -match '^\s*\}\s*' -or $line -match '^\s*\]\s*')
        {
            # This line contains ] or }, decrement the indentation level
            $indent--
        }

        $formattedLine = (' ' * $indent * 4) + $line.TrimStart().Replace(': ', ': ')

        if ($line -match '\s*".*"\s*:\s*\[' -or $line -match '\s*".*"\s*:\s*\{' -or $line -match '^\s*\{\s*' -or $line -match '^\s*\[\s*')
        {
            # This line contains [ or {, increment the indentation level
            $indent++
        }

        if ($previousLine.Trim().EndsWith("{"))
        {
            if ($formattedLine.Trim() -in @("}", "},"))
            {
                $newLine = "$($previousLine.TrimEnd())$($formattedLine.Trim())"
                #Write-Verbose -Message "FOUND SHORTENED LINE: $newLine"
                $formattedLines[($formattedLines.Count - 1)] = $newLine
                $previousLine = $newLine
                $skipAddingLine = $true
            }
        }

        if ($previousLine.Trim().EndsWith("["))
        {
            if ($formattedLine.Trim() -in @("]", "],"))
            {
                $newLine = "$($previousLine.TrimEnd())$($formattedLine.Trim())"
                #Write-Verbose -Message "FOUND SHORTENED LINE: $newLine"
                $formattedLines[($formattedLines.Count - 1)] = $newLine
                $previousLine = $newLine
                $skipAddingLine = $true
            }
        }

        if (-not $skipAddingLine -and -not [String]::IsNullOrWhiteSpace($formattedLine))
        {
            $previousLine = $formattedLine
            $formattedLines += $formattedLine
        }
    }

    $formattedJson = $formattedLines -join "`n"
    return $formattedJson
}
#EndRegion './Private/Format-PolicyDefinitionJson.ps1' 68
#Region './Private/Get-GCWorkerExePath.ps1' 0
function Get-GCWorkerExePath
{
    [CmdletBinding()]
    [OutputType([String])]
    param ()

    $gcWorkerFolderPath = Get-GCWorkerRootPath
    $binFolderPath = Join-Path -Path $gcWorkerFolderPath -ChildPath 'GC'

    $os = Get-OSPlatform

    if ($os -ieq 'Windows')
    {
        $gcWorkerExeName = 'gc_worker.exe'
    }
    else
    {
        $gcWorkerExeName = 'gc_worker'
    }

    $gcWorkerExePath = Join-Path -Path $binFolderPath -ChildPath $gcWorkerExeName

    return $gcWorkerExePath
}
#EndRegion './Private/Get-GCWorkerExePath.ps1' 25
#Region './Private/Get-GCWorkerRootPath.ps1' 0
function Get-GCWorkerRootPath
{
    [CmdletBinding()]
    [OutputType([String])]
    param ()

    $gcWorkerRootPath = Join-Path -Path $PSScriptRoot -ChildPath 'gcworker'
    return $gcWorkerRootPath
}
#EndRegion './Private/Get-GCWorkerRootPath.ps1' 10
#Region './Private/Get-GuestConfigurationPolicySectionFromTemplate.ps1' 0
function Get-GuestConfigurationPolicySectionFromTemplate
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $FileName
    )

    $templateFolderPath = Join-Path -Path $PSScriptRoot -ChildPath 'templates'
    $filePath = Join-Path -Path $templateFolderPath -ChildPath $FileName

    $fileContent = Get-Content -Path $filePath -Raw
    $fileContentObject = $fileContent | ConvertFrom-Json

    $fileContentHashtable = ConvertTo-OrderedHashtable -InputObject $fileContentObject

    return $fileContentHashtable
}
#EndRegion './Private/Get-GuestConfigurationPolicySectionFromTemplate.ps1' 23
#Region './Private/Get-ModuleDependencies.ps1' 0
function Get-ModuleDependencies
{
    [CmdletBinding()]
    [OutputType([Hashtable[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $ModuleName,

        [Parameter()]
        [String]
        $ModuleVersion,

        [Parameter()]
        [String]
        $ModuleSourcePath = $env:PSModulePath
    )

    $moduleDependencies = @()

    if ($ModuleName -ieq 'PSDesiredStateConfiguration')
    {
        throw "Found a dependency on resources from the PSDesiredStateConfiguration module, but we cannot copy these resources into the Guest Configuration package. Please switch these resources to using the PSDscResources module instead."
    }

    $getModuleParameters = @{
        ListAvailable = $true
    }

    if ([String]::IsNullOrWhiteSpace($ModuleVersion))
    {
        Write-Verbose -Message "Searching for a module with the name '$ModuleName'..."
        $getModuleParameters['Name'] = $ModuleName
    }
    else
    {
        Write-Verbose -Message "Searching for a module with the name '$ModuleName' and version '$ModuleVersion'..."
        $getModuleParameters['FullyQualifiedName'] = @{
            ModuleName = $ModuleName
            ModuleVersion = $ModuleVersion
        }
    }

    $originalPSModulePath = $env:PSModulePath

    try
    {
        $env:PSModulePath = $ModuleSourcePath
        $sourceModule = Get-Module @getModuleParameters
    }
    finally
    {
        $env:PSModulePath = $originalPSModulePath
    }

    if ($null -eq $sourceModule)
    {
        throw "Failed to find a module with the name '$ModuleName' and the version '$ModuleVersion'. Please check that the module is installed and available in your PSModulePath."
    }
    elseif ('Count' -in $sourceModule.PSObject.Properties.Name -and $sourceModule.Count -gt 1)
    {
        Write-Verbose -Message "Found $($sourceModule.Count) modules with the name '$ModuleName'..."

        $sourceModule = ($sourceModule | Sort-Object -Property 'Version' -Descending)[0]
        Write-Warning -Message "Found more than one module with the name '$ModuleName'. Using the version '$($sourceModule.Version)'."
    }

    $moduleDependency = @{
        Name = $resourceDependency['ModuleName']
        Version = $resourceDependency['ModuleVersion']
        SourcePath = $sourceModule.ModuleBase
    }

    $moduleDependencies += $moduleDependency

    # Add any modules required by this module to the package
    if ('RequiredModules' -in $sourceModule.PSObject.Properties.Name -and $null -ne $sourceModule.RequiredModules -and $sourceModule.RequiredModules.Count -gt 0)
    {
        foreach ($requiredModule in $sourceModule.RequiredModules)
        {
            Write-Verbose -Message "The module '$ModuleName' requires the module '$($requiredModule.Name)'. Attempting to copy the required module..."

            $getModuleDependenciesParameters = @{
                ModuleName = $requiredModule.Name
                ModuleVersion = $requiredModule.Version
                ModuleSourcePath = $ModuleSourcePath
            }

            $moduleDependencies += Get-ModuleDependencies @getModuleDependenciesParameters
        }
    }

    # Add any modules marked as external module dependencies by this module to the package
    if ('ExternalModuleDependencies' -in $sourceModule.PSObject.Properties.Name -and $null -ne $sourceModule.ExternalModuleDependencies -and $sourceModule.ExternalModuleDependencies.Count -gt 0)
    {
        foreach ($externalModuleDependency in $sourceModule.ExternalModuleDependencies)
        {
            Write-Verbose -Message "The module '$ModuleName' requires the module '$externalModuleDependency'. Attempting to copy the required module..."

            $getModuleDependenciesParameters = @{
                ModuleName = $requiredModule.Name
                ModuleSourcePath = $ModuleSourcePath
            }

            $moduleDependencies += Get-ModuleDependencies @getModuleDependenciesParameters
        }
    }

    return $moduleDependencies
}
#EndRegion './Private/Get-ModuleDependencies.ps1' 112
#Region './Private/Get-MofResouceDependencies.ps1' 0
function Get-MofResouceDependencies
{
    [CmdletBinding()]
    [OutputType([Hashtable[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]
        $MofFilePath
    )

    $MofFilePath = Resolve-RelativePath -Path $MofFilePath

    $resourceDependencies = @()
    $reservedResourceNames = @('OMI_ConfigurationDocument')
    $mofInstances = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($mofFilePath, 4)

    foreach ($mofInstance in $mofInstances)
    {
        if ($reservedResourceNames -inotcontains $mofInstance.CimClass.CimClassName -and $mofInstance.CimInstanceProperties.Name -icontains 'ModuleName')
        {
            $instanceName = ""

            if ($mofInstance.CimInstanceProperties.Name -icontains 'Name')
            {
                $instanceName = $mofInstance.CimInstanceProperties['Name'].Value
            }

            Write-Verbose -Message "Found resource dependency in mof with instance name '$instanceName' and resource name '$($mofInstance.CimClass.CimClassName)' from module '$($mofInstance.ModuleName)' with version '$($mofInstance.ModuleVersion)'."
            $resourceDependencies += @{
                ResourceInstanceName = $instanceName
                ResourceName = $mofInstance.CimClass.CimClassName
                ModuleName = $mofInstance.ModuleName
                ModuleVersion = $mofInstance.ModuleVersion
            }
        }
    }

    Write-Verbose -Message "Found $($resourceDependencies.Count) resource dependencies in the mof."
    return $resourceDependencies
}
#EndRegion './Private/Get-MofResouceDependencies.ps1' 42
#Region './Private/Get-OSPlatform.ps1' 0
function Get-OSPlatform
{
    [CmdletBinding()]
    [OutputType([String])]
    param ()

    $platform = 'Windows'

    if ($PSVersionTable.PSEdition -eq 'Desktop')
    {
        $platform = 'Windows'
    }
    elseif ($PSVersionTable.PSEdition -eq 'Core')
    {
        if ($IsWindows)
        {
            $platform = 'Windows'
        }
        elseif ($IsLinux)
        {
            $platform = 'Linux'
        }
        elseif ($IsMacOS)
        {
            $platform = 'MacOS'
        }
    }

    $platform
}
#EndRegion './Private/Get-OSPlatform.ps1' 31
#Region './Private/Install-GCWorker.ps1' 0
function Install-GCWorker
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [Switch]
        $Force
    )

    $workerInstallPath = Get-GCWorkerRootPath
    $logsFolderPath = Join-Path -Path $workerInstallPath -ChildPath 'logs'

    if (Test-Path -Path $workerInstallPath -PathType 'Container')
    {
        if ($Force)
        {
            $null = Remove-Item -Path $workerInstallPath -Recurse -Force
            $null = New-Item -Path $workerInstallPath -ItemType 'Directory'
        }
    }
    else
    {
        $null = New-Item -Path $workerInstallPath -ItemType 'Directory'
    }

    # The worker has 'GC' hard-coded internally, so if you change this file name, it will not work
    $binFolderDestinationPath = Join-Path -Path $workerInstallPath -ChildPath 'GC'

    if (-not (Test-Path -Path $binFolderDestinationPath -PathType 'Container'))
    {
        $null = New-Item -Path $binFolderDestinationPath -ItemType 'Directory'

        $binFolderSourcePath = Join-Path -Path $PSScriptRoot -ChildPath 'bin'

        $os = Get-OSPlatform

        if ($os -ieq 'Windows')
        {
            $windowsPackageSourcePath = Join-Path -Path $binFolderSourcePath -ChildPath 'DSC_Windows.zip'
            $null = Expand-Archive -Path $windowsPackageSourcePath -DestinationPath $binFolderDestinationPath
        }
        else
        {
            # The Linux package contains an additional folder level
            $linuxPackageSourcePath = Join-Path -Path $binFolderSourcePath -ChildPath 'DSC_Linux.zip'
            $null = Expand-Archive -Path $linuxPackageSourcePath -DestinationPath $workerInstallPath

            if (-not (Test-Path -Path $binFolderDestinationPath -PathType 'Container'))
            {
                throw "Linux agent package structure has changed. Expected a 'GC' folder within the package but the folder '$binFolderDestinationPath' does not exist."
            }

            # Fix for “LTTng-UST: Error (-17) while registering tracepoint probe. Duplicate registration of tracepoint probes having the same name is not allowed.”
            $tracePointProviderLibPath = Join-Path -Path $binFolderDestinationPath -ChildPath 'libcoreclrtraceptprovider.so'

            if (Test-Path -Path $tracePointProviderLibPath)
            {
                $null = Remove-Item -Path $tracePointProviderLibPath -Force
            }

            $bashFilesInBinFolder = @(Get-ChildItem -Path $binFolderDestinationPath -Filter "*.sh" -Recurse)

            foreach ($bashFileInBinFolder in $bashFilesInBinFolder)
            {
                chmod '+x' $bashFileInBinFolder.FullName
            }

            # Give root user permission to execute gc_worker
            chmod 700 $binFolderDestinationPath

            $gcWorkerExePath = Join-Path -Path $binFolderDestinationPath -ChildPath 'gc_worker'
            chmod '+x' $gcWorkerExePath
        }

        $logPath = Join-Path -Path $logsFolderPath -ChildPath 'gc_worker.log'
        $telemetryPath = Join-Path -Path $logsFolderPath -ChildPath 'gc_worker_telemetry.txt'
        $configurationsFolderPath = Join-Path -Path $workerInstallPath -ChildPath 'Configurations'
        $modulePath = Join-Path -Path $binFolderDestinationPath -ChildPath 'Modules'

        # The directory paths in gc.config must have trailing slashes
        $basePath = $workerInstallPath + [System.IO.Path]::DirectorySeparatorChar
        $binPath = $binFolderDestinationPath + [System.IO.Path]::DirectorySeparatorChar
        $configurationsFolderPath = $configurationsFolderPath + [System.IO.Path]::DirectorySeparatorChar
        $modulePath = $modulePath + [System.IO.Path]::DirectorySeparatorChar

        # Save GC config settings file
        $gcConfig = @{
            "DoNotSendReport" = $true
            "SaveLogsInJsonFormat" = $true
            "Paths" = @{
                "BasePath" = $basePath
                "DotNetFrameworkPath" = $binPath
                "UserConfigurationsPath" = $configurationsFolderPath
                "ModulePath" = $modulePath
                "LogPath" = $logPath
                "TelemetryPath" = $telemetryPath
            }
        }

        $gcConfigContent = $gcConfig | ConvertTo-Json

        $gcConfigPath = Join-Path -Path $binFolderDestinationPath -ChildPath 'gc.config'
        $null = Set-Content -Path $gcConfigPath -Value $gcConfigContent -Encoding 'ascii' -Force
    }
    else
    {
        Write-Verbose -Message "Guest Configuration worker binaries already installed at '$binFolderDestinationPath'"
    }

    if (-not (Test-Path -Path $logsFolderPath -PathType 'Container'))
    {
        Write-Verbose -Message "Creating the logs folder at '$logsFolderPath'"
        $null = New-Item -Path $logsFolderPath -ItemType 'Directory'
    }
}
#EndRegion './Private/Install-GCWorker.ps1' 117
#Region './Private/Invoke-GCWorker.ps1' 0
function Invoke-GCWorker
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Arguments
    )

    # Remove the logs if needed
    $gcWorkerFolderPath = Get-GCWorkerRootPath
    $gcLogPath = Join-Path -Path $gcWorkerFolderPath -ChildPath 'logs'
    $standardOutputPath = Join-Path -Path $gcLogPath -ChildPath 'gcworker_stdout.txt'

    if (Test-Path -Path $gcLogPath)
    {
        $null = Remove-Item -Path $gcLogPath -Recurse -Force
    }

    # Linux requires that this path already exists when GC worker is run
    $null = New-Item -Path $gcLogPath -ItemType 'Directory' -Force

    # Execute the publish operation through GC worker
    $gcWorkerExePath = Get-GCWorkerExePath
    $gcEnvPath = Split-Path -Path $gcWorkerExePath -Parent

    $originalEnvPath = $env:Path

    $envPathPieces = $env:Path -split ';'
    if ($envPathPieces -notcontains $gcEnvPath)
    {
        $env:Path = "$originalEnvPath;$gcEnvPath"
    }

    try
    {
        Write-Verbose -Message "Invoking GC worker with the arguments '$Arguments'"
        $null = Start-Process -FilePath $gcWorkerExePath -ArgumentList $Arguments -Wait -NoNewWindow -RedirectStandardOutput $standardOutputPath
    }
    finally
    {
        $env:Path = $originalEnvPath
    }

    # Wait for streams to close
    Start-Sleep -Seconds 1

    # Write output
    Write-GuestConfigurationLogsToConsole
}
#EndRegion './Private/Invoke-GCWorker.ps1' 53
#Region './Private/Invoke-GCWorkerRun.ps1' 0
function Invoke-GCWorkerRun
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigurationName,

        [Parameter()]
        [Switch]
        $Apply
    )

    # Remove any existing reports if needed
    $gcWorkerFolderPath = Get-GCWorkerRootPath
    $reportsFolderPath = Join-Path -Path $gcWorkerFolderPath -ChildPath 'reports'

    if (Test-Path -Path $reportsFolderPath)
    {
        $null = Remove-Item -Path $reportsFolderPath -Recurse -Force
    }

    $arguments = "-o run_consistency -a $ConfigurationName -r "

    if ($Apply)
    {
        $arguments += "-s inguest_apply_and_monitor "
    }

    $arguments += "-c Pending"

    Invoke-GCWorker -Arguments $arguments

    $compliantReportFileName = "{0}_Compliant.json" -f $ConfigurationName
    $compliantReportFilePath = Join-Path -Path $reportsFolderPath -ChildPath $compliantReportFileName
    $compliantReportFileExists = Test-Path -Path $compliantReportFilePath -PathType 'Leaf'

    $nonCompliantReportFileName = "{0}_NonCompliant.json" -f $ConfigurationName
    $nonCompliantReportFilePath = Join-Path -Path $reportsFolderPath -ChildPath $nonCompliantReportFileName
    $nonCompliantReportFileExists = Test-Path -Path $nonCompliantReportFilePath -PathType 'Leaf'

    if ($compliantReportFileExists -and $nonCompliantReportFileExists)
    {
        Write-Warning -Message "Both a compliant report and non-compliant report exist for the package $ConfigurationName"

        $compliantReportFile = Get-Item -Path $compliantReportFilePath
        $nonCompliantReportFile = Get-Item -Path $nonCompliantReportFilePath

        if ($compliantReportFile.LastWriteTime -gt $nonCompliantReportFile.LastWriteTime)
        {
            Write-Warning -Message "Using last compliant report since it has a later LastWriteTime"
            $reportFilePath = $compliantReportFilePath
        }
        elseif ($compliantReportFile.LastWriteTime -lt $nonCompliantReportFile.LastWriteTime)
        {
            Write-Warning -Message "Using last non-compliant report since it has a later LastWriteTime"
            $reportFilePath = $nonCompliantReportFilePath
        }
        else
        {
            throw "The reports have the same LastWriteTime. Please remove the reports under '$reportsFolderPath' and try again."
        }
    }
    elseif ($compliantReportFileExists)
    {
        $reportFilePath = $compliantReportFilePath
    }
    elseif ($nonCompliantReportFileExists)
    {
        $reportFilePath = $nonCompliantReportFilePath
    }
    else
    {
        $logPath = Join-Path -Path $gcWorkerFolderPath -ChildPath 'logs'
        throw "No report was generated. The package likely was not formed correctly or crashed. Please check the logs under the path '$logPath'."
    }

    $reportContent = Get-Content -Path $reportFilePath -Raw
    $report = $reportContent | ConvertFrom-Json

    return $report
}
#EndRegion './Private/Invoke-GCWorkerRun.ps1' 86
#Region './Private/Invoke-GuestConfigurationPackage.ps1' 0
function Invoke-GuestConfigurationPackage
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo]
        $Path,

        [Parameter()]
        [Hashtable[]]
        $Parameter = @(),

        [Parameter()]
        [Switch]
        $Apply
    )

    $os = Get-OSPlatform
    if ($os -ieq 'MacOS')
    {
        throw 'This cmdlet is not supported on MacOS.'
    }

    #-----VALIDATE PARAMETERS-----
    $requiredParameterProperties = @('ResourceType', 'ResourceId', 'ResourcePropertyName', 'ResourcePropertyValue')

    foreach ($parameterInfo in $Parameter)
    {
        foreach ($requiredParameterProperty in $requiredParameterProperties)
        {
            if (-not ($parameterInfo.Keys -contains $requiredParameterProperty))
            {
                $requiredParameterPropertyString = $requiredParameterProperties -join ', '
                throw "One of the specified parameters is missing the mandatory property '$requiredParameterProperty'. The mandatory properties for parameters are: $requiredParameterPropertyString"
            }

            if ($parameterInfo[$requiredParameterProperty] -isnot [string])
            {
                $requiredParameterPropertyString = $requiredParameterProperties -join ', '
                throw "The property '$requiredParameterProperty' of one of the specified parameters is not a string. All parameter property values must be strings."
            }
        }
    }

    #-----VALIDATE PACKAGE SETUP-----
    $Path = Resolve-RelativePath -Path $Path

    if (-not (Test-Path -Path $Path -PathType 'Leaf'))
    {
        throw "No zip file found at the path '$Path'. Please specify the file path to a compressed Guest Configuration package (.zip) with the Path parameter."
    }

    $sourceZipFile = Get-Item -Path $Path

    if ($sourceZipFile.Extension -ine '.zip')
    {
        throw "The file found at the path '$Path' is not a .zip file. It has extension '$($sourceZipFile.Extension)'. Please specify the file path to a compressed Guest Configuration package (.zip) with the Path parameter."
    }

    # Install the Guest Configuration worker if needed
    Install-GCWorker

    # Extract the package
    $gcWorkerPath = Get-GCWorkerRootPath
    $gcWorkerPackagesFolderPath = Join-Path -Path $gcWorkerPath -ChildPath 'packages'

    $packageInstallFolderName = $sourceZipFile.BaseName
    $packageInstallPath = Join-Path -Path $gcWorkerPackagesFolderPath -ChildPath $packageInstallFolderName

    if (Test-Path -Path $packageInstallPath)
    {
        $null = Remove-Item -Path $packageInstallPath -Recurse -Force
    }

    $null = Expand-Archive -Path $Path -DestinationPath $packageInstallPath -Force

    # Find and validate the mof file
    $mofFilePattern = '*.mof'
    $mofChildItems = @( Get-ChildItem -Path $packageInstallPath -Filter $mofFilePattern -File )

    if ($mofChildItems.Count -eq 0)
    {
        throw "No .mof file found in the package. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
    }
    elseif ($mofChildItems.Count -gt 1)
    {
        throw "Found more than one .mof file in the extracted Guest Configuration package. Please remove any extra .mof files from the root of the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
    }

    $mofFile = $mofChildItems[0]
    $packageName = $mofFile.BaseName

    # Get package version
    $metaconfigFileName = "{0}.metaconfig.json" -f $packageName
    $metaconfigFilePath = Join-Path -Path $packageInstallPath -ChildPath $metaconfigFileName

    if (Test-Path -Path $metaconfigFilePath)
    {
        $metaconfig = Get-Content -Path $metaconfigFilePath -Raw | ConvertFrom-Json | ConvertTo-OrderedHashtable

        if ($metaconfig.Keys -contains 'Version')
        {
            $packageVersion = $metaconfig['Version']
            Write-Verbose -Message "Package has the version $packageVersion"
        }

        if ($metaconfig.Keys -contains 'Type')
        {
            $packageType = $metaconfig['Type']
            Write-Verbose -Message "Package has the type $packageType"

            if ($packageType -eq 'Audit' -and $Apply)
            {
                throw 'The specified package has been marked as Audit-only. You cannot apply/remediate an Audit-only package. Please change the type of the package to "AuditAndSet" with New-GuestConfigurationPackage if you would like to apply/remediate with this package.'
            }
        }
        else
        {
            Write-Warning -Message "Failed to determine the package type from the metaconfig file '$metaconfigFileName' in the package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
        }
    }
    else
    {
        Write-Warning -Message "Failed to find the metaconfig file '$metaconfigFileName' in the package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
    }

    # Rename the package install folder to match what the GC worker expects if needed
    if ($packageName -ne $packageInstallFolderName)
    {
        $newPackageInstallPath = Join-Path -Path $gcWorkerPackagesFolderPath -ChildPath $packageName

        if (Test-Path -Path $newPackageInstallPath)
        {
            $null = Remove-Item -Path $newPackageInstallPath -Recurse -Force
        }

        $null = Rename-Item -Path $packageInstallPath -NewName $newPackageInstallPath
        $packageInstallPath = $newPackageInstallPath
    }

    $mofFilePath = Join-Path -Path $packageInstallPath -ChildPath $mofFile.Name

    # Validate dependencies
    $resourceDependencies = @( Get-MofResouceDependencies -MofFilePath $mofFilePath )

    if ($resourceDependencies.Count -le 0)
    {
        throw "Failed to determine resource dependencies from the .mof file in the package. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
    }

    $usingInSpecResource = $false
    $moduleDependencies = @()
    $inSpecProfileNames = @()

    $modulesFolderPath = Join-Path -Path $packageInstallPath -ChildPath 'Modules'

    foreach ($resourceDependency in $resourceDependencies)
    {
        if ($resourceDependency['ResourceName'] -ieq 'MSFT_ChefInSpecResource')
        {
            $usingInSpecResource = $true
            $inSpecProfileNames += $resourceDependency['ResourceInstanceName']
            continue
        }

        $getModuleDependenciesParameters = @{
            ModuleName = $resourceDependency['ModuleName']
            ModuleVersion = $resourceDependency['ModuleVersion']
            ModuleSourcePath = $modulesFolderPath
        }

        $moduleDependencies += Get-ModuleDependencies @getModuleDependenciesParameters
    }

    if ($moduleDependencies.Count -gt 0)
    {
        Write-Verbose -Message "Found the module dependencies: $($moduleDependencies.Name)"
    }

    $duplicateModules = @( $moduleDependencies | Group-Object -Property 'Name' | Where-Object { $_.Count -gt 1 } )

    foreach ($duplicateModule in $duplicateModules)
    {
        $uniqueVersions = @( $duplicateModule.Group.Version | Get-Unique )

        if ($uniqueVersions.Count -gt 1)
        {
            $moduleName = $duplicateModule.Group[0].Name
            throw "Cannot include more than one version of a module in one package. Detected versions $uniqueVersions of the module '$moduleName' are needed for this package."
        }
    }

    if ($usingInSpecResource)
    {
        $metaConfigName = "$packageName.metaconfig.json"
        $metaConfigPath = Join-Path -Path $packageInstallPath -ChildPath $metaConfigName
        $metaConfigContent = Get-Content -Path $metaConfigPath -Raw
        $metaConfig = $metaConfigContent | ConvertFrom-Json
        $packageType = $metaConfig.Type

        if ($packageType -ieq 'AuditAndSet')
        {
            throw "The type of this package was specified as 'AuditAndSet', but native InSpec resource was detected in the provided .mof file. This resource does not currently support the set scenario and can only be used for 'Audit' packages."
        }

        Write-Verbose -Message "Expecting the InSpec profiles: $($inSpecProfileNames)"

        foreach ($expectedInSpecProfileName in $inSpecProfileNames)
        {
            $inSpecProfilePath = Join-Path -Path $modulesFolderPath -ChildPath $expectedInSpecProfileName
            $inSpecProfile = Get-Item -Path $inSpecProfilePath -ErrorAction 'SilentlyContinue'

            if ($null -eq $inSpecProfile)
            {
                throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but there is no item at this path."
            }
            elseif ($inSpecProfile -isnot [System.IO.DirectoryInfo])
            {
                throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but the item at this path is not a directory."
            }
            else
            {
                $inSpecProfileYmlFileName = 'inspec.yml'
                $inSpecProfileYmlFilePath = Join-Path -Path $inSpecProfilePath -ChildPath $inSpecProfileYmlFileName

                if (-not (Test-Path -Path $inSpecProfileYmlFilePath -PathType 'Leaf'))
                {
                    throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but there is no file named '$inSpecProfileYmlFileName' under this path."
                }
            }
        }
    }

    #-----RUN PACKAGE-----

    # Update package metaconfig to use debug mode and force module imports
    $metaconfigName = "$packageName.metaconfig.json"
    $metaconfigPath = Join-Path -Path $packageInstallPath -ChildPath $metaconfigName
    $propertiesToUpdate = @{
        debugMode = 'ForceModuleImport'
    }

    if ($Apply)
    {
        $propertiesToUpdate['configurationMode'] = 'ApplyAndMonitor'
    }

    Set-GuestConfigurationPackageMetaconfigProperty -MetaconfigPath $metaconfigPath -Property $propertiesToUpdate

    # Update package configuration parameters
    if ($null -ne $Parameter -and $Parameter.Count -gt 0)
    {
        Set-GuestConfigurationPackageParameters -Path $mofFilePath -Parameter $Parameter
    }

    # Publish the package via GC worker
    Publish-GCWorkerAssignment -PackagePath $packageInstallPath

    # Set GC worker settings for the package
    Set-GCWorkerSettings -PackagePath $packageInstallPath

    # Invoke GC worker
    $result = Invoke-GCWorkerRun -ConfigurationName $packageName -Apply:$Apply

    return $result
}
#EndRegion './Private/Invoke-GuestConfigurationPackage.ps1' 270
#Region './Private/New-GuestConfigurationPolicyActionSection.ps1' 0
function New-GuestConfigurationPolicyActionSection
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigurationName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigurationVersion,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ContentUri,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ContentHash,

        [Parameter()]
        [ValidateSet('Audit', 'ApplyAndAutoCorrect', 'ApplyAndMonitor')]
        [String]
        $AssignmentType = 'Audit',

        [Parameter()]
        [Hashtable[]]
        $Parameter
    )

    if ($AssignmentType -ieq 'Audit')
    {
        $actionSection = New-GuestConfigurationPolicyAuditActionSection -ConfigurationName $ConfigurationName -Parameter $Parameter
    }
    else
    {
        $setActionSectionParameters = @{
            ConfigurationName = $ConfigurationName
            ConfigurationVersion = $ConfigurationVersion
            ContentUri = $ContentUri
            ContentHash = $ContentHash
            AssignmentType = $AssignmentType
            Parameter = $Parameter
        }

        $actionSection = New-GuestConfigurationPolicySetActionSection @setActionSectionParameters
    }

    if ($null -ne $Parameter -and $Parameter.Count -gt 0)
    {
        $complianceCondition = $actionSection.details.existenceCondition

        $parameterHashStringList = @()

        foreach ($currentParameter in $Parameter)
        {
            $parameterReference = New-GuestConfigurationPolicyParameterReferenceString -Parameter $currentParameter
            $parameterValue = "parameters('$($currentParameter['Name'])')"
            $currentParameterHashString = "'$parameterReference', '=', $parameterValue"
            $parameterHashStringList += $currentParameterHashString
        }

        $concantenatedParameterHashStrings = $parameterHashStringList -join ", ',', "
        $parameterHashString = "[base64(concat($concantenatedParameterHashStrings))]"

        $parameterCondition = [Ordered]@{
            field = 'Microsoft.GuestConfiguration/guestConfigurationAssignments/parameterHash'
            equals = $parameterHashString
        }

        $actionSection.details.existenceCondition = [Ordered]@{
            allOf = @(
                $complianceCondition,
                $parameterCondition
            )
        }
    }

    return $actionSection
}
#EndRegion './Private/New-GuestConfigurationPolicyActionSection.ps1' 87
#Region './Private/New-GuestConfigurationPolicyAuditActionSection.ps1' 0
function New-GuestConfigurationPolicyAuditActionSection
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $ConfigurationName,

        [Parameter()]
        [Hashtable[]]
        $Parameter
    )

    $templateFileName = "4-Action-Audit.json"
    $auditActionSection = Get-GuestConfigurationPolicySectionFromTemplate -FileName $templateFileName

    $assignmentName = New-GuestConfigurationPolicyGuestAssignmentNameReference -ConfigurationName $ConfigurationName

    $auditActionSection.details.name = $assignmentName

    return $auditActionSection
}
#EndRegion './Private/New-GuestConfigurationPolicyAuditActionSection.ps1' 25
#Region './Private/New-GuestConfigurationPolicyConditionsSection.ps1' 0
function New-GuestConfigurationPolicyConditionsSection
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Windows', 'Linux')]
        [String]
        $Platform,

        [Parameter()]
        [System.Collections.Hashtable]
        $Tag
    )

    $templateFileName = "3-Images-$Platform.json"
    $conditionsSection = Get-GuestConfigurationPolicySectionFromTemplate -FileName $templateFileName

    if ($null -ne $Tag -and $Tag.Count -gt 0)
    {
        $tagConditionList = @()

        foreach ($tagName in $Tag.Keys)
        {
            $tagConditionList += [Ordered]@{
                field  = "tags['$tagName']"
                equals = $($Tag[$tagName])
            }
        }

        if ($tagConditionList.Count -eq 1)
        {
            $tagConditions = $tagConditionList[0]
        }
        elseif ($tagConditionList.Count -gt 1)
        {
            $tagConditions = [Ordered]@{
                allOf = $tagConditionList
            }
        }

        $conditionsSection = [Ordered]@{
            allOf = @(
                $conditionsSection,
                $tagConditions
            )
        }
    }

    return $conditionsSection
}
#EndRegion './Private/New-GuestConfigurationPolicyConditionsSection.ps1' 53
#Region './Private/New-GuestConfigurationPolicyContent.ps1' 0
function New-GuestConfigurationPolicyContent
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $DisplayName,

        [Parameter(Mandatory = $true)]
        [String]
        $Description,

        [Parameter(Mandatory = $true)]
        [String]
        $PolicyVersion,

        [Parameter(Mandatory = $true)]
        [String]
        $ConfigurationName,

        [Parameter(Mandatory = $true)]
        [String]
        $ConfigurationVersion,

        [Parameter(Mandatory = $true)]
        [String]
        $ContentUri,

        [Parameter(Mandatory = $true)]
        [String]
        $ContentHash,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Windows', 'Linux')]
        [String]
        $Platform,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Audit', 'ApplyAndMonitor', 'ApplyAndAutoCorrect')]
        [String]
        $AssignmentType,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [String]
        $PolicyId,

        [Parameter()]
        [Hashtable[]]
        $Parameter,

        [Parameter()]
        [Hashtable]
        $Tag
    )

    $metadataSectionParameters = @{
        DisplayName = $DisplayName
        Description = $Description
        PolicyVersion = $PolicyVersion
        ConfigurationName = $ConfigurationName
        ConfigurationVersion = $ConfigurationVersion
        ContentUri = $ContentUri
        ContentHash = $ContentHash
        Parameter = $Parameter
    }

    $metadataSection = New-GuestConfigurationPolicyMetadataSection @metadataSectionParameters

    $parametersSection = New-GuestConfigurationPolicyParametersSection -Parameter $Parameter

    $conditionsSection = New-GuestConfigurationPolicyConditionsSection -Platform $Platform -Tag $Tag

    $actionSectionParameters = @{
        ConfigurationName = $ConfigurationName
        ConfigurationVersion = $ConfigurationVersion
        ContentUri = $ContentUri
        ContentHash = $ContentHash
        AssignmentType = $AssignmentType
        Parameter = $Parameter
    }

    $actionSection = New-GuestConfigurationPolicyActionSection @actionSectionParameters

    $policyDefinitionContent = [Ordered]@{
        properties = $metadataSection + $parametersSection + [Ordered]@{
            policyRule = [Ordered]@{
                if = $conditionsSection
                then = $actionSection
            }
        }
        name = $PolicyId
    }

    return $policyDefinitionContent
}
#EndRegion './Private/New-GuestConfigurationPolicyContent.ps1' 99
#Region './Private/New-GuestConfigurationPolicyGuestAssignmentNameReference.ps1' 0
function New-GuestConfigurationPolicyGuestAssignmentNameReference
{
    [CmdletBinding()]
    [OutputType([String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $ConfigurationName
    )

    $guestAssignmentName = "[concat('{0}`$pid', uniqueString(policy().assignmentId, policy().definitionReferenceId))]" -f $ConfigurationName
    return $guestAssignmentName
}
#EndRegion './Private/New-GuestConfigurationPolicyGuestAssignmentNameReference.ps1' 15
#Region './Private/New-GuestConfigurationPolicyMetadataSection.ps1' 0
function New-GuestConfigurationPolicyMetadataSection
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $DisplayName,

        [Parameter(Mandatory = $true)]
        [String]
        $Description,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $PolicyVersion,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigurationName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigurationVersion,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ContentUri,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ContentHash,

        [Parameter()]
        [Hashtable[]]
        $Parameter
    )

    $templateFileName = '1-Metadata.json'
    $propertiesSection = Get-GuestConfigurationPolicySectionFromTemplate -FileName $templateFileName

    $propertiesSection.displayName = $DisplayName
    $propertiesSection.description = $Description

    $propertiesSection.metadata.version = $PolicyVersion

    $propertiesSection.metadata.guestConfiguration = [Ordered]@{
        name = $ConfigurationName
        version = $ConfigurationVersion
        contentType = 'Custom'
        contentUri = $ContentUri
        contentHash = $ContentHash
    }

    if ($null -ne $Parameter -and $Parameter.Count -gt 0)
    {
        $propertiesSection.metadata.guestConfiguration.configurationParameter = [Ordered]@{}

        foreach ($currentParameter in $Parameter)
        {
            $parameterName = $currentParameter['Name']
            $parameterReferenceString = New-GuestConfigurationPolicyParameterReferenceString -Parameter $currentParameter

            $propertiesSection.metadata.guestConfiguration.configurationParameter.$parameterName = $parameterReferenceString
        }
    }

    return $propertiesSection
}
#EndRegion './Private/New-GuestConfigurationPolicyMetadataSection.ps1' 76
#Region './Private/New-GuestConfigurationPolicyParameterReferenceString.ps1' 0
function New-GuestConfigurationPolicyParameterReferenceString
{
    [CmdletBinding()]
    [OutputType([String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [Hashtable]
        $Parameter
    )

    return "[$($Parameter['ResourceType'])]$($Parameter['ResourceId']);$($Parameter['ResourcePropertyName'])"
}
#EndRegion './Private/New-GuestConfigurationPolicyParameterReferenceString.ps1' 14
#Region './Private/New-GuestConfigurationPolicyParametersSection.ps1' 0
function New-GuestConfigurationPolicyParametersSection
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter()]
        [Hashtable[]]
        $Parameter
    )

    $templateFileName = '2-Parameters.json'
    $parametersSection = Get-GuestConfigurationPolicySectionFromTemplate -FileName $templateFileName

    $optionalFields = @('AllowedValues', 'DefaultValue')

    foreach ($currentParameter in $Parameter)
    {
        $parameterName = $currentParameter['Name']
        $parametersSection.parameters.$parameterName = [Ordered]@{
            type = 'string'
            metadata = [Ordered]@{
                displayName = $currentParameter['DisplayName']
                description = $currentParameter['Description']
            }
        }

        foreach ($optionalField in $optionalFields)
        {
            if ($currentParameter.Keys -contains $optionalField)
            {
                $fieldName = $optionalField.Substring(0, 1).ToLower() + $optionalField.Substring(1)
                $parametersSection.parameters.$parameterName.$fieldName = $currentParameter[$optionalField]
            }
        }
    }

    return $parametersSection
}
#EndRegion './Private/New-GuestConfigurationPolicyParametersSection.ps1' 40
#Region './Private/New-GuestConfigurationPolicySetActionSection.ps1' 0
function New-GuestConfigurationPolicySetActionSection
{
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $ConfigurationName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigurationVersion,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ContentUri,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ContentHash,

        [Parameter(Mandatory = $true)]
        [ValidateSet('ApplyAndMonitor', 'ApplyAndAutoCorrect')]
        [String]
        $AssignmentType,

        [Parameter()]
        [Hashtable[]]
        $Parameter
    )

    $templateFileName = "4-Action-Set.json"
    $setActionSection = Get-GuestConfigurationPolicySectionFromTemplate -FileName $templateFileName

    $assignmentName = New-GuestConfigurationPolicyGuestAssignmentNameReference -ConfigurationName $ConfigurationName

    $setActionSection.details.name = $assignmentName

    $setActionSection.details.deployment.properties.parameters.assignmentName.value = $assignmentName

    foreach ($currentParameter in $Parameter)
    {
        $parameterName = $currentParameter['Name']
        $setActionSection.details.deployment.properties.parameters.$parameterName = [Ordered]@{
            value = "[parameters('$parameterName')]"
        }
        $setActionSection.details.deployment.properties.template.parameters.$parameterName = [Ordered]@{
            type = "string"
        }
    }

    $guestConfigMetadataSection = [Ordered]@{
        name = $ConfigurationName
        version = $ConfigurationVersion
        contentType = 'Custom'
        contentUri = $ContentUri
        contentHash = $ContentHash
        assignmentType = $AssignmentType
    }

    if ($null -ne $Parameter -and $Parameter.Count -gt 0)
    {
        $guestConfigMetadataSection.configurationParameter = @()

        foreach ($currentParameter in $Parameter)
        {
            $parameterName = $currentParameter['Name']
            $parameterReferenceString = New-GuestConfigurationPolicyParameterReferenceString -Parameter $currentParameter

            $guestConfigMetadataSection.configurationParameter += [Ordered]@{
                name = $parameterReferenceString
                value = "[parameters('$parameterName')]"
            }
        }
    }

    $setActionSection.details.deployment.properties.template.resources[0].properties.guestConfiguration = $guestConfigMetadataSection
    $setActionSection.details.deployment.properties.template.resources[1].properties.guestConfiguration = $guestConfigMetadataSection

    return $setActionSection
}
#EndRegion './Private/New-GuestConfigurationPolicySetActionSection.ps1' 86
#Region './Private/Publish-GCWorkerAssignment.ps1' 0
function Publish-GCWorkerAssignment
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $PackagePath
    )

    if (-not (Test-Path -Path $PackagePath))
    {
        throw "No Guest Configuration package found at the path '$PackagePath'"
    }

    $PackagePath = Resolve-RelativePath -Path $PackagePath
    $packageName = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)

    if ($PackagePath.EndsWith([System.IO.Path]::DirectorySeparatorChar))
    {
        $PackagePath = $PackagePath.TrimEnd([System.IO.Path]::DirectorySeparatorChar)
    }

    $arguments = "-o publish_assignment -a $packageName -p `"$PackagePath`""

    Invoke-GCWorker -Arguments $arguments
}
#EndRegion './Private/Publish-GCWorkerAssignment.ps1' 29
#Region './Private/Reset-GCWorkerTempDirectory.ps1' 0
function Reset-GCWorkerTempDirectory
{
    [CmdletBinding()]
    [OutputType([String])]
    param ()

    $gcWorkerRootPath = Get-GCWorkerRootPath
    $tempPath = Join-Path -Path $gcWorkerRootPath -ChildPath 'temp'

    if (Test-Path -Path $tempPath)
    {
        $null = Remove-Item -Path $tempPath -Recurse -Force
    }

    $null = New-Item -Path $tempPath -ItemType 'Directory' -Force

    return $tempPath
}
#EndRegion './Private/Reset-GCWorkerTempDirectory.ps1' 19
#Region './Private/Resolve-RelativePath.ps1' 0
function Resolve-RelativePath
{
    [CmdletBinding()]
    [OutputType([String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path
    )

    # This one doesn't work in PS 5.1
    #$currentLocation = Get-Location
    #$fullPath = [System.IO.Path]::GetFullPath($Path, $currentLocation)

    # This doesn't work when the path doesn't exist yet
    #$fullPath = Convert-Path -Path $Path

    $fullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    return $fullPath
}
#EndRegion './Private/Resolve-RelativePath.ps1' 23
#Region './Private/Set-GCWorkerSettings.ps1' 0
function Set-GCWorkerSettings
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position=1, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $PackagePath
    )

    if (-not (Test-Path -Path $PackagePath))
    {
        throw "No Guest Configuration package found at the path '$PackagePath'"
    }

    $PackagePath = Resolve-RelativePath -Path $PackagePath
    $packageName = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)

    if ($PackagePath.EndsWith([System.IO.Path]::DirectorySeparatorChar))
    {
        $PackagePath = $PackagePath.TrimEnd([System.IO.Path]::DirectorySeparatorChar)
    }

    $arguments = "-o set_agent_settings -a $packageName -p `"$PackagePath`""

    Invoke-GCWorker -Arguments $arguments
}
#EndRegion './Private/Set-GCWorkerSettings.ps1' 29
#Region './Private/Set-GuestConfigurationPackageMetaconfigProperty.ps1' 0
function Set-GuestConfigurationPackageMetaconfigProperty
{
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $MetaconfigPath,

        [Parameter(Mandatory = $true)]
        [Hashtable]
        $Property
    )

    if (Test-Path -Path $MetaconfigPath)
    {
        $metaconfigContent = Get-Content -Path $MetaconfigPath -Raw
        $metaconfig = $metaconfigContent | ConvertFrom-Json | ConvertTo-OrderedHashtable
    }
    else
    {
        $metaconfig = [Ordered]@{}
    }

    foreach ($propertyName in $Property.Keys)
    {
        $metaconfig[$propertyName] = $Property[$propertyName]
    }

    $metaconfigJson = $metaconfig | ConvertTo-Json

    Write-Verbose -Message "Setting the content of the package metaconfig at the path '$MetaconfigPath'..."
    $null = Set-Content -Path $MetaconfigPath -Value $metaconfigJson -Encoding 'ascii' -Force
}
#EndRegion './Private/Set-GuestConfigurationPackageMetaconfigProperty.ps1' 36
#Region './Private/Set-GuestConfigurationPackageParameters.ps1' 0
function Set-GuestConfigurationPackageParameters
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Path,

        [Parameter()]
        [Hashtable[]]
        $Parameter
    )

    if ($Parameter.Count -eq 0)
    {
        return
    }

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

    foreach ($parameterInfo in $Parameter)
    {
        if ($parameterInfo.Keys -notcontains 'ResourceType')
        {
            throw "Policy parameter is missing a mandatory property 'ResourceType'. Please make sure that configuration resource type is specified in configuration parameter."
        }

        if ($parameterInfo.Keys -notcontains 'ResourceId')
        {
            throw "Policy parameter is missing a mandatory property 'ResourceId'. Please make sure that configuration resource Id is specified in configuration parameter."
        }

        if ($parameterInfo.Keys -notcontains 'ResourcePropertyName')
        {
            throw "Policy parameter is missing a mandatory property 'ResourcePropertyName'. Please make sure that configuration resource property name is specified in configuration parameter."
        }

        if ($parameterInfo.Keys -notcontains 'ResourcePropertyValue')
        {
            throw "Policy parameter is missing a mandatory property 'ResourcePropertyValue'. Please make sure that configuration resource property value is specified in configuration parameter."
        }

        $resourceId = "[$($parameterInfo.ResourceType)]$($parameterInfo.ResourceId)"

        $matchingMofInstance = @( $mofInstances | Where-Object {
            ($_.CimInstanceProperties.Name -contains 'ResourceID') -and
            ($_.CimInstanceProperties['ResourceID'].Value -ieq $resourceId) -and
            ($_.CimInstanceProperties.Name -icontains $parameterInfo.ResourcePropertyName)
        })

        if ($null -eq $matchingMofInstance -or $matchingMofInstance.Count -eq 0)
        {
            throw "Failed to find a matching parameter reference with ResourceType:'$($parameterInfo.ResourceType)', ResourceId:'$($parameterInfo.ResourceId)' and ResourcePropertyName:'$($parameterInfo.ResourcePropertyName)' in the configuration. Please ensure that this resource instance exists in the configuration."
        }

        if ($matchingMofInstance.Count -gt 1)
        {
            throw "Found more than one matching parameter reference with ResourceType:'$($parameterInfo.ResourceType)', ResourceId:'$($parameterInfo.ResourceId)' and ResourcePropertyName:'$($parameterInfo.ResourcePropertyName)'. Please ensure that only one resource instance with this information exists in the configuration."
        }

        $mofInstanceParameter = $matchingMofInstance[0].CimInstanceProperties.Item($parameterInfo.ResourcePropertyName)
        $mofInstanceParameter.Value = $parameterInfo.ResourcePropertyValue
    }

    Write-MofContent -MofInstances $mofInstances -OutputPath $Path
}
#EndRegion './Private/Set-GuestConfigurationPackageParameters.ps1' 68
#Region './Private/Write-GuestConfigurationLogsToConsole.ps1' 0
function Write-GuestConfigurationLogsToConsole
{
    [CmdletBinding()]
    param ()

    $gcWorkerFolderPath = Get-GCWorkerRootPath
    $gcLogFolderPath = Join-Path -Path $gcWorkerFolderPath -ChildPath 'logs'
    $gcLogPath = Join-Path -Path $gcLogFolderPath -ChildPath 'gc_agent.json'

    if (Test-Path -Path $gcLogPath)
    {
        $gcLogContent = Get-Content -Path $gcLogPath -Raw
        $gcLog = $gcLogContent | ConvertFrom-Json

        foreach ($logEvent in $gcLog)
        {
            if ($logEvent.type -ieq 'warning')
            {
                Write-Verbose -Message $logEvent.message
            }
            elseif ($logEvent.type -ieq 'error')
            {
                Write-Error -Message $logEvent.message
            }
            else
            {
                Write-Verbose -Message $logEvent.message
            }
        }
    }
}
#EndRegion './Private/Write-GuestConfigurationLogsToConsole.ps1' 32
#Region './Private/Write-MofContent.ps1' 0
function Write-MofContent
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $MofInstances,

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

    $content = ''
    $resourceCount = 0

    foreach ($mofInstance in $MofInstances)
    {
        $resourceClassName = $mofInstance.CimClass.CimClassName
        $content += "instance of $resourceClassName"

        if ($resourceClassName -ne 'OMI_ConfigurationDocument')
        {
            $content += ' as $' + "$resourceClassName$resourceCount"
        }

        $content += "`n{`n"
        foreach ($cimProperty in $mofInstance.CimInstanceProperties)
        {
            $content += " $($cimProperty.Name)"
            if ($cimProperty.CimType -eq 'StringArray')
            {
                $content += " = {""$($cimProperty.Value -replace '[""\\]','\$&')""}; `n"
            }
            else
            {
                $content += " = ""$($cimProperty.Value -replace '[""\\]','\$&')""; `n"
            }
        }

        $content += "};`n"

        $resourceCount++
    }

    $null = Set-Content -Path $OutputPath -Value $content -Force
}
#EndRegion './Private/Write-MofContent.ps1' 48
#Region './Public/Get-GuestConfigurationPackageComplianceStatus.ps1' 0

<#
    .SYNOPSIS
        Runs the given Guest Configuration package to retrieve the compliance status of the package
        on the current machine.

    .PARAMETER Path
        The path to the Guest Configuration package file (.zip) to run.

    .PARAMETER Parameter
        A list of hashtables describing the parameters to use when running the package.

        Basic Example:
        $Parameter = @(
            @{
                ResourceType = 'Service'
                ResourceId = 'windowsService'
                ResourcePropertyName = 'Name'
                ResourcePropertyValue = 'winrm'
            },
            @{
                ResourceType = 'Service'
                ResourceId = 'windowsService'
                ResourcePropertyName = 'Ensure'
                ResourcePropertyValue = 'Present'
            }
        )

        Technical Example:
        The Guest Configuration agent will replace parameter values in the compiled DSC
        configuration (.mof) file in the package before running it.
        If your compiled DSC configuration (.mof) file looked like this:

        instance of TestFile as $TestFile1ref
        {
            ModuleName = "TestFileModule";
            ModuleVersion = "1.0.0.0";
            ResourceID = "[TestFile]MyTestFile"; <--- This is both the resource type and ID
            Path = "test.txt"; <--- Here is the name of the parameter that I want to change the value of
            Content = "default";
            Ensure = "Present";
            SourceInfo = "TestFileSource";
            ConfigurationName = "TestFileConfig";
        };

        Then your parameter value would look like this:

        $Parameter = @(
            @{
                ResourceType = 'TestFile'
                ResourceId = 'MyTestFile'
                ResourcePropertyName = 'Path'
                ResourcePropertyValue = 'C:\myPath\newFile.txt'
            }
        )

    .EXAMPLE
        Get-GuestConfigurationPackageComplianceStatus -Path ./custom_policy/WindowsTLS.zip

    .EXAMPLE
        $Parameter = @(
            @{
                ResourceType = 'Service'
                ResourceId = 'windowsService'
                ResourcePropertyName = 'Name'
                ResourcePropertyValue = 'winrm'
            }
        )

        Get-GuestConfigurationPackageComplianceStatus `
            -Path ./custom_policy/AuditWindowsService.zip `
            -Parameter $Parameter

    .OUTPUTS
        Returns a PSCustomObject with the report properties from running the package.
        Here is an example output:
            additionalProperties : {}
            assignmentName : TestFilePackage
            complianceStatus : False
            endTime : 5/9/2022 11:42:12 PM
            jobId : 18df23b4-cd22-4c26-b4b7-85b91873ec41
            operationtype : Consistency
            resources : {@{complianceStatus=False; properties=; reasons=System.Object[]}}
            startTime : 5/9/2022 11:42:10 PM
#>

function Get-GuestConfigurationPackageComplianceStatus
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo]
        $Path,

        [Parameter()]
        [Hashtable[]]
        $Parameter = @()
    )

    $invokeParameters = @{
        Path = $Path
    }

    if ($null -ne $Parameter)
    {
        $invokeParameters['Parameter'] = $Parameter
    }

    $result = Invoke-GuestConfigurationPackage @invokeParameters

    return $result
}
#EndRegion './Public/Get-GuestConfigurationPackageComplianceStatus.ps1' 115
#Region './Public/New-GuestConfigurationPackage.ps1' 0

<#
    .SYNOPSIS
        Creates a package to run code on machines through Azure Guest Configuration.

    .PARAMETER Name
        The name of the Guest Configuration package.

    .PARAMETER Configuration
        The path to the compiled DSC configuration file (.mof) to base the package on.

    .PARAMETER Version
        The semantic version of the Guest Configuration package.
        The default value is '1.0.0'.

    .PARAMETER Type
        Sets a tag in the metaconfig data of the package specifying whether or not this package is
        Audit-only or can support Set/Apply functionality.

        Audit indicates that the package will only monitor settings and cannot set the state of
        the machine.
        AuditAndSet indicates that the package can be used for both monitoring and setting the
        state of the machine.

        The default value is Audit.

    .PARAMETER FrequencyMinutes
        The frequency at which Guest Configuration should run this package in minutes.
        The default value is 15.
        15 is also the mimimum value.
        Guest Configuration cannot run a package less-frequently than every 15 minutes.

    .PARAMETER Path
        The path to a folder to output the package under.
        By default the package will be created under the current working directory.

    .PARAMETER ChefInspecProfilePath
        The path to a folder containing Chef InSpec profiles to include with the package.

        The compiled DSC configuration (.mof) provided must include a reference to the native Chef
        InSpec resource with the reference name of the resources matching the name of the profile
        folder to use.

        If the compiled DSC configuration (.mof) provided includes a reference to the native Chef
        InSpec resource, then specifying a Chef InSpec profile to include with this parameter is
        required.

    .PARAMETER FilesToInclude
        The path(s) to any extra files or folders to include under the Modules path within the package.
        Please note, any files added here may not be accessible by custom modules.
        Files needed for custom modules need to be included within those modules.

    .PARAMETER Force
        If present, this function will overwrite any existing package files at the output path.

    .EXAMPLE
        New-GuestConfigurationPackage `
            -Name 'WindowsTLS' `
            -Configuration ./custom_policy/WindowsTLS/localhost.mof `
            -Path ./git/repository/release/policy/WindowsTLS

    .OUTPUTS
        Returns a PSCustomObject with the name and path of the new Guest Configuration package.
        [PSCustomObject]@{
            PSTypeName = 'GuestConfiguration.Package'
            Name = (Same as the Name parameter)
            Path = (Path to the newly created package zip file)
        }
#>

function New-GuestConfigurationPackage
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param
    (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Name,

        [Parameter(Position = 1, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo]
        $Configuration,

        [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Version = '1.0.0',

        [Parameter()]
        [ValidateSet('Audit', 'AuditAndSet')]
        [ValidateNotNullOrEmpty()]
        [String]
        $Type = 'Audit',

        [Parameter()]
        [int]
        $FrequencyMinutes = 15,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.IO.DirectoryInfo]
        $Path = $(Get-Item -Path $(Get-Location)),

        [Parameter()]
        [System.IO.DirectoryInfo]
        $ChefInspecProfilePath,

        [Parameter()]
        [String[]]
        $FilesToInclude,

        [Parameter()]
        [Switch]
        $Force
    )

    Write-Verbose -Message 'Starting New-GuestConfigurationPackage'

    $Configuration = Resolve-RelativePath -Path $Configuration
    $Path = Resolve-RelativePath -Path $Path

    if (-not [String]::IsNullOrEmpty($ChefInspecProfilePath))
    {
        $ChefInspecProfilePath = Resolve-RelativePath -Path $ChefInspecProfilePath
    }

    #-----VALIDATION-----

    if ($FrequencyMinutes -lt 15)
    {
        throw "FrequencyMinutes must be 15 or greater. Guest Configuration cannot run packages more frequently than every 15 minutes."
    }

    # Validate mof
    if (-not (Test-Path -Path $Configuration -PathType 'Leaf'))
    {
        throw "No file found at the path '$Configuration'. Please specify the file path to a compiled DSC configuration (.mof) with the Configuration parameter."
    }

    $sourceMofFile = Get-Item -Path $Configuration

    if ($sourceMofFile.Extension -ine '.mof')
    {
        throw "The file found at the path '$Configuration' is not a .mof file. It has extension '$($sourceMofFile.Extension)'. Please specify the file path to a compiled DSC configuration (.mof) with the Configuration parameter."
    }

    # Validate dependencies
    $resourceDependencies = @( Get-MofResouceDependencies -MofFilePath $Configuration )

    if ($resourceDependencies.Count -le 0)
    {
        throw "Failed to determine resource dependencies from the mof at the path '$Configuration'. Please specify the file path to a compiled DSC configuration (.mof) with the Configuration parameter."
    }

    $usingInSpecResource = $false
    $moduleDependencies = @()
    $inSpecProfileNames = @()

    foreach ($resourceDependency in $resourceDependencies)
    {
        if ($resourceDependency['ResourceName'] -ieq 'MSFT_ChefInSpecResource')
        {
            $usingInSpecResource = $true
            $inSpecProfileNames += $resourceDependency['ResourceInstanceName']
            continue
        }

        $getModuleDependenciesParameters = @{
            ModuleName = $resourceDependency['ModuleName']
            ModuleVersion = $resourceDependency['ModuleVersion']
        }

        $moduleDependencies += Get-ModuleDependencies @getModuleDependenciesParameters
    }

    if ($moduleDependencies.Count -gt 0)
    {
        Write-Verbose -Message "Found the module dependencies: $($moduleDependencies.Name)"
    }

    $duplicateModules = @( $moduleDependencies | Group-Object -Property 'Name' | Where-Object { $_.Count -gt 1 } )

    foreach ($duplicateModule in $duplicateModules)
    {
        $uniqueVersions = @( $duplicateModule.Group.Version | Get-Unique )

        if ($uniqueVersions.Count -gt 1)
        {
            $moduleName = $duplicateModule.Group[0].Name
            throw "Cannot include more than one version of a module in one package. Detected versions $uniqueVersions of the module '$moduleName' are needed for this package."
        }
    }

    $inSpecProfileSourcePaths = @()

    if ($usingInSpecResource)
    {
        Write-Verbose -Message "Expecting the InSpec profiles: $($inSpecProfileNames)"

        if ($Type -ieq 'AuditAndSet')
        {
            throw "The type of this package was specified as 'AuditAndSet', but native InSpec resource was detected in the provided .mof file. This resource does not currently support the set scenario and can only be used for 'Audit' packages."
        }

        if ([String]::IsNullOrEmpty($ChefInspecProfilePath))
        {
            throw "The native InSpec resource was detected in the provided .mof file, but no InSpec profiles folder path was provided. Please provide the path to an InSpec profiles folder via the ChefInspecProfilePath parameter."
        }
        else
        {
            $inSpecProfileFolder = Get-Item -Path $ChefInspecProfilePath -ErrorAction 'SilentlyContinue'

            if ($null -eq $inSpecProfileFolder)
            {
                throw "The native InSpec resource was detected in the provided .mof file, but the specified path to the InSpec profiles folder does not exist. Please provide the path to an InSpec profiles folder via the ChefInspecProfilePath parameter."
            }
            elseif ($inSpecProfileFolder -isnot [System.IO.DirectoryInfo])
            {
                throw "The native InSpec resource was detected in the provided .mof file, but the specified path to the InSpec profiles folder is not a directory. Please provide the path to an InSpec profiles folder via the ChefInspecProfilePath parameter."
            }
            else
            {
                foreach ($expectedInSpecProfileName in $inSpecProfileNames)
                {
                    $inSpecProfilePath = Join-Path -Path $ChefInspecProfilePath -ChildPath $expectedInSpecProfileName
                    $inSpecProfile = Get-Item -Path $inSpecProfilePath -ErrorAction 'SilentlyContinue'

                    if ($null -eq $inSpecProfile)
                    {
                        throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but there is no item at this path."
                    }
                    elseif ($inSpecProfile -isnot [System.IO.DirectoryInfo])
                    {
                        throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but the item at this path is not a directory."
                    }
                    else
                    {
                        $inSpecProfileYmlFileName = 'inspec.yml'
                        $inSpecProfileYmlFilePath = Join-Path -Path $inSpecProfilePath -ChildPath $inSpecProfileYmlFileName

                        if (Test-Path -Path $inSpecProfileYmlFilePath -PathType 'Leaf')
                        {
                            $inSpecProfileSourcePaths += $inSpecProfilePath
                        }
                        else
                        {
                            throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but there file named '$inSpecProfileYmlFileName' under this path."
                        }
                    }
                }
            }
        }
    }
    elseif (-not [String]::IsNullOrEmpty($ChefInspecProfilePath))
    {
        throw "A Chef InSpec profile path was provided, but the native InSpec resource was not detected in the provided .mof file. Please provide a compiled DSC configuration (.mof) that references the native InSpec resource or remove the reference to the ChefInspecProfilePath parameter."
    }

    # Check extra files if needed
    foreach ($file in $FilesToInclude)
    {
        $filePath = Resolve-RelativePath -Path $file
        if (-not (Test-Path -Path $filePath))
        {
            throw "The item to include from the path '$filePath' does not exist. Please update or remove the FilesToInclude parameter."
        }
    }

    # Check set-up folder
    $packageRootPath = Join-Path -Path $Path -ChildPath $Name

    if (Test-Path -Path $packageRootPath)
    {
        if (-not $Force)
        {
            throw "A folder already exists at the package folder path '$packageRootPath'. Please remove it or use the Force parameter. With -Force the cmdlet will remove this folder for you."
        }
    }

    # Check destination
    $packageDestinationPath = "$packageRootPath.zip"

    if (Test-Path -Path $packageDestinationPath)
    {
        if (-not $Force)
        {
            throw "A file already exists at the package destination path '$packageDestinationPath'. Please remove it or use the Force parameter. With -Force the cmdlet will remove this file for you."
        }
    }

    #-----PACKAGE CREATION-----

    # Clear the root package folder
    if (Test-Path -Path $packageRootPath)
    {
        if ($Configuration.FullName.Contains($packageRootPath))
        {
            Write-Warning -Message "You have elected to forcibly remove the existing package folder path '$packageRootPath', but the specificed source path for the configuration document is under this path at '$Configuration'. The configuration document at this source path will be changed to match package requirements."
            $gcWorkerTempPath = Reset-GCWorkerTempDirectory
            $copiedMof = Copy-Item -Path $Configuration -Destination $gcWorkerTempPath -Force
            $Configuration = $copiedMof.FullName
        }

        Write-Verbose -Message "Removing an existing item at the path '$packageRootPath'..."
        $null = Remove-Item -Path $packageRootPath -Recurse -Force
    }

    Write-Verbose -Message "Creating the package root folder at the path '$packageRootPath'..."
    $null = New-Item -Path $packageRootPath -ItemType 'Directory' -Force

    # Clear the package destination
    if (Test-Path -Path $packageDestinationPath)
    {
        Write-Verbose -Message "Removing an existing item at the path '$packageDestinationPath'..."
        $null = Remove-Item -Path $packageDestinationPath -Recurse -Force
    }

    # Create the package structure
    $modulesFolderPath = Join-Path -Path $packageRootPath -ChildPath 'Modules'
    Write-Verbose -Message "Creating the package Modules folder at the path '$modulesFolderPath'..."
    $null = New-Item -Path $modulesFolderPath -ItemType 'Directory'

    # Create the metaconfig file
    $metaconfigFileName = "$Name.metaconfig.json"
    $metaconfigFilePath = Join-Path -Path $packageRootPath -ChildPath $metaconfigFileName

    $metaconfig = @{
        Type = $Type
        Version = $Version
    }

    if ($FrequencyMinutes -gt 15)
    {
        $metaconfig['configurationModeFrequencyMins'] = $FrequencyMinutes
    }

    $metaconfigJson = $metaconfig | ConvertTo-Json
    Write-Verbose -Message "Setting the content of the package metaconfig at the path '$metaconfigFilePath'..."
    $null = Set-Content -Path $metaconfigFilePath -Value $metaconfigJson -Encoding 'ascii'

    # Copy the mof into the package
    $mofFileName = "$Name.mof"
    $mofFilePath = Join-Path -Path $packageRootPath -ChildPath $mofFileName

    Write-Verbose -Message "Copying the compiled DSC configuration (.mof) from the path '$Configuration' to the package path '$mofFilePath'..."
    $null = Copy-Item -Path $Configuration -Destination $mofFilePath

    # Edit the native Chef InSpec resource parameters in the mof if needed
    if ($usingInSpecResource)
    {
        Edit-GuestConfigurationPackageMofChefInSpecContent -PackageName $Name -MofPath $mofFilePath
    }

    # Copy resource dependencies
    foreach ($moduleDependency in $moduleDependencies)
    {
        $moduleDestinationPath = Join-Path -Path $modulesFolderPath -ChildPath $moduleDependency['Name']

        Write-Verbose -Message "Copying module from '$($moduleDependency['SourcePath'])' to '$moduleDestinationPath'"
        $null = Copy-Item -Path $moduleDependency['SourcePath'] -Destination $moduleDestinationPath -Container -Recurse -Force
    }

    # Copy native Chef InSpec resource if needed
    if ($usingInSpecResource)
    {
        $nativeResourcesFolder = Join-Path -Path $modulesFolderPath -ChildPath 'DscNativeResources'
        Write-Verbose -Message "Creating the package native resources folder at the path '$nativeResourcesFolder'..."
        $null = New-Item -Path $nativeResourcesFolder -ItemType 'Directory'

        $inSpecResourceFolder = Join-Path -Path $nativeResourcesFolder -ChildPath 'MSFT_ChefInSpecResource'
        Write-Verbose -Message "Creating the native Chef InSpec resource folder at the path '$inSpecResourceFolder'..."
        $null = New-Item -Path $inSpecResourceFolder -ItemType 'Directory'

        $dscResourcesFolderPath = Join-Path -Path $PSScriptRoot -ChildPath 'DscResources'
        $inSpecResourceSourcePath = Join-Path -Path $dscResourcesFolderPath -ChildPath 'MSFT_ChefInSpecResource'

        $installInSpecScriptSourcePath = Join-Path -Path $inSpecResourceSourcePath -ChildPath 'install_inspec.sh'
        Write-Verbose -Message "Copying the Chef Inspec install script from the path '$installInSpecScriptSourcePath' to the package path '$modulesFolderPath'..."
        $null = Copy-Item -Path $installInSpecScriptSourcePath -Destination $modulesFolderPath

        $inSpecResourceLibrarySourcePath = Join-Path -Path $inSpecResourceSourcePath -ChildPath 'libMSFT_ChefInSpecResource.so'
        Write-Verbose -Message "Copying the native Chef Inspec resource library from the path '$inSpecResourceLibrarySourcePath' to the package path '$inSpecResourceFolder'..."
        $null = Copy-Item -Path $inSpecResourceLibrarySourcePath -Destination $inSpecResourceFolder

        $inSpecResourceSchemaMofSourcePath = Join-Path -Path $inSpecResourceSourcePath -ChildPath 'MSFT_ChefInSpecResource.schema.mof'
        Write-Verbose -Message "Copying the native Chef Inspec resource schema from the path '$inSpecResourceSchemaMofSourcePath' to the package path '$inSpecResourceFolder'..."
        $null = Copy-Item -Path $inSpecResourceSchemaMofSourcePath -Destination $inSpecResourceFolder

        foreach ($inSpecProfileSourcePath in $inSpecProfileSourcePaths)
        {
            Write-Verbose -Message "Copying the Chef Inspec profile from the path '$inSpecProfileSourcePath' to the package path '$modulesFolderPath'..."
            $null = Copy-Item -Path $inSpecProfileSourcePath -Destination $modulesFolderPath -Container -Recurse
        }
    }

    # Copy extra files
    foreach ($file in $FilesToInclude)
    {
        $filePath = Resolve-RelativePath -Path $file

        if (Test-Path -Path $filePath -PathType 'Leaf')
        {
            Write-Verbose -Message "Copying the custom file to include from the path '$filePath' to the package module path '$modulesFolderPath'..."
            $null = Copy-Item -Path $filePath -Destination $modulesFolderPath
        }
        else
        {
            Write-Verbose -Message "Copying the custom folder to include from the path '$filePath' to the package module path '$modulesFolderPath'..."
            $null = Copy-Item -Path $filePath -Destination $modulesFolderPath -Container -Recurse
        }
    }

    # Zip the package
    $compressArchiveSourcePath = Join-Path -Path $packageRootPath -ChildPath '*'
    Write-Verbose -Message "Compressing the generated package from the path '$compressArchiveSourcePath' to the package path '$packageDestinationPath'..."
    $null = Compress-Archive -Path $compressArchiveSourcePath -DestinationPath $packageDestinationPath -CompressionLevel 'Fastest'

    return [PSCustomObject]@{
        PSTypeName = 'GuestConfiguration.Package'
        Name = $Name
        Path = $packageDestinationPath
    }
}
#EndRegion './Public/New-GuestConfigurationPackage.ps1' 427
#Region './Public/New-GuestConfigurationPolicy.ps1' 0

<#
    .SYNOPSIS
        Creates a policy definition to monitor and remediate settings on machines through
        Azure Guest Configuration and Azure Policy.

    .PARAMETER DisplayName
        The display name of the policy to create.
        The display name has a maximum length of 128 characters.

    .PARAMETER Description
        The description of the policy to create.
        The display name has a maximum length of 512 characters.

    .PARAMETER PolicyId
        The unique GUID of the policy definition.
        If you are trying to update an existing policy definition, then this ID must match the 'name'
        field in the existing definition.

        You can run New-Guid to generate a new GUID.

    .PARAMETER PolicyVersion
        The version of the policy definition.
        If you are updating an existing policy definition, then this version should be greater than
        the value in the 'metadata.version' field in the existing definition.

        Note: This is NOT the version of the Guest Configuration package.
        You can validate the Guest Configuration package version via the ContentVersion parameter.

    .PARAMETER ContentUri
        The public HTTP or HTTPS URI of the Guest Configuration package (.zip) to run via the created policy.
        Example: https://github.com/azure/auditservice/release/AuditService.zip

    .PARAMETER ContentVersion
        If specified, the version of the Guest Configuration package (.zip) downloaded via the
        content URI must match this value.
        This is for validation only.

        Note: This is NOT the version of the policy definition.
        You can define the policy definition version via the PolicyVersion parameter.

    .PARAMETER Path
        The path to the folder under which to create the new policy definition file.
        The default value is the 'definitions' folder under your GuestConfiguration module path.

    .PARAMETER Platform
        The target platform (Windows or Linux) for the policy.
        The default value is Windows.

    .PARAMETER Parameter
        The parameters to expose on the policy.
        All parameters passed to the policy must be single string values.

        Example:
            $policyParameters = @(
                @{
                    Name = 'ServiceName' # Required
                    DisplayName = 'Windows Service Name' # Required
                    Description = 'Name of the windows service to be audited.' # Required
                    ResourceType = 'Service' # Required
                    ResourceId = 'windowsService' # Required
                    ResourcePropertyName = 'Name' # Required
                    DefaultValue = 'winrm' # Optional
                    AllowedValues = @('wscsvc', 'WSearch', 'wcncsvc', 'winrm') # Optional
                },
                @{
                    Name = 'ServiceState' # Required
                    DisplayName = 'Windows Service State' # Required
                    Description = 'State of the windows service to be audited.' # Required
                    ResourceType = 'Service' # Required
                    ResourceId = 'windowsService' # Required
                    ResourcePropertyName = 'State' # Required
                    DefaultValue = 'Running' # Optional
                    AllowedValues = @('Running', 'Disabled') # Optional
                }
            )

    .PARAMETER Mode
        Defines the mode under which this policy should run the package on the machine.

        Allowed modes:
            Audit: Monitors the machine only. Will not make modifications to the machine.
            ApplyAndMonitor: Modifies the machine once if it does not match the expected state.
              Then monitors the machine only until another remediation task is triggered via Azure Policy.
              Will make modifications to the machine.
            ApplyAndAutoCorrect: Modifies the machine any time it does not match the expected state.
              You will need trigger a remediation task via Azure Policy to start modifications the first time.
              Will make modifications to the machine.

        The default value is Audit.

        If the package has been created as Audit-only, you cannot create an Apply policy with that package.
        The package will need to be re-created in AuditAndSet mode.

    .PARAMETER Tag
        A hashtable of the tags that should be on machines to apply this policy on.
        If this is specified, the created policy will only be applied to machines with all the specified tags.

    .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.' `
            -PolicyVersion 1.1.0 `
            -Path ./git/custom_policy `
            -Tag @{ Owner = 'WebTeam' }

    .EXAMPLE
        $PolicyParameterInfo = @(
            @{
                Name = 'ServiceName' # Policy parameter name (mandatory)
                DisplayName = 'windows service name.' # Policy parameter display name (mandatory)
                Description = "Name of the windows service to be audited." # Policy parameter description (mandatory)
                ResourceType = "Service" # configuration resource type (mandatory)
                ResourceId = 'windowsService' # configuration resource property name (mandatory)
                ResourcePropertyName = "Name" # configuration resource property name (mandatory)
                DefaultValue = 'winrm' # Policy parameter default value (optional)
                AllowedValues = @('wscsvc','WSearch','wcncsvc','winrm') # Policy parameter allowed values (optional)
            }
        )

        New-GuestConfigurationPolicy -ContentUri 'https://github.com/azure/auditservice/release/AuditService.zip' `
            -DisplayName 'Monitor Windows Service Policy.' `
            -Description 'Policy to monitor service on Windows machine.' `
            -PolicyId $myPolicyGuid `
            -PolicyVersion 2.4.0 `
            -Path ./policyDefinitions `
            -Parameter $PolicyParameterInfo

    .OUTPUTS
        Returns the name and path of the Guest Configuration policy definition.
        This output can then be piped into New-AzPolicyDefinition.

        @{
            PSTypeName = 'GuestConfiguration.Policy'
            Name = $policyName
            Path = $Path
        }
#>

function New-GuestConfigurationPolicy
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $DisplayName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Description,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Guid]
        $PolicyId,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Version]
        $PolicyVersion,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Uri]
        $ContentUri,

        [Parameter()]
        [System.Version]
        $ContentVersion,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [Parameter()]
        [ValidateSet('Windows', 'Linux')]
        [System.String]
        $Platform = 'Windows',

        [Parameter()]
        [System.Collections.Hashtable[]]
        $Parameter,

        [Parameter()]
        [ValidateSet('Audit', 'ApplyAndAutoCorrect', 'ApplyAndMonitor')]
        [System.String]
        $Mode = 'Audit',

        [Parameter()]
        [System.Collections.Hashtable]
        $Tag
    )

    # Validate parameters
    if ($DisplayName.Length -gt 128)
    {
        throw "The specified display name is more than the limit of 128 characters. Please specify a shorter display name."
    }

    if ($Description.Length -gt 512)
    {
        throw "The specified description is more than the limit of 512 characters. Please specify a shorter description."
    }

    if ($null -eq $ContentUri.AbsoluteURI)
    {
        throw "The specified package URI is not an absolute URI. Please specify a valid HTTP or HTTPS URI with the ContentUri parameter."
    }

    if ($ContentUri.Scheme -notmatch '[http|https]')
    {
        throw "The specified package URI does not follow the HTTP or HTTPS scheme. Please specify a valid HTTP or HTTPS URI with the ContentUri parameter."
    }

    $requiredParameterProperties = @('Name', 'DisplayName', 'Description', 'ResourceType', 'ResourceId', 'ResourcePropertyName')

    foreach ($parameterInfo in $Parameter)
    {
        foreach ($requiredParameterProperty in $requiredParameterProperties)
        {
            if (-not ($parameterInfo.Keys -contains $requiredParameterProperty))
            {
                $requiredParameterPropertyString = $requiredParameterProperties -join ', '
                throw "One of the specified policy parameters is missing the mandatory property '$requiredParameterProperty'. The mandatory properties for parameters are: $requiredParameterPropertyString"
            }

            if ($parameterInfo[$requiredParameterProperty] -isnot [string])
            {
                $requiredParameterPropertyString = $requiredParameterProperties -join ', '
                throw "The property '$requiredParameterProperty' of one of the specified parameters is not a string. All parameter property values must be strings."
            }
        }
    }

    # Download package
    $tempPath = Reset-GCWorkerTempDirectory

    $packageFileDownloadName = 'temp.zip'
    $packageFileDownloadPath = Join-Path -Path $tempPath -ChildPath $packageFileDownloadName

    if (Test-Path -Path $packageFileDownloadPath)
    {
        $null = Remove-Item -Path $packageFileDownloadPath -Force
    }

    $null = Invoke-WebRequest -Uri $ContentUri -OutFile $packageFileDownloadPath

    if ($null -eq (Get-Command -Name 'Get-FileHash' -ErrorAction 'SilentlyContinue'))
    {
        $null = Import-Module -Name 'Microsoft.PowerShell.Utility'
    }
    $contentHash = (Get-FileHash -Path $packageFileDownloadPath -Algorithm 'SHA256').Hash

    # Extract package
    $packagePath = Join-Path -Path $tempPath -ChildPath 'extracted'
    $null = Expand-Archive -Path $packageFileDownloadPath -DestinationPath $packagePath -Force

    # Get configuration name
    $mofFilePattern = '*.mof'
    $mofChildItems = @( Get-ChildItem -Path $packagePath -Filter $mofFilePattern -File )

    if ($mofChildItems.Count -eq 0)
    {
        throw "No .mof file found in the extracted Guest Configuration package at '$packagePath'. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
    }
    elseif ($mofChildItems.Count -gt 1)
    {
        throw "Found more than one .mof file in the extracted Guest Configuration package at '$packagePath'. Please remove any extra .mof files from the root of the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
    }

    $mofFile = $mofChildItems[0]
    $packageName = $mofFile.BaseName

    # Get package version
    $packageVersion = '1.0.0'
    $metaconfigFileName = "{0}.metaconfig.json" -f $packageName
    $metaconfigFilePath =Join-Path -Path $packagePath -ChildPath $metaconfigFileName

    if (Test-Path -Path $metaconfigFilePath)
    {
        $metaconfig = Get-Content -Path $metaconfigFilePath -Raw | ConvertFrom-Json | ConvertTo-OrderedHashtable

        if ($metaconfig.Keys -contains 'Version')
        {
            $packageVersion = $metaconfig['Version']
            Write-Verbose -Message "Downloaded package has the version $packageVersion"

            if ($null -ne $ContentVersion -and $ContentVersion -ne $packageVersion)
            {
                throw "Downloaded package version ($packageVersion) does not match specfied content version ($ContentVersion)."
            }
        }
        else
        {
            if ($null -eq $ContentVersion)
            {
                Write-Warning -Message "Failed to determine the package version from the metaconfig file '$metaconfigFileName' in the downloaded package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
            }
            else
            {
                throw "Failed to determine the package version from the metaconfig file '$metaconfigFileName' in the downloaded package. Package version does not match specfied content version ($ContentVersion). Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
            }
        }

        if ($metaconfig.Keys -contains 'Type')
        {
            $packageType = $metaconfig['Type']
            Write-Verbose -Message "Downloaded package has the type $packageType"

            if ($packageType -eq 'Audit' -and $Mode -ne 'Audit')
            {
                throw 'The specified package has been marked as Audit-only. You cannot create an Apply policy with an Audit-only package. Please change the mode of the policy or the type of the package.'
            }
        }
        else
        {
            Write-Warning -Message "Failed to determine the package type from the metaconfig file '$metaconfigFileName' in the downloaded package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
        }
    }
    else
    {
        Write-Warning -Message "Failed to find the metaconfig file '$metaconfigFileName' in the downloaded package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
    }

    # Determine paths
    if ([String]::IsNullOrEmpty($Path))
    {
        $Path = Get-Location
    }

    $Path = Resolve-RelativePath -Path $Path

    if (-not (Test-Path -Path $Path))
    {
        $null = New-Item -Path $Path -ItemType 'Directory' -Force
    }

    # Determine if policy is AINE or DINE
    if ($Mode -eq 'Audit')
    {
        $fileName = '{0}_AuditIfNotExists.json' -f $packageName
    }
    else
    {
        $fileName = '{0}_DeployIfNotExists.json' -f $packageName
    }

    $filePath = Join-Path -Path $Path -ChildPath $fileName

    if (Test-Path -Path $filePath)
    {
        $null = Remove-Item -Path $filePath -Force
    }

    # Generate definition
    $policyDefinitionContentParameters = @{
        DisplayName = $DisplayName
        Description = $Description
        PolicyVersion = $PolicyVersion
        ConfigurationName = $packageName
        ConfigurationVersion = $packageVersion
        ContentUri = $ContentUri
        ContentHash = $contentHash
        Platform = $Platform
        AssignmentType = $Mode
        PolicyId = $PolicyId
        Parameter = $Parameter
        Tag = $Tag
    }
    $policyDefinitionContent = New-GuestConfigurationPolicyContent @policyDefinitionContentParameters

    # Convert definition hashtable to JSON
    $policyDefinitionContentJson = (ConvertTo-Json -InputObject $policyDefinitionContent -Depth 100).Replace('\u0027', "'")
    $formattedPolicyDefinitionContentJson = Format-PolicyDefinitionJson -Json $policyDefinitionContentJson

    # Write JSON to file
    $null = Set-Content -Path $filePath -Value $formattedPolicyDefinitionContentJson -Encoding 'UTF8' -Force

    # Return policy information
    $result = [PSCustomObject]@{
        PSTypeName = 'GuestConfiguration.Policy'
        Name = $packageName
        Path = $filePath
        PolicyId = $PolicyId
    }

    return $result
}
#EndRegion './Public/New-GuestConfigurationPolicy.ps1' 394
#Region './Public/Protect-GuestConfigurationPackage.ps1' 0

<#
    .SYNOPSIS
        Signs a Guest Configuration package using either a certificate on Windows
        or GPG keys on Linux.

    .PARAMETER Path
        The path of the Guest Configuration package to sign.

    .PARAMETER Certificate
        The 'Code Signing' certificate to sign the package with.
        This is only supported on Windows.

        This certificate will need to be installed on machines running this package.

        See examples for how to generate a test certificate.

    .PARAMETER PrivateGpgKeyPath
        The private GPG key path to sign the package with.
        This is only supported on Linux.

        See examples for how to generate this key.

    .PARAMETER PublicGpgKeyPath
        The public GPG key path to sign the package with.
        This is only supported on Linux.

        This key will need to be installed on any machines running this package at the path:
            /usr/local/share/ca-certificates/gc/pub_keyring.gpg

        See examples for how to generate this key.

    .EXAMPLE
        # Windows
        # Please note that self-signed certs should not be used in production, only testing

        # Create a code signing cert
        $myCert = New-SelfSignedCertificate -Type 'CodeSigningCert' -DnsName 'GCEncryptionCertificate' -HashAlgorithm 'SHA256'

        # Export the certificates
        $myPwd = ConvertTo-SecureString -String 'Password1234' -Force -AsPlainText
        $myCert | Export-PfxCertificate -FilePath 'C:\demo\GCPrivateKey.pfx' -Password $myPwd
        $myCert | Export-Certificate -FilePath 'C:\demo\GCPublicKey.cer' -Force

        # Import the certificate
        Import-PfxCertificate -FilePath 'C:\demo\GCPrivateKey.pfx' -Password $myPwd -CertStoreLocation 'Cert:\LocalMachine\My'

        # Sign the package
        $certToSignThePackage = Get-ChildItem -Path cert:\LocalMachine\My | Where-Object {($_.Subject-eq "CN=GCEncryptionCertificate") }
        Protect-GuestConfigurationPackage -Path C:\demo\AuditWindowsService.zip -Certificate $certToSignThePackage -Verbose

    .EXAMPLE
        # Linux
        # Generate gpg key
        gpg --gen-key

        # Export public key
        gpg --output public.gpg --export <email-id used to generate gpg key>
        # Export private key
        gpg --output private.gpg --export-secret-key <email-id used to generate gpg key>

        # Sign Linux policy package
        Protect-GuestConfigurationPackage -Path ./not_installed_application_linux.zip -PrivateGpgKeyPath ./private.gpg -PublicGpgKeyPath ./public.gpg -Verbose

    .OUTPUTS
        Returns the name of the configuration in the package and the path of the output signed Guest Configuration package.
        $result = [PSCustomObject]@{
            Name = $configurationName
            Path = $signedPackageFilePath
        }
#>

function Protect-GuestConfigurationPackage
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Certificate")]
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "GpgKeys")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path,

        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [ValidateNotNullOrEmpty()]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate,

        [Parameter(Mandatory = $true, ParameterSetName = "GpgKeys")]
        [ValidateNotNullOrEmpty()]
        [String]
        $PrivateGpgKeyPath,

        [Parameter(Mandatory = $true, ParameterSetName = "GpgKeys")]
        [ValidateNotNullOrEmpty()]
        [String]
        $PublicGpgKeyPath
    )

    $os = Get-OSPlatform

    if ($PSCmdlet.ParameterSetName -eq 'GpgKeys' -and $os -ine 'Linux')
    {
        throw 'GPG key signing is only supported on Linux.'
    }
    elseif ($PSCmdlet.ParameterSetName -eq 'Certificate' -and $os -ine 'Windows')
    {
        throw 'Certificate signing is only supported on Windows.'
    }

    $Path = Resolve-RelativePath -Path $Path
    if (-not (Test-Path $Path -PathType 'Leaf'))
    {
        throw "Could not find a file at the path '$Path'"
    }

    if ($PSCmdlet.ParameterSetName -eq 'GpgKeys')
    {
        $PrivateGpgKeyPath = Resolve-RelativePath -Path $PrivateGpgKeyPath
        if (-not (Test-Path -Path $PrivateGpgKeyPath))
        {
            throw "Could not find the private GPG key file at the path '$PrivateGpgKeyPath'"
        }

        $PublicGpgKeyPath = Resolve-RelativePath -Path $PublicGpgKeyPath
        if (-not (Test-Path -Path $PublicGpgKeyPath))
        {
            throw "Could not find the public GPG key file at the path '$PublicGpgKeyPath'"
        }
    }

    $package = Get-Item -Path $Path
    $packageDirectory = $package.Directory
    $packageFileName = [System.IO.Path]::GetFileNameWithoutExtension($Path)

    $signedPackageName = "$($packageFileName)_signed.zip"

    $signedPackageFilePath = Join-Path -Path $packageDirectory -ChildPath $signedPackageName
    if (Test-Path -Path $signedPackageFilePath)
    {
        $null = Remove-Item -Path $signedPackageFilePath -Force
    }

    $tempDirectory = Reset-GCWorkerTempDirectory

    try
    {
        # Unzip policy package.
        $null = Expand-Archive -Path $Path -DestinationPath $tempDirectory

        # Find and validate the mof file
        $mofFilePattern = '*.mof'
        $mofChildItems = @( Get-ChildItem -Path $tempDirectory -Filter $mofFilePattern -File )

        if ($mofChildItems.Count -eq 0)
        {
            throw "No .mof file found in the package. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
        }
        elseif ($mofChildItems.Count -gt 1)
        {
            throw "Found more than one .mof file in the extracted Guest Configuration package. Please remove any extra .mof files from the root of the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
        }

        $mofFile = $mofChildItems[0]
        $configurationName = $mofFile.BaseName

        if ($PSCmdlet.ParameterSetName -eq 'Certificate')
        {
            # Create catalog file
            $catalogFilePath = Join-Path -Path $tempDirectory -ChildPath "$configurationName.cat"

            if (Test-Path -Path $catalogFilePath)
            {
                $null = Remove-Item -Path $catalogFilePath -Force
            }

            Write-Verbose -Message "Creating the catalog file at '$catalogFilePath'"
            $null = New-FileCatalog -Path $tempDirectory -CatalogFilePath $catalogFilePath -CatalogVersion 2

            # Sign catalog file
            Write-Verbose -Message "Signing the catalog file at '$catalogFilePath'"
            $codeSignOutput = Set-AuthenticodeSignature -Certificate $Certificate -FilePath $catalogFilePath

            # Validate that file was signed
            $signature = Get-AuthenticodeSignature -FilePath $catalogFilePath
            if ($null -eq $signature.SignerCertificate)
            {
                throw $codeSignOutput.StatusMessage
            }
            elseif ($signature.SignerCertificate.Thumbprint -ne $Certificate.Thumbprint)
            {
                throw $codeSignOutput.StatusMessage
            }
        }
        else
        {
            $ascFilePath = Join-Path -Path $tempDirectory -ChildPath "$configurationName.asc"
            $hashFilePath = Join-Path -Path $tempDirectory -ChildPath "$configurationName.sha256sums"

            Write-Verbose -Message "Creating hash file at '$hashFilePath'"

            Push-Location -Path $tempDirectory
            try
            {
                bash -c "find ./ -type f -print0 | xargs -0 sha256sum | grep -v sha256sums > $hashFilePath"
            }
            finally
            {
                Pop-Location
            }

            Write-Verbose -Message "Signing hash file at '$hashFilePath'"
            gpg --import $PrivateGpgKeyPath
            gpg --no-default-keyring --keyring $PublicGpgKeyPath --output $ascFilePath --armor --detach-sign $hashFilePath
        }

        # Zip the signed Guest Configuration package
        Write-Verbose -Message "Creating the signed Guest Configuration package at '$signedPackageFilePath'"
        $archiveSourcePath = Join-Path -Path $tempDirectory -ChildPath '*'
        $null = Compress-Archive -Path $archiveSourcePath -DestinationPath $signedPackageFilePath

        $result = [PSCustomObject]@{
            Name = $configurationName
            Path = $signedPackageFilePath
        }
    }
    finally
    {
        $null = Reset-GCWorkerTempDirectory
    }

    return $result
}
#EndRegion './Public/Protect-GuestConfigurationPackage.ps1' 233
#Region './Public/Start-GuestConfigurationPackageRemediation.ps1' 0

<#
    .SYNOPSIS
        Applies the given Guest Configuration package file (.zip) to the current machine.
        This will modify your local machine.

    .PARAMETER Path
        The path to the Guest Configuration package file (.zip) to apply.

    .PARAMETER Parameter
        A list of hashtables describing the parameters to use when running the package.

        Basic Example:
        $Parameter = @(
            @{
                ResourceType = 'Service'
                ResourceId = 'windowsService'
                ResourcePropertyName = 'Name'
                ResourcePropertyValue = 'winrm'
            },
            @{
                ResourceType = 'Service'
                ResourceId = 'windowsService'
                ResourcePropertyName = 'Ensure'
                ResourcePropertyValue = 'Present'
            }
        )

        Technical Example:
        The Guest Configuration agent will replace parameter values in the compiled DSC configuration (.mof)
        file in the package before running it.
        If your compiled DSC configuration (.mof) file looked like this:

        instance of TestFile as $TestFile1ref
        {
            ModuleName = "TestFileModule";
            ModuleVersion = "1.0.0.0";
            ResourceID = "[TestFile]MyTestFile"; <--- This is both the resource type and ID
            Path = "test.txt"; <--- Here is the name of the parameter that I want to change the value of
            Content = "default";
            Ensure = "Present";
            SourceInfo = "TestFileSource";
            ConfigurationName = "TestFileConfig";
        };

        Then your parameter value would look like this:

        $Parameter = @(
            @{
                ResourceType = 'TestFile'
                ResourceId = 'MyTestFile'
                ResourcePropertyName = 'Path'
                ResourcePropertyValue = 'C:\myPath\newFile.txt'
            }
        )

    .EXAMPLE
        Start-GuestConfigurationPackage -Path ./custom_policy/WindowsTLS.zip

    .EXAMPLE
        $Parameter = @(
            @{
                ResourceType = 'MyFile'
                ResourceId = 'hi'
                ResourcePropertyName = 'Ensure'
                ResourcePropertyValue = 'Present'
            }
        )

        Start-GuestConfigurationPackage -Path ./custom_policy/AuditWindowsService.zip -Parameter $Parameter

    .OUTPUTS
        Returns a PSCustomObject with the report properties from running the package.
        Here is an example output:
            additionalProperties : {}
            assignmentName : TestFilePackage
            complianceStatus : True
            endTime : 5/9/2022 11:42:12 PM
            jobId : 18df23b4-cd22-4c26-b4b7-85b91873ec41
            operationtype : Consistency
            resources : {@{complianceStatus=True; properties=; reasons=System.Object[]}}
            startTime : 5/9/2022 11:42:10 PM
#>

function Start-GuestConfigurationPackageRemediation
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [Parameter()]
        [Hashtable[]]
        $Parameter = @()
    )

    $invokeParameters = @{
        Path = $Path
        Apply = $true
    }

    if ($null -ne $Parameter)
    {
        $invokeParameters['Parameter'] = $Parameter
    }

    $result = Invoke-GuestConfigurationPackage @invokeParameters

    return $result
}
#EndRegion './Public/Start-GuestConfigurationPackageRemediation.ps1' 113

# SIG # Begin signature block
# MIInkwYJKoZIhvcNAQcCoIInhDCCJ4ACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBgQEK4zBZKp03a
# vkBPDteg7/D0wKV7ymZODAldHEpdU6CCDXYwggX0MIID3KADAgECAhMzAAACURR2
# zMWFg24LAAAAAAJRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDBIpXR3b1IYAMunV9ZYBVYsaA7S64mqacKy/OJUf0Lr/LW/tWlJDzJH9nFAhs0
# zzSdQQcLhShOSTUxtlwZD9dnfIcx4pZgu0VHkqQw2dVc8Ob21GBo5sVrXgEAQxZo
# rlEuAl20KpSIFLUBwoZFGFSQNSMcqPudXOw+Mhvn6rXYv/pjXIjgBntn6p1f+0+C
# 2NXuFrIwjJIJd0erGefwMg//VqUTcRaj6SiCXSY6kjO1J9P8oaRQBHIOFEfLlXQ3
# a1ATlM7evCUvg3iBprpL+j1JMAUVv+87NRApprPyV75U/FKLlO2ioDbb69e3S725
# XQLW+/nJM4ihVQ0BHadh74/lAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUMLgM7NX5EnpPfK5uU6FPvn2g/Ekw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ2NzU5NjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIVJlff+Fp0ylEJhmvap
# NVv1bYLSWf58OqRRIDnXbHQ+FobsOwL83/ncPC3xl8ySR5uK/af4ZDy7DcDw0yEd
# mKbRLzHIfcztZVSrlsg0GKwZuaB2MEI1VizNCoZlN+HlFZa4DNm3J0LhTWrZjVR0
# M6V57cFW0GsV4NlqmtelT9JFEae7PomwgAV9xOScz8HzvbZeERcoSRp9eRsQwOw7
# 8XeCLeglqjUnz9gFM7RliCYP58Fgphtkht9LNEcErLOVW17m6/Dj75zg/IS+//6G
# FEK2oXnw5EIIWZraFHqSaee+NMgOw/R6bwB8qLv5ClOJEpGKA3XPJvS9YgOpF920
# Vu4Afqa5Rv5UJKrsxA7HOiuH4TwpkP3XQ801YLMp4LavXnvqNkX5lhFcITvb01GQ
# lcC5h+XfCv0L4hUum/QrFLavQXJ/vtirCnte5Bediqmjx3lswaTRbr/j+KX833A1
# l9NIJmdGFcVLXp1en3IWG/fjLIuP7BqPPaN7A1tzhWxL+xx9yw5vQiT1Yn14YGmw
# OzBYYLX0H9dKRLWMxMXGvo0PWEuXzYyrdDQExPf66Fq/EiRpZv2EYl2gbl9fxc3s
# qoIkyNlL1BCrvmzunkwt4cwvqWremUtqTJ2B53MbBHlf4RfvKz9NVuh5KHdr82AS
# MMjU4C8KNTqzgisqQdCy8unTMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGXMwghlvAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAJRFHbMxYWDbgsAAAAAAlEwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIITilNGxeLlo+WEWC6HG0j/6
# EgRr8P2aNPve8ju5gZV1MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAPmJHBgdD/SuK2LVaFmgPDfMbkteM7ouB9zd3aUO2D1DwmnqtcDqrQj7l
# LYHPcG/GVfIwBM9cRzTHt1Qu2Ofi4Tfssl0KPdsObopgrdwCi3UUTZC7HNEYrn1i
# LObsm+QBDV7qcna7Jct/7PXZUSOCdOLlbXoECc3bADMFjYBmgE7aY/WGCGAoXmBf
# /puIeuacbmDXctWSbKFls6vhBfOUAHojCbpe0EEXeptAPrlxnTlYz4XGDt+wKb3a
# 8gSmcxUcmqAuktShcFZAgX7jbtELsAazbsNbqBCZezU1DcZS4h9g9wGULukpfr9+
# Nb+BvyMXoVD8VNLr2nEPLrLTtCmaBKGCFv0wghb5BgorBgEEAYI3AwMBMYIW6TCC
# FuUGCSqGSIb3DQEHAqCCFtYwghbSAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFRBgsq
# hkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB6cHuPNsaxx3i62qpACuS8AkACX5RMOjjFTw4AjrZhdQIGYoJRlkZm
# GBMyMDIyMDYwNzIyMzQwMy41OTlaMASAAgH0oIHQpIHNMIHKMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3QkYxLUUz
# RUEtQjgwODElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCC
# EVQwggcMMIIE9KADAgECAhMzAAABnytFNRUILktdAAEAAAGfMA0GCSqGSIb3DQEB
# CwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIxMTIwMjE5MDUy
# MloXDTIzMDIyODE5MDUyMlowgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMx
# JjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjdCRjEtRTNFQS1CODA4MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEApPV5fE1RsomQL85vLQeHyz9M5y/PN2AyBtN47Nf1swmi
# Alw7NJF5Sd/TlGcHgRWv1fJdu5pQY8i2Q7U4N1vHUDkQ7p35+0s2RKBZpV2kmHEI
# cgzFeYIcYupYfMtzVdUzRxmC82qEJrQXrhUpRB/cKeqwv7ESuxj6zp9e1wBs6Pv8
# hcuw31oCEON19+brdtE0oVHmA67ORjlaR6e6LqkGEU6bvpQGgh36vLa/ixaiMo6Z
# L8cW9x3MelY7XtDTx+hpyAk/OD8VmCu3qGuQMW7E1KlkMolraxqMkMlz+MiCn01G
# D7bExQoteIriTa98kRo6OFNTh2VNshplngq3XHCYJG8upNjeQIUWLyh63jz4eaFh
# 2NNYPE3JMVeIeIpaKr2mmBodxwz1j8OCqCshMu0BxrmStagJIXloil9qhNHjUVrp
# pgij4XXBd3XFYlSPWil4bcuMzO+rbFI3HQrZxuVSCOnlOnc3C+mBadLzJeyTyN8v
# SK8fbARIlZkooDNkw2VOEVCGxSLQ+tAyWMzR9Kxrtg79/T/9DsKMd+z92X7weYwH
# oOgfkgUg9GsIvn+tSRa1XP1GfN1vubYCP9MXCxlhwTXRIm0hdTRX61dCjwin4vYg
# 9gZEIDGItNgmPBN7rPlMmAODRWHFiaY2nASgAXgwXZGNQT3xoM7JGioSBoXaMfUC
# AwEAAaOCATYwggEyMB0GA1UdDgQWBBRiNPLVHhMWK0gOLujf2WrH1h3IYTAfBgNV
# HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU
# aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
# CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz
# L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV
# HRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IC
# AQAdiigpPDvpGfPpvWz10CAJqPusWyg2ipDEd//NgPF1QDGvUaSLWCZHZLWvgumS
# FQbGRAESZCp1qWCYoshgVdz5j6CDD+cxW69AWCzKJQyWTLR9zIn1QQ/TZJ2DSoPh
# m1smgD7PFWelA9wkc46FWdj2x0R/lDpdmHP3JtSEdwZb45XDcMpKcwRlJ3QEXn7s
# 430UnwfnQc5pRWPnTBPPidzr73jK2iHM50q5a+OhlzKFhibdIQSTX+BuSWasl3vJ
# /M9skeaaC9rojEc6iF19a8AiF4XCzxYEijf7yca8R4hfQclYqLn+IwnA/DtpjveL
# aAkqixEbqHUnvXUO6qylQaJw6GFgMfltFxgF9qmqGZqhLp+0G/IZ8jclaydgtn2c
# AGNsol92TICxlK6Q0aCVnT/rXOUkuimdX8MoS/ygII4jT71AYruzxeCx8eU0RVOx
# 2V74KWQ5+cZLZF2YnQOEtujWbDEsoMdEdZ11d8m2NyXZTX0RE7ekiH0HQsUV+WFG
# yOTXb7lTIsuaAd25X4T4DScqNKnZpByhNqTeHPIsPUq2o51nDNG1BMaw5DanMGqt
# dQ88HNJQxl9eIJ4xkW4IZehy7A+48cdPm7syRymT8xnUyzBSqEnSXleKVP7d3T23
# VNtR0utBMdiKdk3Rn4LUvTzs1WkwOFLnLpJW42ZEIoX4NjCCB3EwggVZoAMCAQIC
# EzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoX
# DTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC
# 0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VG
# Iwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP
# 2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/P
# XfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361
# VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwB
# Sru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9
# X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269e
# wvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDw
# wvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr
# 9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+e
# FnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAj
# BgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+n
# FV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEw
# PwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9j
# cy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3
# FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAf
# BgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBH
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNS
# b29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl
# ckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4Swf
# ZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTC
# j/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu
# 2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/
# GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3D
# YXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbO
# xnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqO
# Cb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I
# 6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0
# zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaM
# mdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNT
# TY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLLMIICNAIBATCB+KGB0KSBzTCByjEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWlj
# cm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBF
# U046N0JGMS1FM0VBLUI4MDgxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAHRdrpgf8ssMRSxUwvKyfRb/XPa3oIGD
# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF
# BQACBQDmSc/uMCIYDzIwMjIwNjA3MjEyMzU4WhgPMjAyMjA2MDgyMTIzNThaMHQw
# OgYKKwYBBAGEWQoEATEsMCowCgIFAOZJz+4CAQAwBwIBAAICE9YwBwIBAAICEbUw
# CgIFAOZLIW4CAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgC
# AQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAqpKLbBQRelJ6n
# K8PrxjhGZKoF369Vd+Scxc+Ub+N5y3yNElHqOmJUuzmWTWGNCVOKp+dsrfuLT7pX
# qXHDU8VNTpm4PAXXhSwKcK8l37Zd9SDCahugF3c2D9Y0cbrqj4k1kOPcc9epPokZ
# 0DZ7YNUvpDEmK12MknqLnVzPTTl1dTGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABnytFNRUILktdAAEAAAGfMA0GCWCGSAFl
# AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN
# AQkEMSIEIKZS9dDU6c/b3z/fa7UFLscS7Z0uC80rMdQSBeMpe/keMIH6BgsqhkiG
# 9w0BCRACLzGB6jCB5zCB5DCBvQQghvFeKaIniZ6l51c8DVHo3ioCyXxB+z/o7Kcs
# N0t8XNowgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# AZ8rRTUVCC5LXQABAAABnzAiBCBFFQQ6uF27Gm41gtzfX0wKw1CZibQDWvFWL1vS
# S3g4TDANBgkqhkiG9w0BAQsFAASCAgAxHHyFGCyd4/aUa0WlV/YWf7i1IoLwosI1
# 1Uh/jAAlQ4c/JQS7PbzZC2gbw4YfYD0t1PHAD1yQobjDhrCHePlawwnVFiI/S2Ws
# JaRb3X5L+x21WnUebEftyx/89Vr7nFN9a1sEEfvCunjBGOsu/vwqdPoo+vAFpaw6
# CBg5OmG7Zp3VhRI7vRTbon8wHoZ1DOMhuC9/XgIW64jnc547lx/pCOvoG/U2lV2P
# jnAqFflveA17NglD34tvepADkthKDoDmTKC1yxNw9qdXprYpXnbCOoF5sbl29kzO
# EFkZDUe72kAIilYy5Q7nazZi9iYRm0kkWe1Tn3Y7MgVKEQBv2As54d5hrxvwEp/n
# EoXqRANK8xHo5Ye3nK7aYfhI2bBR6DCU5nPa7erIpnxs8YR4A3vUyhT+8BVkOkK4
# EqEYBCkVL7+zefu6Rbwvfg1S/9RqXSSfQSdVy9hX6LRCvD3rYJ6Pz/Cbw7dCq3/R
# ce/C+8FQQeW9axJtIubvOfkXuuVweFr2qLr8XT7pv2kerGKY7GsuZb8RTmfETHYS
# f/wVhoGPF0yj4uv5u7ShCFLqM+vCImwK+zxA6bx6mKK6CKHXSiZDUCSQWSyEnngP
# TAFhKXYRCoBYDHdc/tjmEASPwW45OXb3ufVC52OwYtDf5wMIdLwBh/KWiKYuJHSR
# F6hNbFdslw==
# SIG # End signature block