GuestConfiguration.psm1

#Region './prefix.ps1' 0
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'

Import-Module $PSScriptRoot/Modules/GuestConfigPath -Force
Import-LocalizedData -BaseDirectory $PSScriptRoot -FileName GuestConfiguration.psd1 -BindingVariable GuestConfigurationManifest

if ($IsLinux -and (
    $PSVersionTable.PSVersion.Major -lt 7 -or
    ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -lt 2)
    ))
{
    throw 'The Linux agent requires at least PowerShell v7.2.preview.6 to support the DSC subsystem.'
}

$currentCulture = [System.Globalization.CultureInfo]::CurrentCulture
if (($currentCulture.Name -eq 'en-US-POSIX') -and ($(Get-OSPlatform) -eq 'Linux'))
{
    Write-Warning "'$($currentCulture.Name)' Culture is not supported, changing it to 'en-US'"
    # Set Culture info to en-US
    [System.Globalization.CultureInfo]::CurrentUICulture = [System.Globalization.CultureInfo]::new('en-US')
    [System.Globalization.CultureInfo]::CurrentCulture = [System.Globalization.CultureInfo]::new('en-US')
}

#inject version info to GuestConfigPath.psm1
InitReleaseVersionInfo $GuestConfigurationManifest.moduleVersion
#EndRegion './prefix.ps1' 26
#Region './Enum/AssignmentType.ps1' 0
enum AssignmentType
{
    ApplyAndAutoCorrect
    ApplyAndMonitor
    Audit
}
#EndRegion './Enum/AssignmentType.ps1' 7
#Region './Enum/PackageType.ps1' 0
enum PackageType
{
    Audit
    AuditAndSet
}
#EndRegion './Enum/PackageType.ps1' 6
#Region './Private/ConvertTo-OrderedHashtable.ps1' 0
function ConvertTo-OrderedHashtable
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $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-ChefInSpecMofContent.ps1' 0

