This article explores how Azure Policy can be used to automatically configure and enforce license types for Arc-enabled SQL Server instances, helping organizations stay compliant while reducing operational overhead.
Azure Arc enables customers to onboard SQL Server instances - hosted on Linux or Windows - into Azure, regardless of where they are hosted: on‑premises, in multicloud environments, or at the edge. Once onboarded, these resources can be managed through the Azure Portal using services like Azure Monitor, Azure Policy, and Microsoft Defender for Cloud.
An important part of this onboarding is configuring the license type on each Arc-enabled resource to match your licensing agreement with Microsoft. For SQL Server, the LicenseType property on the Arc extension determines how the instance is licensed: Paid (you have a SQL Server license with Software Assurance or a SQL Server subscription), PAYG (you are paying for SQL Server software on a pay-as-you-go basis), or LicenseOnly (you have a perpetual SQL Server license). Setting this correctly matters for two reasons:
- Unlocking additional benefits: customers with Paid or PAYG license type gain access to some Azure services at no extra cost - such as Azure Update Manager and Machine Configuration - as well as exclusive capabilities like Best Practices Assessment and Remote Support
- Enabling pay-as-you-go billing: customers who do not have Software Assurance can pay for SQL Server software only when they use it via their Azure subscription by setting the license type to PAYG
Configure the license types at scale using Azure Policy
Configuring the license type on each Arc-enabled SQL Server instance can be done manually in the Azure Portal, but for large scale operations, automation is essential.
One way to implement automation is via PowerShell, as explained here: Configure SQL Server - SQL Server enabled by Azure Arc | Microsoft Learn. But here we will focus on how this can be automated using Azure Policy. An existing article, written by Jeff Pigott, explains this process for Windows Server, which inspired extending the same approach to SQL Server.
How to deploy the policy?
Deployment has two steps:
- Create/update the Azure Policy definition and assignment
- Start a remediation task so existing Arc-enabled SQL Server extensions are brought into compliance
You can deploy Azure Policy in multiple ways. In this article, we use PowerShell. See also: Tutorial: Build policies to enforce compliance - Azure Policy | Microsoft Learn.
Source code: microsoft/sql-server-samples/.../arc-sql-license-type-compliance.
Personal repository: claestom/sql-arc-policy-license-config.
Definition and assignment creation
Download the required files:
# Optional: create and enter a local working directory
mkdir sql-arc-lt-compliance
cd sql-arc-lt-compliance
$baseUrl = "https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/manage/azure-arc-enabled-sql-server/compliance/arc-sql-license-type-compliance"
New-Item -ItemType Directory -Path policy, scripts -Force | Out-Null
curl -sLo policy/azurepolicy.json "$baseUrl/policy/azurepolicy.json"
curl -sLo scripts/deployment.ps1 "$baseUrl/scripts/deployment.ps1"
curl -sLo scripts/start-remediation.ps1 "$baseUrl/scripts/start-remediation.ps1"
Note: On Windows PowerShell 5.1, curl is an alias for Invoke-WebRequest. Use curl.exe instead, or run the commands in PowerShell 7+.
Authenticate to Azure:
Connect-AzAccount
Set your variables. Only TargetLicenseType is required - all others are optional:
# Required
$TargetLicenseType = "PAYG" # "Paid" or "PAYG"
# Optional (uncomment to override defaults)
# $ManagementGroupId = "<management-group-id>" # Default: tenant root management group
# $SubscriptionId = "<subscription-id>" # Default: policy assigned at management group scope
# $ExtensionType = "Both" # "Windows", "Linux", or "Both" (default)
# $LicenseTypesToOverwrite = @("Unspecified","Paid","PAYG","LicenseOnly") # Default: all
Run the deployment script:
# Minimal: uses defaults for management group, platform, and overwrite targets
.\scripts\deployment.ps1 -TargetLicenseType $TargetLicenseType
# With subscription scope
.\scripts\deployment.ps1 -TargetLicenseType $TargetLicenseType -SubscriptionId $SubscriptionId
# With all options
.\scripts\deployment.ps1 `
-ManagementGroupId $ManagementGroupId `
-SubscriptionId $SubscriptionId `
-ExtensionType $ExtensionType `
-TargetLicenseType $TargetLicenseType `
-LicenseTypesToOverwrite $LicenseTypesToOverwrite
Parameter notes:
- ManagementGroupId (optional): management group where the policy definition is created. Defaults to the tenant root management group when not specified
- ExtensionType (optional, default
Both):Windows,Linux, orBoth. WhenBoth, a single policy definition and assignment covers both platforms - SubscriptionId (optional): if provided, assignment scope is subscription (otherwise management group scope)
- TargetLicenseType (required):
PaidorPAYG - LicenseTypesToOverwrite (optional, default all): controls which current states are eligible for update
Unspecified= no current LicenseTypePaid,PAYG,LicenseOnly= explicit current values
The script also creates a system-assigned managed identity on the policy assignment and assigns required roles automatically. Role assignments include retry logic (5 attempts, 10-second delay) to handle managed identity replication delays, which helps prevent common PolicyAuthorizationFailed errors.
Remediation task creation
After deployment, allow a few minutes for Azure Policy to run a compliance scan for the selected scope.
You can monitor this in Azure Policy → Compliance.
More info: Get policy compliance data - Azure Policy | Microsoft Learn.
Set your variables. TargetLicenseType is required and must match the value used during deployment:
# Required
$TargetLicenseType = "PAYG" # Must match the deployment target
# Optional (uncomment to override defaults)
# $ManagementGroupId = "<management-group-id>" # Default: tenant root management group
# $SubscriptionId = "<subscription-id>" # Default: remediation runs at management group scope
# $ExtensionType = "Both" # Must match the platform used for deployment
Then start remediation:
# Minimal: uses defaults for management group and platform
.\scripts\start-remediation.ps1 -TargetLicenseType $TargetLicenseType -GrantMissingPermissions
# With subscription scope
.\scripts\start-remediation.ps1 -TargetLicenseType $TargetLicenseType -SubscriptionId $SubscriptionId -GrantMissingPermissions
# With all options
.\scripts\start-remediation.ps1 `
-ManagementGroupId $ManagementGroupId `
-ExtensionType $ExtensionType `
-SubscriptionId $SubscriptionId `
-TargetLicenseType $TargetLicenseType `
-GrantMissingPermissions
Parameter notes:
- ManagementGroupId (optional): defaults to tenant root management group
- ExtensionType (optional, default
Both): must match the platform used for the assignment - SubscriptionId (optional): run remediation at subscription scope
- TargetLicenseType (required): must match the assignment target
- GrantMissingPermissions (optional switch): checks and assigns missing required roles before remediation starts
You can track remediation progress in Azure Policy → Remediation → Remediation tasks. It can take a few minutes to complete, depending on scope and resource count.
Recurring Billing Consent (PAYG)
When TargetLicenseType is set to PAYG, the policy automatically includes ConsentToRecurringPAYG in the extension settings with Consented: true and a UTC timestamp. For details of this requirement see: Move SQL Server license agreement to pay-as-you-go subscription - SQL Server enabled by Azure Arc | Microsoft Learn.
The policy also checks for ConsentToRecurringPAYG in its compliance evaluation - resources with LicenseType: PAYG but missing the consent property are flagged as non-compliant and remediated. This applies both when transitioning to PAYG and for existing PAYG extensions that predate the consent requirement (backward compatibility).
Note: Once ConsentToRecurringPAYG is set on an extension, it cannot be removed - this is enforced by the Azure resource provider. When transitioning away from PAYG, the policy changes LicenseType but leaves the consent property in place.
RBAC
When .\scripts\deployment.ps1 creates the policy assignment, it uses -IdentityType SystemAssigned. Azure then creates a managed identity for that assignment.
The assignment identity needs these roles at assignment scope (or inherited scope):
- Azure Extension for SQL Server Deployment: allows updating Arc SQL extension settings, including LicenseType
- Reader: allows reading resource and extension state for policy evaluation
- Resource Policy Contributor: allows policy-driven template deployments required by DeployIfNotExists
This identity is used whenever DeployIfNotExists applies changes, both during regular compliance evaluation and during remediation runs.
By default, the deployment script assigns these roles automatically with built-in retry logic to handle managed identity replication delays, which helps prevent common PolicyAuthorizationFailed errors.
Brownfield and Greenfield Scenarios
This policy is useful in both brownfield and greenfield Azure Arc environments.
Brownfield: existing Arc SQL inventory
In a brownfield environment, you already have Arc-enabled SQL Server resources in inventory and the current LicenseType values might be mixed, incorrect, or missing. This is where Azure Policy is especially useful, because it gives you a controlled way to remediate the current estate at scale.
Depending on how you configure targetLicenseType and licenseTypesToOverwrite, you can use the policy to:
- standardize all in-scope resources on a single value
- set LicenseType only when it is missing
- migrate a specific subset, such as Paid to PAYG
- preserve selected states while correcting only the resources that need attention
Examples:
Standardize everything to Paid
- targetLicenseType: Paid
- licenseTypesToOverwrite: ['Unspecified','Paid','PAYG','LicenseOnly']
Result: every in-scope Arc SQL extension is converged to LicenseType == Paid.
Backfill only missing values
- targetLicenseType: Paid
- licenseTypesToOverwrite: ['Unspecified']
Result: only resources without a configured LicenseType are updated; existing Paid, PAYG, and LicenseOnly values remain unchanged.
Migrate only Paid to PAYG
- targetLicenseType: PAYG
- licenseTypesToOverwrite: ['Paid']
Result: only resources currently set to Paid are updated to PAYG; missing, PAYG, and LicenseOnly remain unchanged. When transitioning to PAYG, the policy also automatically sets ConsentToRecurringPAYG with Consented: true and a UTC timestamp, as required for recurring pay-as-you-go billing.
Protect existing PAYG, fix only missing or LicenseOnly
- targetLicenseType: Paid
- licenseTypesToOverwrite: ['Unspecified','LicenseOnly']
Result: resources with no LicenseType or with LicenseOnly are updated to Paid, while existing PAYG stays untouched.
Greenfield: newly onboarded SQL Servers
In a greenfield scenario, the main value of Azure Policy is ongoing enforcement. Once new SQL Servers are onboarded to Azure Arc and fall within the assignment scope, the policy can act as a governance control to keep LicenseType aligned with your business model.
This means Azure Policy is not only a remediation mechanism for existing inventory, but also a way to continuously enforce the intended license configuration for future Arc-enabled SQL Server resources.
Azure Policy vs tagging
By default, Microsoft manages automatic deployment of SQL Server extension for Azure. It include an option to enforce the LicenseType setting via tags. See Manage Automatic Connection - SQL Server enabled by Azure Arc | Microsoft Learn for details. This way all newly onboarded SQL Server instance are set to the desired LicenceType from day one. The deployment of the Azure Policy is still important to ensure that the changes of the extension properties or ad-hoc additions of the SQL Server instances stay compliant to our business model.
A practical way to think about it:
- Tagging ensures the initial compliance of the newly connected Arc-enabled SQL servers
- Azure Policy enforces ongoing compliance of the existing Arc-enabled SQL servers
Tools
Interested in gaining better visibility into LicenseType configurations across your estate? Below you'll find an insightful KQL query and an accompanying workbook to help track compliance.
KQL Query
resources
| where type == "microsoft.hybridcompute/machines"
| where properties.detectedProperties.mssqldiscovered == "true"
| extend machineIdHasSQLServerDiscovered = id
| project name, machineIdHasSQLServerDiscovered, resourceGroup, subscriptionId
| join kind= leftouter (
resources
| where type == "microsoft.hybridcompute/machines/extensions" | where properties.type in ("WindowsAgent.SqlServer","LinuxAgent.SqlServer")
| extend machineIdHasSQLServerExtensionInstalled = iff(id contains "/extensions/WindowsAgent.SqlServer" or id contains "/extensions/LinuxAgent.SqlServer", substring(id, 0, indexof(id, "/extensions/")), "")
| project License_Type = properties.settings.LicenseType,
machineIdHasSQLServerExtensionInstalled)on $left.machineIdHasSQLServerDiscovered == $right.machineIdHasSQLServerExtensionInstalled
| where isnotempty(machineIdHasSQLServerExtensionInstalled)
| project-away machineIdHasSQLServerDiscovered, machineIdHasSQLServerExtensionInstalled
Source: Configure SQL Server - SQL Server enabled by Azure Arc | Microsoft Learn.
Azure Workbook
Resources
- Configure SQL Server - SQL Server enabled by Azure Arc | Microsoft Learn
- Azure Policy documentation | Microsoft Learn
- Automating Windows Server Licensing Benefits with Azure Arc Policy | Microsoft Community Hub
- Recurring billing consent - SQL Server enabled by Azure Arc | Microsoft Learn
- claestom/azure-arc-sa-workbook: Azure Workbook for monitoring Software Assurance compliance across Arc-enabled servers and SQL Server instances
- microsoft/sql-server-samples/.../arc-sql-license-type-compliance
- claestom/sql-arc-policy-license-config
Thank you!