Problem:

Azure Blueprints can only be assigned at the Management Group scope using a REST API call. Assigning blueprints at this scope can be used to prevent Subscription Owners from removing a blueprint assignment. Though the assignment is made to the Management Group, the blueprint technically has a subscription scope. Blueprints are essentially subscription based deployments.

Assigning a blueprint at the Management Group scope can be a point of contention since Subscription Owners are given that permission to design, update, and maintain a subscription. However, the whole point of using Blueprints is to stamp out a subscription and ideally you want that configuration to be maintained.

Solution:

Using PowerShell, you can make a REST API call to assign the blueprint. The code below will not work unless you have a blueprint definition that has been published. So first things first… go create your blueprint definition and publish it. That can be created in the Portal or through code. After we have a published blueprint, we can assign the blueprint to a management group. Here are the steps:

  1. Download the script from my Github repo.
  2. Open PowerShell. This script requires the AZ module.
  3. Change your working directory to the folder where you downloaded the script.
  4. Call the script using all the parameters. See below for an example:
.\Assign-AzureBlueprint.ps1 -Blueprint test -IdentityType SystemAssigned -Location eastus -LockAssignment None -ManagementGroup JasonMasten -Subscription 'Visual Studio Enterprise Subscription'

NOTE: This script was written to support a blueprint definition with a single policy artifact. The values for the policy artifact are hard coded in the blueprint definition. If you want to specify your Resource Group and Parameter values during the assignment, the Body variable will need to be updated with that information. The syntax for those properties in JSON can be found on Microsoft’s Docs website. You will need to convert the JSON values to PowerShell objects to add them to my script.

Explanation:

In this section, I will break down the script and explain each chunk of code:

  1. Params
Param(

    [parameter(Mandatory=$true)]
    [string]$Blueprint,
    
    [parameter(Mandatory=$true)]
    [ValidateSet("UserAssigned","SystemAssigned")]
    [string]$IdentityType,

    [parameter(Mandatory=$true)]
    [ValidateScript({(Get-AzLocation).Location -contains $_})]
    [string]$Location,

    [parameter(Mandatory=$true)]
    [ValidateSet("AllResourcesDoNotDelete","AllResourcesReadOnly","None")]
    [string]$LockAssignment,
    
    [parameter(Mandatory=$true)]
    [string]$ManagementGroup,

    [parameter(Mandatory=$true)]
    [string]$Subscription

)

[cmdletbinding]

The Params block takes the guess work out of setting up the variables. A few of the variables have validation to ensure you pick the correct values.

  • Blueprint variable: the name of your blueprint definition that is already defined and stored at the appropriate Management Group scope.
  • IdentityType variable: choose one of the two options. This value determines which type of identity you will use to assign your blueprint. A system assigned identity is ideal if you have the appropriate permissions.
  • Location variable: input an Azure location using its “Location” value not the “Display Name”. For instance, “East US” would be the display name and the location value would be “eastus”. A validation script will ensure a proper Azure location is used for your blueprint assignment.
  • LockAssignment variable: input which type of lock assignment you want to include with your blueprint assignment. The three allowed options are included in the validation set. More info on blueprint locks can be found on Microsoft’s Docs website.
  • ManagementGroup variable: input the name of a Management Group within your tenant. The Management Group you choose should be a parent to the subscription you plan to manage with your blueprint.
  • Subscription variable: input the name of the subscription that you want to manage with your blueprint.
  1. Coxtext
Set-AzContext -Subscription $Subscription -ErrorAction Stop

$Context = Get-AzContext

In the Context section, we are setting the context to ensure we are working in the appropriate Azure Subscription and getting the context to use data from output.

  1. User Assigned Managed Identity Resource ID
if($IdentityType -eq 'UserAssigned')
{
    $IdentityName = Read-Host -Prompt 'User Assigned Identity Name'
    $IdentityId = (Get-AzResource -ResourceType 'Microsoft.ManagedIdentity/userAssignedIdentities' -Name $IdentityName).ResourceId
}

The User Assigned Managed Identity section acquires the resource ID of a User Assigned Managed Identity if using that type of identity to assign your blueprint.

  1. Header
$Profile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile

$Client = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($Profile)

$Token = $Client.AcquireAccessToken($Context.Subscription.TenantId)

$Header = @{
    'Content-Type'='application/json'
    'Authorization'='Bearer ' + $Token.AccessToken
}

The Header section of the code defines the header used in the REST API call.

  1. URI
$URI = "https://management.azure.com/providers/Microsoft.Management/managementGroups/" + $ManagementGroup + "/providers/Microsoft.Blueprint/blueprintAssignments/" + $Blueprint + "?api-version=2018-11-01-preview"

The URI section informs the Azure Control Plane which resource you plan manage with your REST API call.

  1. Body
$Body = @{
    
    identity = switch($IdentityType){
        
        SystemAssigned {@{ type = $IdentityType }}
        UserAssigned {@{ type = $IdentityType; userAssignedIdentities = @{ $IdentityId = @{} } }}

    }
    properties = @{
    
        blueprintId = (Get-AzBlueprint | Where-Object {$_.Name -eq $Blueprint}).Id
        resourceGroups = @{}
        scope = '/subscriptions/' + $Context.Subscription.Id
        locks = @{ mode = $LockAssignment }
        parameters = @{}
    }
    location = $Location

}

$BodyJson = ConvertTo-Json -InputObject $Body -Depth 100

The Body is a JSON object that needs to sent in the REST API call. Since we need to manipulate the body, the body is defined as a PowerShell object and later converted to JSON. If you require Resource Groups and / or Parameters with your blueprint assignment, you will need to update those properties within the PowerShell object.

  1. REST API Call
Invoke-RestMethod `
    -Headers $Header `
    -Method Put `
    -Uri $URI `
    -Body $BodyJson `
    -Verbose

In this last chunk of code, you will be making a REST API call to Azure using the “Invoke-RestMethod” cmdlet. The “Put” method is used to create or update a blueprint assignment.

References: