|
| 1 | +# Azure Data Factory SSIS Integration Runtime License Type Configuration with Azure Policy |
| 2 | + |
| 3 | +This solution deploys and remediates a custom Azure Policy that audits the `licenseType` property on Azure Data Factory Managed **SSIS Integration Runtimes** (`Microsoft.DataFactory/factories/integrationRuntimes`) and provides a companion script to bring them to a selected target value. |
| 4 | + |
| 5 | +## What Is In This Folder |
| 6 | + |
| 7 | +- `policy/azurepolicy.json`: Custom policy definition (AuditIfNotExists). |
| 8 | +- `scripts/deployment.ps1`: Creates/updates the policy definition and policy assignment. |
| 9 | +- `scripts/start-remediation.ps1`: Enumerates non-compliant SSIS Integration Runtimes and updates them via `Set-AzDataFactoryV2IntegrationRuntime`. |
| 10 | + |
| 11 | +## License Type Mapping |
| 12 | + |
| 13 | +| Parameter value | Portal label | API `licenseType` | |
| 14 | +|---|---|---| |
| 15 | +| `LicenseIncluded` | Pay-as-you-go | `LicenseIncluded` | |
| 16 | +| `BasePrice` | Azure Hybrid Benefit | `BasePrice` | |
| 17 | + |
| 18 | +> **Note:** Only **Managed** Integration Runtimes with `ssisProperties` (i.e., SSIS-enabled IRs) are in scope. Self-Hosted IRs and Managed IRs without `ssisProperties` (data movement / managed VNet only) do not consume a SQL license and are ignored by both the policy and the remediation script. |
| 19 | +
|
| 20 | +## Why AuditIfNotExists Instead Of DeployIfNotExists |
| 21 | + |
| 22 | +The four sibling packs (`sql-arc`, `sql-mi`, `sql-iaas`, `sql-paas`) use `DeployIfNotExists` with an embedded ARM template that PATCHes a single license property. That works because their resource providers merge nested properties on PUT. |
| 23 | + |
| 24 | +The Azure Data Factory resource provider does **not**: `properties.typeProperties` on `Microsoft.DataFactory/factories/integrationRuntimes` is a discriminated union, and a partial PUT through ARM would null out `computeProperties` (node size, count, VNet, etc.) and break the runtime. The official Microsoft sample [`enable-payg-for-azure-sql.ps1`](https://github.com/microsoft/sql-server-samples/blob/master/samples/manage/enable-payg-for-azure-sql/enable-payg-for-azure-sql.ps1) avoids this by calling `Set-AzDataFactoryV2IntegrationRuntime`, which performs an internal GET → merge → PUT against the data plane. |
| 25 | + |
| 26 | +The `Modify` effect is also not available: the `Managed.typeProperties.ssisProperties.licenseType` alias is **not** marked `Modifiable`. |
| 27 | + |
| 28 | +This pack therefore: |
| 29 | + |
| 30 | +- **Audits** drift via the policy assignment (so SSIS IRs show up in the compliance dashboard alongside the other SQL workloads). |
| 31 | +- **Remediates** out-of-band via `scripts/start-remediation.ps1`, which mirrors the Microsoft sample. |
| 32 | + |
| 33 | +## Licensing Conditions |
| 34 | + |
| 35 | +When selecting Azure Hybrid Benefit, ensure you meet the licensing requirements: |
| 36 | + |
| 37 | +- **Azure Hybrid Benefit** (`BasePrice`): *"I confirm that I have a SQL Server License with Software Assurance to apply this Azure Hybrid Benefit for SQL Server."* |
| 38 | + |
| 39 | +The deployment script will prompt for confirmation when targeting `BasePrice`. Use `-SkipLicenseConfirmation` to suppress the prompt in automated pipelines (the operator assumes responsibility for license compliance). |
| 40 | + |
| 41 | +## Prerequisites |
| 42 | + |
| 43 | +- PowerShell with Az modules installed (`Az.Resources` for deployment; `Az.DataFactory` for remediation). |
| 44 | +- Logged in to Azure (`Connect-AzAccount`). |
| 45 | +- Permissions to create policy definitions/assignments at target scope. |
| 46 | +- For remediation: Contributor on each Data Factory whose Integration Runtimes will be updated. |
| 47 | + |
| 48 | +## Deploy Policy |
| 49 | + |
| 50 | +Parameter reference: |
| 51 | + |
| 52 | +| Parameter | Required | Default | Allowed values | Description | |
| 53 | +|---|---|---|---|---| |
| 54 | +| `ManagementGroupId` | No | Tenant root group | Any valid management group ID | Scope where the policy definition is created. Defaults to the tenant root management group when not specified. | |
| 55 | +| `SubscriptionId` | No | Not set | Any valid subscription ID | If provided, policy assignment scope is the subscription. | |
| 56 | +| `TargetLicenseType` | Yes | N/A | `LicenseIncluded`, `BasePrice` | Target license type to enforce. | |
| 57 | +| `LicenseTypesToOverwrite` | No | All | `LicenseIncluded`, `BasePrice` | Select which current license states are eligible for update. | |
| 58 | +| `SkipLicenseConfirmation` | No | `false` | Switch (`present`/`not present`) | Skip the interactive license confirmation prompt (for CI/CD pipelines). | |
| 59 | + |
| 60 | +Definition and assignment creation: |
| 61 | + |
| 62 | +1. Download the required files. |
| 63 | + |
| 64 | +```powershell |
| 65 | +# Optional: create and enter a local working directory |
| 66 | +mkdir sa-sql-ssis-policy |
| 67 | +cd sa-sql-ssis-policy |
| 68 | +``` |
| 69 | + |
| 70 | +```powershell |
| 71 | +$baseUrl = "https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/manage/azure-data-factory-ssis/sql-ssis-license-type-compliance" |
| 72 | +
|
| 73 | +New-Item -ItemType Directory -Path policy, scripts -Force | Out-Null |
| 74 | +
|
| 75 | +curl -sLo policy/azurepolicy.json "$baseUrl/policy/azurepolicy.json" |
| 76 | +curl -sLo scripts/deployment.ps1 "$baseUrl/scripts/deployment.ps1" |
| 77 | +curl -sLo scripts/start-remediation.ps1 "$baseUrl/scripts/start-remediation.ps1" |
| 78 | +``` |
| 79 | + |
| 80 | +> **Note:** On Windows PowerShell 5.1, `curl` is an alias for `Invoke-WebRequest`. Use `curl.exe` instead, or run the commands in PowerShell 7+. |
| 81 | +
|
| 82 | +2. Login to Azure. |
| 83 | + |
| 84 | +```powershell |
| 85 | +Connect-AzAccount |
| 86 | +``` |
| 87 | + |
| 88 | +3. Set your variables. Only `TargetLicenseType` is required — all others are optional. |
| 89 | + |
| 90 | +```powershell |
| 91 | +# ── Required ── |
| 92 | +$TargetLicenseType = "LicenseIncluded" # "LicenseIncluded" or "BasePrice" |
| 93 | +
|
| 94 | +# ── Optional (uncomment to override defaults) ── |
| 95 | +# $ManagementGroupId = "<management-group-id>" # Default: tenant root management group |
| 96 | +# $SubscriptionId = "<subscription-id>" # Default: policy assigned at management group scope |
| 97 | +# $LicenseTypesToOverwrite = @("LicenseIncluded","BasePrice") # Default: all |
| 98 | +``` |
| 99 | + |
| 100 | +4. Run the deployment. |
| 101 | + |
| 102 | +```powershell |
| 103 | +# Minimal — uses defaults for management group and overwrite targets |
| 104 | +.\scripts\deployment.ps1 -TargetLicenseType $TargetLicenseType |
| 105 | +
|
| 106 | +# With subscription scope |
| 107 | +.\scripts\deployment.ps1 -TargetLicenseType $TargetLicenseType -SubscriptionId $SubscriptionId |
| 108 | +
|
| 109 | +# With all options |
| 110 | +.\scripts\deployment.ps1 ` |
| 111 | + -ManagementGroupId $ManagementGroupId ` |
| 112 | + -SubscriptionId $SubscriptionId ` |
| 113 | + -TargetLicenseType $TargetLicenseType ` |
| 114 | + -LicenseTypesToOverwrite $LicenseTypesToOverwrite |
| 115 | +``` |
| 116 | + |
| 117 | +This will: |
| 118 | +* Create/update the policy definition at the management group scope. |
| 119 | +* Create/assign the policy (at subscription scope when `-SubscriptionId` is provided, otherwise at management group scope). |
| 120 | +* Audit (no system-assigned identity is created — `AuditIfNotExists` does not require one) all Managed SSIS Integration Runtimes whose current `licenseType` is in `LicenseTypesToOverwrite` and does not match `TargetLicenseType`. |
| 121 | + |
| 122 | +**Scenario examples:** |
| 123 | + |
| 124 | +```powershell |
| 125 | +# Audit all SSIS IRs against Pay-as-you-go |
| 126 | +.\scripts\deployment.ps1 -TargetLicenseType "LicenseIncluded" |
| 127 | +
|
| 128 | +# Audit only IRs currently on Pay-as-you-go against Azure Hybrid Benefit |
| 129 | +.\scripts\deployment.ps1 -TargetLicenseType "BasePrice" -LicenseTypesToOverwrite @("LicenseIncluded") |
| 130 | +``` |
| 131 | + |
| 132 | +## Start Remediation |
| 133 | + |
| 134 | +Unlike the sibling packs, remediation does **not** use `Start-AzPolicyRemediation` — see [Why AuditIfNotExists Instead Of DeployIfNotExists](#why-auditifnotexists-instead-of-deployifnotexists). Instead, `scripts/start-remediation.ps1` enumerates SSIS IRs in scope and updates each one via `Set-AzDataFactoryV2IntegrationRuntime`. |
| 135 | + |
| 136 | +Parameter reference: |
| 137 | + |
| 138 | +| Parameter | Required | Default | Allowed values | Description | |
| 139 | +|---|---|---|---|---| |
| 140 | +| `ManagementGroupId` | No | Tenant root group | Any valid management group ID | Enumerates all subscriptions under this management group when `SubscriptionId` is not specified. | |
| 141 | +| `SubscriptionId` | No | Not set | Any valid subscription ID | If provided, remediation is limited to this subscription. | |
| 142 | +| `TargetLicenseType` | Yes | N/A | `LicenseIncluded`, `BasePrice` | Must match the assignment target license type. | |
| 143 | +| `LicenseTypesToOverwrite` | No | All | `LicenseIncluded`, `BasePrice` | Filter which current license states are eligible for update. Should match the value used at deployment time. | |
| 144 | +| `Force` | No | `false` | Switch (`present`/`not present`) | Skip the interactive confirmation that lists candidates before applying updates. | |
| 145 | + |
| 146 | +1. Set your variables. `TargetLicenseType` is required and must match the value used during deployment — all others are optional. |
| 147 | + |
| 148 | +```powershell |
| 149 | +# ── Required ── |
| 150 | +$TargetLicenseType = "LicenseIncluded" # Must match the deployment target |
| 151 | +
|
| 152 | +# ── Optional (uncomment to override defaults) ── |
| 153 | +# $ManagementGroupId = "<management-group-id>" # Default: tenant root management group |
| 154 | +# $SubscriptionId = "<subscription-id>" # Default: scans all subscriptions under the management group |
| 155 | +# $LicenseTypesToOverwrite = @("LicenseIncluded","BasePrice") # Default: all |
| 156 | +``` |
| 157 | + |
| 158 | +2. Run the remediation. |
| 159 | + |
| 160 | +```powershell |
| 161 | +# Minimal — scans all subscriptions under the tenant root management group |
| 162 | +.\scripts\start-remediation.ps1 -TargetLicenseType $TargetLicenseType |
| 163 | +
|
| 164 | +# With subscription scope |
| 165 | +.\scripts\start-remediation.ps1 -TargetLicenseType $TargetLicenseType -SubscriptionId $SubscriptionId |
| 166 | +
|
| 167 | +# With all options (non-interactive) |
| 168 | +.\scripts\start-remediation.ps1 ` |
| 169 | + -ManagementGroupId $ManagementGroupId ` |
| 170 | + -SubscriptionId $SubscriptionId ` |
| 171 | + -TargetLicenseType $TargetLicenseType ` |
| 172 | + -LicenseTypesToOverwrite $LicenseTypesToOverwrite ` |
| 173 | + -Force |
| 174 | +``` |
| 175 | + |
| 176 | +The script: |
| 177 | + |
| 178 | +1. Enumerates Data Factories and Integration Runtimes in scope. |
| 179 | +2. Filters to Managed SSIS IRs whose `LicenseType` does not match `TargetLicenseType` and whose current value is in `LicenseTypesToOverwrite`. |
| 180 | +3. Prints the candidate list and prompts for confirmation (skip with `-Force`). |
| 181 | +4. Calls `Set-AzDataFactoryV2IntegrationRuntime -LicenseType <target> -Force` against each candidate (the cmdlet performs the required GET → merge → PUT internally). |
| 182 | +5. Reports per-IR success or failure. |
| 183 | + |
| 184 | +> **Note:** `Set-AzDataFactoryV2IntegrationRuntime` requires the caller to have at least **Data Factory Contributor** (or Contributor) on each factory. The script runs in the interactive Az context — there is no managed identity involved. |
| 185 | +
|
| 186 | +## Scope |
| 187 | + |
| 188 | +The policy targets `Microsoft.DataFactory/factories/integrationRuntimes` resources and is filtered to: |
| 189 | + |
| 190 | +- IRs whose `type` is `Managed`. |
| 191 | +- IRs that have `typeProperties.ssisProperties` (i.e., SSIS-enabled). Self-Hosted IRs and Managed IRs without SSIS properties are out of scope. |
| 192 | + |
| 193 | +## Reference |
| 194 | + |
| 195 | +- Microsoft sample script (origin of this pattern): [enable-payg-for-azure-sql.ps1](https://github.com/microsoft/sql-server-samples/blob/master/samples/manage/enable-payg-for-azure-sql/enable-payg-for-azure-sql.ps1) |
| 196 | +- Azure Data Factory: [Configure Azure-SSIS Integration Runtime](https://learn.microsoft.com/azure/data-factory/create-azure-ssis-integration-runtime) |
0 commit comments