function Edit-ChefInSpecMofContent
{
    [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-ChefInSpecMofContent.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'

    if ($IsWindows)
    {
        $gcWorkerExeName = 'gc_worker.exe'
    }
    else
    {
        $gcWorkerExeName = 'gc_worker'
    }

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

    return $gcWorkerExePath
}
#EndRegion './Private/Get-GCWorkerExePath.ps1' 23
#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-GuestConfigurationPackageFromUri.ps1' 0
function Get-GuestConfigurationPackageFromUri
{
    [CmdletBinding()]
    [OutputType([System.Io.FileInfo])]
    param
    (
        [Parameter()]
        [Uri]
        [ValidateScript({([Uri]$_).Scheme -match '^http'})]
        [Alias('Url')]
        $Uri
    )

    # Abstracting this in another function as we may want to support Proxy later.
    $tempFileName = [io.path]::GetTempFileName()
    $null = [System.Net.WebClient]::new().DownloadFile($Uri, $tempFileName)

    # The zip can be PackageName_0.2.3.zip, so we really need to look at the MOF to find its name.
    $packageName = Get-GuestConfigurationPackageNameFromZip -Path $tempFileName

    Move-Item -Path $tempFileName -Destination ('{0}.zip' -f $packageName) -Force -PassThru
}
#EndRegion './Private/Get-GuestConfigurationPackageFromUri.ps1' 23
#Region './Private/Get-GuestConfigurationPackageMetaConfig.ps1' 0
function Get-GuestConfigurationPackageMetaConfig
{
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path
    )

    $packageName = Get-GuestConfigurationPackageName -Path $Path
    $metadataFileName = '{0}.metaconfig.json' -f $packageName
    $metadataFile = Join-Path -Path $Path -ChildPath $metadataFileName

    if (Test-Path -Path $metadataFile)
    {
        Write-Debug -Message "Loading metadata from meta config file '$metadataFile'."
        $metadata = Get-Content -raw -Path $metadataFile | ConvertFrom-Json -AsHashtable -ErrorAction Stop
    }
    else
    {
        $metadata = @{}
    }

    #region Extra meta file until Agent supports one unique metadata file
    $extraMetadataFileName = 'extra.{0}' -f $metadataFileName
    $extraMetadataFile = Join-Path -Path $Path -ChildPath $extraMetadataFileName

    if (Test-Path -Path $extraMetadataFile)
    {
        Write-Debug -Message "Loading extra metadata from extra meta file '$extraMetadataFile'."
        $extraMetadata = Get-Content -raw -Path $extraMetadataFile | ConvertFrom-Json -AsHashtable -ErrorAction Stop

        foreach ($extraKey in $extraMetadata.keys)
        {
            if (-not $metadata.ContainsKey($extraKey))
            {
                $metadata[$extraKey] = $extraMetadata[$extraKey]
            }
            else
            {
                Write-Verbose -Message "The metadata '$extraKey' is already defined in '$metadataFile'."
            }
        }
    }
    #endregion

    return $metadata
}
#EndRegion './Private/Get-GuestConfigurationPackageMetaConfig.ps1' 51
#Region './Private/Get-GuestConfigurationPackageMetadataFromZip.ps1' 0
function Get-GuestConfigurationPackageMetadataFromZip
{
    [CmdletBinding()]
    [OutputType([PSObject])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Io.FileInfo]
        $Path
    )

    $Path = [System.IO.Path]::GetFullPath($Path) # Get Absolute path as .Net methods don't like relative paths.

    try
    {
        $tempFolderPackage = Join-Path -Path ([io.path]::GetTempPath()) -ChildPath ([guid]::NewGuid().Guid)
        Expand-Archive -LiteralPath $Path -DestinationPath $tempFolderPackage -Force
        Get-GuestConfigurationPackageMetaConfig -Path $tempFolderPackage
    }
    finally
    {
        # Remove the temporarily extracted package
        Remove-Item -Force -Recurse $tempFolderPackage -ErrorAction SilentlyContinue
    }
}
#EndRegion './Private/Get-GuestConfigurationPackageMetadataFromZip.ps1' 26
#Region './Private/Get-GuestConfigurationPackageName.ps1' 0
function Get-GuestConfigurationPackageName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Io.FileInfo]
        $Path
    )

    $Path = [System.IO.Path]::GetFullPath($Path) # Get Absolute path as .Net method don't like relative paths.
    # Make sure we only get the MOF which is at the root of the package
    $mofFile = @() + (Get-ChildItem -Path (Join-Path -Path $Path -ChildPath *.mof) -File -ErrorAction Stop)

    if ($mofFile.Count -ne 1)
    {
        throw "Invalid GuestConfiguration Package at '$Path'. Found $($mofFile.Count) mof files."
        return
    }
    else
    {
        Write-Debug -Message "Found the MOF '$($moffile)' in $Path."
    }

    return ([System.Io.Path]::GetFileNameWithoutExtension($mofFile[0]))
}
#EndRegion './Private/Get-GuestConfigurationPackageName.ps1' 28
#Region './Private/Get-GuestConfigurationPackageNameFromZip.ps1' 0
function Get-GuestConfigurationPackageNameFromZip
{
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Mandatory = $true)]
        [System.Io.FileInfo]
        $Path
    )

    $Path = [System.IO.Path]::GetFullPath($Path) # Get Absolute path as .Net method don't like relative paths.

    try
    {
        $zipRead = [IO.Compression.ZipFile]::OpenRead($Path)
        # Make sure we only get the MOF which is at the root of the package
        $mofFile = @() + $zipRead.Entries.FullName.Where({((Split-Path -Leaf -Path $_) -eq $_) -and $_ -match '\.mof$'})
    }
    finally
    {
        # Close the zip so we can move it.
        $zipRead.Dispose()
    }

    if ($mofFile.count -ne 1)
    {
        throw "Invalid policy package, failed to find unique dsc document in policy package downloaded from '$Uri'."
    }

    return ([System.Io.Path]::GetFileNameWithoutExtension($mofFile[0]))
}
#EndRegion './Private/Get-GuestConfigurationPackageNameFromZip.ps1' 32
#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 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
            }

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

            $moduleDependencies += Get-ModuleDependencies @getModuleDependenciesParameters
        }
    }

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

    $MofFilePath = [System.IO.Path]::GetFullPath($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-ResouceDependenciesFromMof.ps1' 42
#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'

        if ($IsWindows)
        {
            $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'" -Verbose
        $null = New-Item -Path $logsFolderPath -ItemType 'Directory'
    }
}
#EndRegion './Private/Install-GCWorker.ps1' 115
#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
    )

    if ($IsMacOS)
    {
        throw 'The Invoke-GuestConfigurationPackage cmdlet is not supported on MacOS'
    }

    $Path = [System.IO.Path]::GetFullPath($Path)

    #-----VALIDATE PACKAGE SETUP-----

    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

    # 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-ResouceDependenciesFromMof -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-MetaconfigProperty -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' 215
