Deploy through Azure Machine Configuration

Use Azure Machine Configuration when you want Azure Policy to evaluate a ConfigForge baseline on Azure virtual machines or Azure Arc-enabled servers.

The deployment path is:

ConfigForge baseline → MOF → resolved MOF → Machine Configuration ZIP → Azure Storage → policy JSON → Azure Policy definition → policy assignment

ConfigForge exports the compiled MOF. You do not need to author a separate DSC configuration.

ConfigForge is an experimental community tool. Test every package and policy on a disposable machine before assigning it broadly.

Supported behavior

Package or policy modePurposeCurrent guidance
AuditReport compliance without changing the machineRecommended default
AuditAndSet package + ApplyAndMonitor policyApply once during remediation, then monitorUse only with a Microsoft.OSConfig version whose Set() path you validated
AuditAndSet package + ApplyAndAutoCorrect policyReapply whenever drift is detectedUse only with a validated Microsoft.OSConfig remediation path

Microsoft.OSConfig 1.3.11 audits ConfigForge packages successfully, but its PowerShell DSC resource has an upstream Set() serialization defect. The examples below therefore use Audit.

Prerequisites

Use PowerShell 7:

  • Windows: PowerShell 7.1.3 or later.
  • Linux: PowerShell 7.2.4 or later.
  • Local Machine Configuration package testing is not supported on macOS. Export the MOF on macOS, then move it to a supported packaging machine.

Install the packaging and Azure modules:

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

Install-Module GuestConfiguration -Scope CurrentUser -Force
Install-Module Microsoft.OSConfig -Scope CurrentUser -Force

Install-Module Az.Accounts -Scope CurrentUser -Force
Install-Module Az.Storage -Scope CurrentUser -Force
Install-Module Az.Resources -Scope CurrentUser -Force
Install-Module Az.PolicyInsights -Scope CurrentUser -Force

Confirm which Microsoft.OSConfig version packaging will use:

Get-Module -ListAvailable Microsoft.OSConfig |
  Sort-Object Version -Descending |
  Select-Object Name, Version, ModuleBase

Target machines need:

  • Azure VM Guest Configuration extension 1.26.24 or later, or Azure Arc agent 1.10.0 or later.
  • A system-assigned managed identity when required by the selected policy.
  • The built-in initiative Deploy prerequisites to enable machine configuration policies on virtual machines assigned at or above the target scope.

1. Export the MOF from ConfigForge

  1. Open the baseline in Baseline Detail.
  2. Select Export.
  3. Select MOF (.mof).
  4. Save the file in a versioned working folder.

Example:

$WorkingDirectory = 'C:\ConfigForge\MachineConfiguration'
$MofPath = Join-Path $WorkingDirectory 'ContosoSecurityBaseline.mof'
$PackageName = 'ContosoSecurityBaseline_1_0_0'

New-Item -ItemType Directory -Path $WorkingDirectory -Force | Out-Null
Test-Path $MofPath

Use a unique package name for every baseline release. Reusing package names or content URIs can leave Azure evaluating cached content.

2. Resolve the Microsoft.OSConfig module version

ConfigForge exports:

ModuleName = "Microsoft.OSConfig";
ModuleVersion = "0.0.0";

0.0.0 is a portable placeholder so the same exported MOF can move between customer environments. Machine Configuration runtime requires the exact module version bundled into the ZIP, so resolve the placeholder on a packaging copy before running New-GuestConfigurationPackage.

$InstalledOSConfig = Get-Module -ListAvailable Microsoft.OSConfig |
  Sort-Object Version -Descending |
  Select-Object -First 1

if (-not $InstalledOSConfig) {
  throw 'Microsoft.OSConfig is not installed.'
}

$ResolvedMofPath = Join-Path $WorkingDirectory `
  'ContosoSecurityBaseline.resolved.mof'

(Get-Content $MofPath -Raw).Replace(
  'ModuleVersion = "0.0.0";',
  "ModuleVersion = `"$($InstalledOSConfig.Version)`";"
) | Set-Content $ResolvedMofPath -Encoding utf8