#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]
        $Version,

        [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 = $Version
        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.ContainsKey($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-Path -Path $PackagePath
    $packageName = Split-Path -Path $PackagePath -LeafBase

    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/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-Path -Path $PackagePath
    $packageName = Split-Path -Path $PackagePath -LeafBase

    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-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 (-not $parameterInfo.ContainsKey('ResourceType'))
        {
            throw "Policy parameter is missing a mandatory property 'ResourceType'. Please make sure that configuration resource type is specified in configuration parameter."
        }

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

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

        if (-not $parameterInfo.ContainsKey('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/Set-MetaconfigProperty.ps1' 0
function Set-MetaconfigProperty
{
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $MetaconfigPath,

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

    $metaconfigContent = Get-Content -Path $MetaconfigPath -Raw
    $metaconfig = $metaconfigContent | ConvertFrom-Json -AsHashtable

    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-MetaconfigProperty.ps1' 29
#Region './Private/Update-GuestConfigurationPackageMetaconfig.ps1' 0
function Update-GuestConfigurationPackageMetaconfig
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $MetaConfigPath,

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

        [Parameter(Mandatory = $true)]
        [System.String]
        $Value
    )

    $metadataFile = $MetaConfigPath

    #region Write extra metadata on different file until the GC Agents supports it
    if ($Key -notin @('debugMode','ConfigurationModeFrequencyMins','configurationMode'))
    {
        $fileName = Split-Path -Path $MetadataFile -Leaf
        $filePath = Split-Path -Path $MetadataFile -Parent
        $metadataFileName = 'extra.{0}' -f $fileName

        $metadataFile = Join-Path -Path $filePath -ChildPath $metadataFileName
    }
    #endregion

    Write-Debug -Message "Updating the file '$metadataFile' with key $Key = '$Value'."

    if (Test-Path -Path $metadataFile)
    {
        $metaConfigObject = Get-Content -Raw -Path $metadataFile | ConvertFrom-Json -AsHashtable
        $metaConfigObject[$Key] = $Value
        $metaConfigObject | ConvertTo-Json | Out-File -Path $metadataFile -Encoding ascii -Force
    }
    else
    {
        @{
            $Key = $Value
        } | ConvertTo-Json | Out-File -Path $metadataFile -Encoding ascii -Force
    }
}
#EndRegion './Private/Update-GuestConfigurationPackageMetaconfig.ps1' 47
#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 compliance details.
#>


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

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

    if ($IsMacOS)
    {
        throw 'The Test-GuestConfigurationPackage cmdlet is not supported on MacOS'
    }

    $invokeParameters = @{
        Path = $Path
    }

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

    $result = Invoke-GuestConfigurationPackage @invokeParameters

    return $result
}
#EndRegion './Public/Get-GuestConfigurationPackageComplianceStatus.ps1' 107
#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.
        This is a tag for you to keep track of your pacakges; it is not currently used by Guest Configuration or Azure Policy.

    .PARAMETER Type
        Sets a tag in the metaconfig data of the package specifying whether or not this package can support Set functionality or not.
        This tag is currently used only for verfication by this module and does not affect the functionality of the package.

        Audit indicates that the package will not set the state of the machine and may only monitor settings.
        AuditAndSet indicates that the package may be used for setting the state of the machine.

        By default this tag is set to Audit.

    .PARAMETER Path
        The path to a folder to output the package under.
        By default the package will be created under the current working directory (Get-Item -Path $(Get-Location)).

    .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 to a file or folder to include under the Modules path within the package.

    .PARAMETER Force
        If present, this function will overwrite any existing package files.

    .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 = '0.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()]
        [System.IO.FileInfo]
        $FilesToInclude,

        [Parameter()]
        [Switch]
        $Force
    )

    Write-Verbose -Message 'Starting New-GuestConfigurationPackage'

    $currentLocation = Get-Location

    $Configuration = [System.IO.Path]::GetFullPath($Configuration, $currentLocation)
    $Path = [System.IO.Path]::GetFullPath($Path, $currentLocation)

    if (-not [String]::IsNullOrEmpty($ChefInspecProfilePath))
    {
        $ChefInspecProfilePath = [System.IO.Path]::GetFullPath($ChefInspecProfilePath, $currentLocation)
    }

    if (-not [String]::IsNullOrEmpty($FilesToInclude))
    {
        $FilesToInclude = [System.IO.Path]::GetFullPath($FilesToInclude, $currentLocation)
    }

    #-----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-ResouceDependenciesFromMof -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
    if (-not [string]::IsNullOrEmpty($FilesToInclude))
    {
        if (-not (Test-Path -Path $FilesToInclude))
        {
            throw "The item to include from the path '$FilesToInclude' 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 "An item already exists at the package path '$packageRootPath'. Please remove it or use the Force parameter."
        }
    }

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

    if (Test-Path -Path $packageDestinationPath)
    {
        if (-not $Force)
        {
            throw "An item already exists at the package destination path '$packageDestinationPath'. Please remove it or use the Force parameter."
        }
    }

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

    # Clear the root package folder
    if (Test-Path -Path $packageRootPath)
    {
        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-ChefInSpecMofContent -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
    if (-not [string]::IsNullOrEmpty($FilesToInclude))
    {
        if (Test-Path $FilesToInclude -PathType 'Leaf')
        {
            Write-Verbose -Message "Copying the custom file to include from the path '$FilesToInclude' to the package path '$modulesFolderPath'..."
            $null = Copy-Item -Path $FilesToInclude -Destination $modulesFolderPath
        }
        else
        {
            Write-Verbose -Message "Copying the custom folder to include from the path '$FilesToInclude' to the package path '$modulesFolderPath'..."
            $null = Copy-Item -Path $FilesToInclude -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' 407
#Region './Public/New-GuestConfigurationPolicy.ps1' 0

<#
    .SYNOPSIS
        Creates a policy definition to run code 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 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 PolicyId
        The unique ID 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 defintiion.
        This field is normally a GUID.
        The default value is a new GUID.

    .PARAMETER Version
        The version of the policy definition.
        If you are trying to update an existing policy definition, then this version must be greater than the value in the 'metadata.version' field in the existing defintiion.
        Note: This is NOT the version of the Guest Configuration package.
        The default value is '1.0.0'.

    .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 strings.

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

    .PARAMETER Mode
        Defines the modification mode under which this policy should run code from the package to modify 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.

    .PARAMETER Tag
        The tags that should be on machines to apply this policy on.

    .EXAMPLE
        New-GuestConfigurationPolicy `
            -ContentUri https://github.com/azure/auditservice/release/AuditService.zip `
            -DisplayName 'Monitor Windows Service Policy.' `
            -Description 'Policy to monitor service on Windows machine.' `
            -Version 1.0.0.0
            -Path ./git/custom_policy
            -Tag @{Owner = 'WebTeam'}

        $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 (optional)
                ResourceType = "Service" # dsc configuration resource type (mandatory)
                ResourceId = 'windowsService' # dsc configuration resource property name (mandatory)
                ResourcePropertyName = "Name" # dsc 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.' `
            -Version 1.0.0.0
            -Path ./policyDefinitions `
            -Parameter $PolicyParameterInfo

    .OUTPUTS
        Return name and path of the Guest Configuration policy definitions.
        @{
            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, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Uri]
        $ContentUri,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Guid]
        $PolicyId = [System.Guid]::NewGuid(),

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Version]
        $Version = '1.0.0',

        [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.ContainsKey($requiredParameterProperty)))
            {
                $requiredParameterPropertyString = $requiredParameterProperties -join ', '
                throw "One of the specified policy parameters is missing the mandatory property '$requiredParameterProperty'. The mandatory properties for parameters are: $requiredParameterPropertyString"
            }
        }
    }

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

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

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

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

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

    $contentHash = (Get-FileHash -Path $packageFileDownloadPath -Algorithm 'SHA256').Hash

    # Extract package
    $packageFolderName = 'temp'
    $packagePath = Join-Path -Path $gcWorkerPackagesFolderPath -ChildPath $packageFolderName

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

    $null = Expand-Archive -Path $packageFileDownloadPath -DestinationPath $packagePath -Force

    # Validate package?

    # 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 -AsHashtable

        if ($metaconfig.ContainsKey('Version'))
        {
            $packageVersion = $metaconfig['Version']
        }
    }

    # Determine paths
    if ([String]::IsNullOrEmpty($Path))
    {
        $Path = Join-Path -Path $gcWorkerPath -ChildPath 'definitions'
    }

    $currentLocation = Get-Location
    $Path = [System.IO.Path]::GetFullPath($Path, $currentLocation)

    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
        Version = $Version
        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
    $formattedPolicyDefinitionContentJson = Format-PolicyDefinitionJson -Json $policyDefinitionContentJson

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

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

    return $result

    # Check if the package is signed (nothing is using this right now)
    # $packageIsSigned = (($null -ne (Get-ChildItem -Path $unzippedPkgPath -Filter *.cat)) -or
    # (($null -ne (Get-ChildItem -Path $unzippedPkgPath -Filter *.asc)) -and ($null -ne (Get-ChildItem -Path $unzippedPkgPath -Filter *.sha256sums))))
}
#EndRegion './Public/New-GuestConfigurationPolicy.ps1' 331
#Region './Public/Protect-GuestConfigurationPackage.ps1' 0

<#
    .SYNOPSIS
        Signs a Guest Configuration policy package using certificate on Windows and Gpg keys on Linux.

    .Parameter Path
        Full path of the Guest Configuration package.

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

    .Parameter PrivateGpgKeyPath
        Private Gpg key path. This is only supported on Linux.

    .Parameter PublicGpgKeyPath
        Public Gpg key path. This is only supported on Linux.

    .Example
        $Cert = Get-ChildItem -Path Cert:/CurrentUser/AuthRoot -Recurse | Where-Object {($_.Thumbprint -eq "0563b8630d62d75abbc8ab1e4bdfb5a899b65d43") }
        Protect-GuestConfigurationPackage -Path ./custom_policy/WindowsTLS.zip -Certificate $Cert

    .OUTPUTS
        Return name and path of the signed Guest Configuration Policy package.
#>


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
    )

    $Path = Resolve-Path $Path
    if (-not (Test-Path $Path -PathType Leaf))
    {
        throw 'Invalid Guest Configuration package path.'
    }

    try
    {
        $packageFileName = [System.IO.Path]::GetFileNameWithoutExtension($Path)
        $signedPackageFilePath = Join-Path (Get-ChildItem $Path).Directory "$($packageFileName)_signed.zip"
        $tempDir = Join-Path -Path (Get-ChildItem $Path).Directory -ChildPath 'temp'
        Remove-Item $signedPackageFilePath -Force -ErrorAction SilentlyContinue
        $null = New-Item -ItemType Directory -Force -Path $tempDir

        # Unzip policy package.
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::ExtractToDirectory($Path, $tempDir)

        # Get policy name
        $dscDocument = Get-ChildItem -Path $tempDir -Filter *.mof
        if (-not $dscDocument)
        {
            throw "Invalid policy package, failed to find dsc document in policy package."
        }

        $policyName = [System.IO.Path]::GetFileNameWithoutExtension($dscDocument)

        $osPlatform = Get-OSPlatform
        if ($PSCmdlet.ParameterSetName -eq "Certificate")
        {
            if ($osPlatform -eq "Linux")
            {
                throw 'Certificate signing not supported on Linux.'
            }

            # Create catalog file
            $catalogFilePath = Join-Path -Path $tempDir -ChildPath "$policyName.cat"
            Remove-Item $catalogFilePath -Force -ErrorAction SilentlyContinue
            Write-Verbose "Creating catalog file : $catalogFilePath."
            New-FileCatalog -Path $tempDir -CatalogVersion 2.0 -CatalogFilePath $catalogFilePath | Out-Null

            # Sign catalog file
            Write-Verbose "Signing catalog file : $catalogFilePath."
            $CodeSignOutput = Set-AuthenticodeSignature -Certificate $Certificate -FilePath $catalogFilePath

            $Signature = Get-AuthenticodeSignature $catalogFilePath
            if ($null -ne $Signature.SignerCertificate)
            {
                if ($Signature.SignerCertificate.Thumbprint -ne $Certificate.Thumbprint)
                {
                    throw $CodeSignOutput.StatusMessage
                }
            }
            else
            {
                throw $CodeSignOutput.StatusMessage
            }
        }
        else
        {
            if ($osPlatform -eq "Windows")
            {
                throw 'Gpg signing not supported on Windows.'
            }

            $PrivateGpgKeyPath = Resolve-Path $PrivateGpgKeyPath
            $PublicGpgKeyPath = Resolve-Path $PublicGpgKeyPath
            $ascFilePath = Join-Path $tempDir "$policyName.asc"
            $hashFilePath = Join-Path $tempDir "$policyName.sha256sums"

            Remove-Item $ascFilePath -Force -ErrorAction SilentlyContinue
            Remove-Item $hashFilePath -Force -ErrorAction SilentlyContinue

            Write-Verbose "Creating file hash : $hashFilePath."
            Push-Location -Path $tempDir
            bash -c "find ./ -type f -print0 | xargs -0 sha256sum | grep -v sha256sums > $hashFilePath"
            Pop-Location

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

        # Zip the signed Guest Configuration package
        Write-Verbose "Creating signed Guest Configuration package : '$signedPackageFilePath'."
        [System.IO.Compression.ZipFile]::CreateFromDirectory($tempDir, $signedPackageFilePath)

        $result = [pscustomobject]@{
            Name = $policyName
            Path = $signedPackageFilePath
        }

        return $result
    }
    finally
    {
        Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}
#EndRegion './Public/Protect-GuestConfigurationPackage.ps1' 151
#Region './Public/Publish-GuestConfigurationPackage.ps1' 0

<#
    .SYNOPSIS
        Publish a Guest Configuration policy package to Azure blob storage.
        The goal is to simplify the number of steps by scoping to a specific
        task.

        Generates a SAS token with a 3-year lifespan, to mitigate the risk
        of a malicious person discovering the published content.

        Requires a resource group, storage account, and container
        to be pre-staged. For details on how to pre-stage these things see the
        documentation for the Az Storage cmdlets.
        https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-powershell.

    .Parameter Path
        Location of the .zip file containing the Guest Configuration artifacts

    .Parameter ResourceGroupName
        The Azure resource group for the storage account

    .Parameter StorageAccountName
        The name of the storage account for where the package will be published
        Storage account names must be globally unique

    .Parameter StorageContainerName
        Name of the storage container in Azure Storage account (default: "guestconfiguration")

    .Example
        Publish-GuestConfigurationPackage -Path ./package.zip -ResourceGroupName 'resourcegroup' -StorageAccountName 'sa12345'

    .OUTPUTS
        Return a publicly accessible URI containing a SAS token with a 3-year expiration.
#>


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

        [Parameter(Position = 1, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ResourceGroupName,

        [Parameter(Position = 2, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $StorageAccountName,

        [Parameter()]
        [System.String]
        $StorageContainerName = 'guestconfiguration',

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Force
    )

    # Get Storage Context
    $Context = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName |
        ForEach-Object { $_.Context }

    # Blob name from file name
    $BlobName = (Get-Item -Path $Path -ErrorAction Stop).Name

    $setAzStorageBlobContentParams = @{
        Context   = $Context
        Container = $StorageContainerName
        Blob      = $BlobName
        File      = $Path
    }

    if ($true -eq $Force)
    {
        $setAzStorageBlobContentParams.Add('Force', $true)
    }

    # Upload file
    $null = Set-AzStorageBlobContent @setAzStorageBlobContentParams

    # Get url with SAS token
    # THREE YEAR EXPIRATION
    $StartTime = Get-Date

    # Add permissions on just the one file and not the whole blob
    # Put expiration time as a parameter
    $newAzStorageBlobSASTokenParams = @{
        Context    = $Context
        Container  = $StorageContainerName
        Blob       = $BlobName
        StartTime  = $StartTime
        ExpiryTime = $StartTime.AddYears('3')
        Permission = 'rl'
        FullUri    = $true
    }

    $SAS = New-AzStorageBlobSASToken @newAzStorageBlobSASTokenParams

    # Output
    return [PSCustomObject]@{
        ContentUri = $SAS
    }
}
#EndRegion './Public/Publish-GuestConfigurationPackage.ps1' 109
#Region './Public/Publish-GuestConfigurationPolicy.ps1' 0

<#
    .SYNOPSIS
        Publishes the Guest Configuration policy in Azure Policy Center.

    .Parameter Path
        Guest Configuration policy path.

    .Example
        Publish-GuestConfigurationPolicy -Path ./git/custom_policy
#>


function Publish-GuestConfigurationPolicy
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [Parameter()]
        [System.String]
        $ManagementGroupName
    )

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

    # Publish policy
    $jsonDefinition = Get-Content -Path $Path | ConvertFrom-Json | ForEach-Object { $_ }
    $definitionContent = $jsonDefinition.Properties

    $newAzureRmPolicyDefinitionParameters = @{
        Name        = $jsonDefinition.name
        DisplayName = $($definitionContent.DisplayName | ConvertTo-Json -Depth 20).replace('"', '')
        Description = $($definitionContent.Description | ConvertTo-Json -Depth 20).replace('"', '')
        Policy      = $($definitionContent.policyRule | ConvertTo-Json -Depth 20)
        Metadata    = $($definitionContent.Metadata | ConvertTo-Json -Depth 20)
        ApiVersion  = '2018-05-01'
        Verbose     = $true
    }

    if ($definitionContent.PSObject.Properties.Name -contains 'parameters')
    {
        $newAzureRmPolicyDefinitionParameters['Parameter'] = ConvertTo-Json -InputObject $definitionContent.parameters -Depth 15
    }

    if ($ManagementGroupName)
    {
        $newAzureRmPolicyDefinitionParameters['ManagementGroupName'] = $ManagementGroupName
    }

    Write-Verbose -Message "Publishing '$($jsonDefinition.properties.displayName)' ..."
    New-AzPolicyDefinition @newAzureRmPolicyDefinitionParameters
}
#EndRegion './Public/Publish-GuestConfigurationPolicy.ps1' 57
#Region './Public/Start-GuestConfigurationPackageRemediation.ps1' 0

<#
    .SYNOPSIS
        Applies the given Guest Configuration package file (.zip) to the current 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
        None.
#>


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

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

    if ($IsMacOS)
    {
        throw 'The Start-GuestConfigurationPackageRemediation cmdlet is not supported on MacOS'
    }

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

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

    $result = Invoke-GuestConfigurationPackage @invokeParameters

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

<#
    .SYNOPSIS
        Tests whether or not the given Guest Configuration package is compliant on the current machine.

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

    .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 (in the square brackets) 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
        Test-GuestConfigurationPackage -Path ./custom_policy/WindowsTLS.zip

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

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

    .OUTPUTS
        Returns a PSCustomObject with the compliance details.
#>


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

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

    if ($IsMacOS)
    {
        throw 'The Test-GuestConfigurationPackage cmdlet is not supported on MacOS'
    }

    $invokeParameters = @{
        Path = $Path
    }

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

    $result = Invoke-GuestConfigurationPackage @invokeParameters

    return $result
}
#EndRegion './Public/Test-GuestConfigurationPackage.ps1' 107

# SIG # Begin signature block
# MIInrAYJKoZIhvcNAQcCoIInnTCCJ5kCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCS/brYLrz/Wwg4
# Ue/8QDXTS55qltqdDt4c0/DaiRFS96CCDXYwggX0MIID3KADAgECAhMzAAACURR2
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGYwwghmIAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAJRFHbMxYWDbgsAAAAAAlEwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIAXPCMLnt5trEdRF/IryabFt
# COz2AS6s0y+/yvWJ+GXfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAL03px01uZBcqtSxKKly5gpKqdn06fXNPY/ysXg0v9i0tzmowaQBIZiQr
# ee2UN458iglr0qF2ODPuEhp9FlqiOA+p6UWjN9P4ix/1TFPyn6o5Gx4ClIjT4fOj
# lHpnRMJIb7ZhCoVU/ixx8i+jT1AmpEw+lptFX5TGJQR64vv/onRi68QrrzNImJt9
# as+/PAb2aabdOP5g60ijPExjkt4s6f4dJErOG95Ia+XVPCmVBqHCFckgPb3toKkl
# j8Ya7oCNMm5xskNJH2tTpLGnAeA8FNTjOhwBILhEE/ZnGz+LrYiGk9slGPu2tspZ
# aAD+XzToVb5yRAeDUkCCFRfO+5kfz6GCFxYwghcSBgorBgEEAYI3AwMBMYIXAjCC
# Fv4GCSqGSIb3DQEHAqCCFu8wghbrAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAGQaHbUEO2tEAkmGVn/MCChYyw7p7alWTIGKvaIOc3twIGYhe4Dl1g
# GBMyMDIyMDQxOTE2MzkzMC40OTdaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRZTCCBxQwggT8oAMCAQICEzMAAAGHQhZmBFzz9I0AAQAAAYcwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjEx
# MDI4MTkyNzM5WhcNMjMwMTI2MTkyNzM5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5peBljPQOj0REIh4GQADyu
# p/k4epvi2MCmC2kBZWV206q6HYbJ4ux1SEZKgRbhvKINSma8tNsRJpm3afPTuNMQ
# iEH1vBSlu6Dv5WGLVAEFtpKdLjLmBCdV5Genh43DLqhCcAuO1GpGcdwHpGpD/hnx
# rgwzvZGRkEXvCKYoieEJJ+pmw4W0aYHFDnbA85jc+pFxu4HyH4HLOZM7iiD+pzeL
# bIz4chefFVNGXyND0gvp+Tl9IbFPIXlnt6VIg6+YKj9emupCIxSR5IDT2IDIrxk0
# CnIzcTXF558pk/yVbjbO2b5NT+Sln/BtVxvzLWWef47qrINEKFJkGY1YdF0ZLu2K
# TKZLVu13xQN8vmCThtaVc4swdz8yV1AvDGvAFMsLVLlC1o2E+cu/xMMB1pp9UU9D
# 4WKfZJqX0uhyGHxAW6GFL1C1th5xjX8euX+FJHO5IDUkG3gB+sozuXYOjFMD386P
# 6wXTWmXycWe0bHEwck7ZcW5X1E+OsjxwlwAol/wYWhxGOHnRYKf3CG+1xxSW7uJj
# QwD80ZQJCI9UhrjvRuLG5IzPV6TTFoJY9nQvUCzf9IHgRV6DSPBfh3x8OCaPqeok
# j/IZ+N5pJFikhjok+ISyc6HBKxZ7SuYvT3m10qtgbzIM1XPgQbwjd+Q3w9JqJESs
# WptrUDNOtEjkct0u7nHrAgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUIlHAFrpBLWGB
# ft1hc5mQPw2ljSAwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN
# BgkqhkiG9w0BAQsFAAOCAgEAuat+U9kSowBUTf93GI5zxc7T2uynqiSiZyoL4lI/
# SDHwvPxUNqMelMMneNE0u8yr7bjJddRRZX1pBJ3Dd5aooYkgKiAzznVb69aCWODR
# UATh8y6XrXqR5zINFC9lFx874yoNuFTiM29HbXT+vfkCMu0g6XFYfSiNBFyhtm6J
# aQcqakbbRkw3dCUJDrTHYkqWg6Th8IAjDVxPMSLuPIYrk9O+aCKpxyYWt+/Pw6bo
# CNA2Y2HGR9XBvOCdOrnKucoK6HfbM69fTmwmSXSS6cryVgsvD0QwJEYzyYJUpn59
# lEdCr56Mgjs47MwMDQqfySZ5EadtMHiLc5ve4jYPhWRdSfRNenliAdbAq+1Y/UA+
# irfs/NaznC0X8YZTtv7CtPVMPg4kJD27ujmd07A7y7K/MhvVtgPMl3IIDaneF6S7
# XRJH+djiDdBoES0mNSzQweCCmPmY9+CMDGw27NrAooOS4tiC1hDFf+oMi9+ye9TW
# 1u1c6O33BqzZ3vTqsYBQtll+XXoMcid0sdmysOpW39OCml50wTks9EAvkXybs/Jz
# qSH9vPlpjD4nF9pe4LPzKwuWp5siv523ipGjsfgF4cGC8xEzYPtSHMoI3cJ26lim
# ny3QjSn4bG/4DOUhW4A+QExF9WwMcFBVcSr0L9HSEQJuWg4xdw2aYL8t5kqI0qwT
# f/MwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB
# CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD
# VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe
# Fw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGm
# TOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/H
# ZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc
# wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62A
# W36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1w
# jjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCG
# MFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ
# 1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP
# 8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFz
# ymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHz
# NgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3
# xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsG
# AQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/
# LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG
# DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYB
# BQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G
# A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw
# VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j
# cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF
# BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br
# aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQEL
# BQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfC
# cTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AF
# vonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l
# 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn
# 8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5m
# O0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyx
# TkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4
# S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9
# y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM
# +Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhw
# RNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC1DCCAj0C
# AQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
# ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjA4NDItNEJFNi1DMjlBMSUwIwYD
# VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoD
# FQB4d5NUBCgGRIbCxmBh7X/+gxuD86CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA5gi8EzAiGA8yMDIyMDQxOTEy
# NDE1NVoYDzIwMjIwNDIwMTI0MTU1WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDm
# CLwTAgEAMAcCAQACAgOZMAcCAQACAhFGMAoCBQDmCg2TAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQEFBQADgYEAZU88u5tC+BAzP6Z+235Nf14hb78MxPIN/6J4eY5dwqty
# MfElTpdGULr/IbX+UCfMck1IaZFO1jhq65/3pA1ZdtHlxJ7hDIkvq9mxG72wsGW5
# hZYr/Bd6Ro0ygH5kDvuzuN15gCCei0mb7tbdU+twrjDgK95POaBE/4sfbh2800Ux
# ggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# AYdCFmYEXPP0jQABAAABhzANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCATckYYSqYNGaf++3Z6wZh6
# 92THIB+qiMMZHeqOdyh6mjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMQs
# 8KATbqAfdsLAtksWYc+B1RsrwmZ8GDbnM6RsmptYMIGYMIGApH4wfDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGHQhZmBFzz9I0AAQAAAYcwIgQg+0Yr
# JcZSQGp45oC8f3zXCKnc/xbBm8vIkP6E8wpdItUwDQYJKoZIhvcNAQELBQAEggIA
# UQZ02/dRyjWP+/3RTI8fm1jPkmqERdpA8MK71nFuiU8NoZJjnzNQ184BR+KGX7Tx
# X2CoYzoy42zCRyEBiqUXLIugBd/6yiQco3uoAZZrZSQxsg1M8O4RxToYPLgyA6rq
# TcOubhxJAmCwgZjTFOPws+EAcnOXhatq1mt61J0ndj7wFAfDKP1KEgEdqA2BDQZj
# 4DLHXor1iP200HWknj884DcGNoXAFsLHw9zYa1Knn8ps6mz+tJ4WUPCmYGj+AsOp
# cJ/ryJLczjO+lKs4UhGzwVtFPgY48em18orG4ZxFAZLQPbBoBPPrFs7mPVfLNI2Z
# 9O3Mm6lEBP3wqHbbZKaEaMavX4BxW0js1nMSOEb4bIys7Bi6MlGVI5jbb6qOMfcG
# 8Wpp9NujfXXwqWPHeW4qjgJicR95VtMq0c5DHbXk/TDTnR2hQZcs1iPXlvIKCS0T
# vv/VoadHlhjFeZaCR/W+lweGPdQRmPEEZY+EfdywgDHGDQUnlnUUIx05s169+5fQ
# T0XiKpXlb3yJF6512vkuIOyaxneuTDv/ZTBGkN7Kjcrotq6nE0aBEdH1QSTcI2GX
# IuKmwEwZBRcrf8zTiZKUGu7xKw7bjLNlrXkFj6d7FzgZMHbebq41ruMDIZApTvt4
# od+BUMA9gV5Vg48t3+NoPhVx1HtByVJwCwSSiu8tL0A=
# SIG # End signature block