Confirm that every OSConfig instance was resolved:

Select-String -Path $ResolvedMofPath -Pattern 'ModuleVersion'

Do not replace ModuleName = "Microsoft.OSConfig".

3. Create the package

Create an Audit package:

Import-Module GuestConfiguration

$Package = New-GuestConfigurationPackage `
  -Name $PackageName `
  -Configuration $ResolvedMofPath `
  -Type Audit `
  -Path $WorkingDirectory `
  -Force

$PackagePath = $Package.Path
Get-Item $PackagePath

The ZIP contains:

  • The resolved MOF.
  • Guest Configuration runtime metadata.
  • The installed Microsoft.OSConfig module.

Keep the uncompressed package below 100 MB.

4. Test the package locally

Run local tests from an elevated PowerShell 7 session on Windows or through sudo pwsh on Linux:

Get-GuestConfigurationPackageComplianceStatus -Path $PackagePath

The first run installs the local Machine Configuration test agent. The result should include one resource result for every Test wrapper in the exported baseline.

For example, the ConfigForge Windows Server 2025 Workgroup Member baseline produces 296 resource results.

If you use a newer Microsoft.OSConfig version with a validated remediation path, recreate the package as AuditAndSet, then test remediation only on a disposable machine:

Start-GuestConfigurationPackageRemediation `
  -Path $PackagePath `
  -Verbose

Get-GuestConfigurationPackageComplianceStatus -Path $PackagePath

5. Optional: sign the package

Machine Configuration always validates the package SHA-256 hash. Package signing is optional unless your organization requires certificate or GPG validation.

Run Protect-GuestConfigurationPackage before upload and before policy generation.

Windows example:

$Certificate = Get-ChildItem Cert:\LocalMachine\My |
  Where-Object {
    $_.EnhancedKeyUsageList.FriendlyName -contains 'Code Signing'
  } |
  Select-Object -First 1

Protect-GuestConfigurationPackage `
  -Path $PackagePath `
  -Certificate $Certificate `
  -Verbose

Signed-package enforcement also requires distributing the public certificate and configuring target-machine certificate validation.

6. Upload the ZIP to Azure Storage

Publish-GuestConfigurationPackage is not part of GuestConfiguration 4.11.0. Upload the ZIP with Set-AzStorageBlobContent.

The following example uses a private container and a read-only SAS URI:

$SubscriptionId = '<subscription-id>'
$StorageResourceGroup = '<storage-resource-group>'
$StorageAccountName = '<storage-account-name>'
$ContainerName = 'machine-configuration'

Connect-AzAccount
Set-AzContext -SubscriptionId $SubscriptionId

$StorageKey = (
  Get-AzStorageAccountKey `
    -ResourceGroupName $StorageResourceGroup `
    -Name $StorageAccountName |
  Select-Object -First 1
).Value

$StorageContext = New-AzStorageContext `
  -StorageAccountName $StorageAccountName `
  -StorageAccountKey $StorageKey

if (-not (Get-AzStorageContainer `
    -Name $ContainerName `
    -Context $StorageContext `
    -ErrorAction SilentlyContinue)) {
  New-AzStorageContainer `
    -Name $ContainerName `
    -Context $StorageContext | Out-Null
}

$BlobName = Split-Path $PackagePath -Leaf

Set-AzStorageBlobContent `
  -Container $ContainerName `
  -File $PackagePath `
  -Blob $BlobName `
  -Context $StorageContext `
  -Force | Out-Null

$StartTime = (Get-Date).AddMinutes(-5)
$ExpiryTime = $StartTime.AddYears(1)

$ContentUri = New-AzStorageBlobSASToken `
  -StartTime $StartTime `
  -ExpiryTime $ExpiryTime `
  -Container $ContainerName `
  -Blob $BlobName `
  -Permission r `
  -Context $StorageContext `
  -FullUri

Treat a SAS URI as a secret. Do not commit it to source control.

For private access without SAS:

  • Azure VMs can use a system-assigned or user-assigned identity with Storage Blob Data Reader.
  • Azure Arc package downloads do not support user-assigned identity in this scenario. Use SAS or system-assigned identity.

7. Generate the Machine Configuration policy

Generate an Audit policy:

$PolicyOutputPath = Join-Path $WorkingDirectory 'policy'
New-Item -ItemType Directory -Path $PolicyOutputPath -Force | Out-Null

$PolicyConfig = @{
  PolicyId      = (New-Guid).Guid
  ContentUri    = $ContentUri
  DisplayName   = 'Contoso security baseline'
  Description   = 'Audits the ConfigForge security baseline.'
  Path          = $PolicyOutputPath
  Platform      = 'Windows'
  PolicyVersion = [version]'1.0.0'
  Mode          = 'Audit'
}

$PolicyResult = New-GuestConfigurationPolicy @PolicyConfig
$PolicyFile = $PolicyResult.Path
Get-Item $PolicyFile

New-GuestConfigurationPolicy -Path expects an output folder, not the package path. Use the returned Path instead of constructing the policy filename.

Audit mode produces an _AuditIfNotExists.json file. Apply modes produce a _DeployIfNotExists.json file.

Do not modify the ZIP after this step. The policy contains the package hash. If the package changes, upload the new ZIP and regenerate the policy.

8. Publish the Azure Policy definition

Creating a policy definition typically requires Resource Policy Contributor or equivalent permissions.

$PolicyDefinition = New-AzPolicyDefinition `
  -Name 'configforge-contoso-security-baseline-1-0-0' `
  -DisplayName 'Contoso security baseline' `
  -Description 'Machine Configuration policy generated from ConfigForge.' `
  -Policy $PolicyFile

The definition appears under Azure Policy > Definitions.

9. Assign the policy

Assign the prerequisite initiative first:

Deploy prerequisites to enable machine configuration policies on virtual machines

Then assign the custom Audit policy:

$TargetResourceGroupName = '<target-resource-group>'
$Scope = (Get-AzResourceGroup -Name $TargetResourceGroupName).ResourceId

$Assignment = New-AzPolicyAssignment `
  -Name 'configforge-contoso-audit' `
  -DisplayName 'ConfigForge - Contoso baseline audit' `
  -Scope $Scope `
  -PolicyDefinition $PolicyDefinition

For DeployIfNotExists policies, the assignment needs a managed identity, location, and the roles declared in the generated policy's roleDefinitionIds. The Azure portal's Create a remediation task flow is the simplest way to create the identity and required role assignments.

10. Verify the deployment

  1. Open Azure Policy > Compliance.
  2. Locate the custom assignment.
  3. Confirm that the prerequisite initiative is compliant.
  4. Confirm that Azure created a guest configuration assignment on the target VM or Arc-enabled server.
  5. Wait for package download, extraction, and the first consistency scan.
  6. Open the guest assignment report and confirm that the resource count matches the exported baseline.

Machine Configuration evaluation is asynchronous. Policy state can initially show NonCompliant while the guest assignment is still Pending.

Troubleshooting

SymptomResolution
Publish-GuestConfigurationPackage is not recognizedUpload with Set-AzStorageBlobContent.
Microsoft.OSConfig cannot be foundInstall it in the PowerShell 7 module path and recreate the ZIP.
More than one Microsoft.OSConfig version is installedThe resolution step selects the newest version. Remove older copies if you require a different deterministic version.
Runtime says module version 0.0.0 does not existPackage the resolved MOF, not the portable exported MOF.
Package audits but remediation does not change values on Microsoft.OSConfig 1.3.11Use Audit or package with a newer version whose Set() path you validated.
Local compliance fails with access errorsRun PowerShell 7 elevated or through sudo.
Package download returns HTTP 403Renew the SAS URI or correct Storage Blob Data Reader access.
The extension rejects the package hashUpload a new ZIP and regenerate the policy. Do not alter the existing ZIP.
Remediation does not startCheck the assignment identity, resource-group deployment permissions, role assignments, and remediation task.
Package exceeds its size limitReduce package content; keep the uncompressed package below 100 MB.

Microsoft Learn references