Run PowerShell scripts with Azure Bicep

Did you know you can run Azure PowerShell or CLI scripts with Bicep? With Microsoft.Resources/deploymentScripts you can execute scripts in a Bicep deployment. In addition, you can work with the outputs of the script that you ran. This opens loads of automation possibilities and flexibility.

What are Deployment Scripts?

The deploymentScript resource can run PowerShell or Bash scripts that are run inside a temporary container. The given scripts run during an ARM deployment, so besides infrastructure deployments, it is possible to call internal or external APIs or gather resource information before deploying infrastructure. 

DeploymentScript uses User-Assigned Managed Identities, or Connect-AzAccount/az login. This identity connects to Azure and runs the scripts inside the container. A managed identity can get an RBAC (or Azure AD) role, this helps to scope what a deployment scripts can control, deploy or delete.

Once the deployment script is finished, it is possible to get the outputs of a script. These outputs can be used to populate properties in other resource deployments.

How does it work?

When the resource type Microsoft.Resources/deploymentScripts is deployed the Azure Resource Manager deploys the following resources in the given resource group:

  • Azure Container Instance – is used for running scripts.
  • Azure Storage Account – is used to store script outputs in a file share.
  • Deployment Script – is used for debugging or logging.

The Container Instance and the Storage Account are temporary resources. The ARM engine will automatically clean up these resources once the deployment script has been completed. Billing is minimal when you let the ARM engine clean up the resources. It is also possible to use existing container instances and storage accounts however, these do not clean up automatically and have to be managed by yourself.

How does this work in practice?

To demonstrate the working of deployment scripts, we want to deploy the following resources:

  • User-Assigned Managed Identity
  • Deployment Script (and indirectly also a container instance and storage account)
  • Azure Key Vault

The goal of this Bicep deployment is to create an Azure Key Vault, along with a service principal that is granted access to the policy of the key vault.

param parLocation string = 'westeurope'
var varTenantId = tenant().tenantId
resource resManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {
name: 'john-managed-identity'
location: parLocation
}
resource resDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'create-spn-for-kv'
location: parLocation
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${resManagedIdentity.id}' : {}
}
}
properties: {
azPowerShellVersion: '9.0'
retentionInterval: 'P1D'
scriptContent: '''
$spnAppId = New-AzADServicePrincipal -DisplayName "my-keyvault-spn" | Select-Object -ExpandProperty AppId
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['appId'] = $spnAppId
'''
}
}
resource resKeyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: 'my-ds-key-vault'
location: parLocation
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
tenantId: varTenantId
accessPolicies: [
{
tenantId: varTenantId
objectId: resDeploymentScript.properties.outputs.appId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
sku: {
name: 'standard'
family: 'A'
}
}
}

Deployment Scripts resource

resource resDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'create-spn-for-kv'
location: parLocation
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${resManagedIdentity.id}' : {}
}
}
properties: {
azPowerShellVersion: '9.0'
retentionInterval: 'P1D'
scriptContent: '''
$spnAppId = New-AzADServicePrincipal -DisplayName "my-keyvault-spn" | Select-Object -ExpandProperty AppId
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['appId'] = $spnAppId
'''
}
}

Let’s take a look at the deployment script resource Microsoft.Resources/deploymentScripts. In this Bicep resource several properties are important:

  • kind

This specifies the type of the script. Only Azure PowerShell or Azure CLI scripts are supported, but not limited to Azure PowerShell or Azure CLI commands. Values are: AzurePowerShell or AzureCLI.

  • identity

Not required, but an identity is needed in some way. The identity property uses a user-assigned managed identity that is created in an earlier stage in the Bicep deployment resManagedIdentity.id. Currently, only user-assigned managed identities can be used. If no identity is given, the deployment script has to call Connect-AzAccount in PowerShell or az login in AzureCLI.

  • azPowerShellVersion / azCliVersion

Azure PowerShell or Azure CLI version to be used when running script. Not all versions are supported, but Microsoft shared a list of supported versions for Azure PowerShell here or Azure CLI here.

  • retentionInterval

The retention interval is used to determine how long the script results should be stored. By default, the results are erased after running the script. In the example, I used P1D, which stands for “one day”. The script refers to the deployment script resource.

  • scriptContent

The script to run. This can be a multi-line ''' inline script or the loadTextContent() Bicep function can be used load a script from a file.

The output of the example

If we run the demonstration example a few resources are being created:

The resources defined in the example are key vault, managed identity and deployment scripts. The storage account and container instances are created by the deployment script and the names are randomly generated.

In the deployment script resource we can see that the container instance and storage account are linked and that they are removed after the provision state is succeeded.

The script created a service principal and used the appId as an output. This output can be found in the outputs of the deployment script.

The deployment script output is used in the access policies of the key vault. The created service principal has permission to the created key vault. 

Deployment Script outputs

In the script contents, outputs can be defined that can be utilized elsewhere in the Bicep template. The definition of deployment script outputs is different between Azure PowerShell and Azure CLI:

Azure PowerShell

$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['appId'] = $spnAppId

For Azure PowerShell, you have to define a hashtable with the name DeploymentScriptOutputs

Azure CLI

$spnAppId > $AZ_SCRIPTS_OUTPUT_PATH

For Azure CLI, you have to output the value to the $AZ_SCRIPTS_OUTPUT_PATH variable.

Use the output in Bicep

resource resDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
...
}
output outScriptOutput string = resDeploymentScript.properties.outputs.appId

To use the outputs generated by the Azure PowerShell or CLI script refer to the symbolic name of the resource. This resource contains the property .properties.outputs. which has the value of $spnAppId stored in appId.

Deployment Script input parameters

As you can see, we are creating a service principal with a predefined name. We can make this name adjustable by populating it with parameters supplied by Bicep.

param parSpnName string = 'my-input-parameter'
### hidden managed identity resource ###
resource resDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'create-spn-for-kv'
location: parLocation
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${resManagedIdentity.id}' : {}
}
}
properties: {
azPowerShellVersion: '9.0'
retentionInterval: 'P1D'
arguments: '-SpnName ${parSpnName}'
scriptContent: '''
param (
[string] $SpnName
)
$spnAppId = New-AzADServicePrincipal -DisplayName $SpnName | Select-Object -ExpandProperty AppId
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['appId'] = $spnAppId
'''
}
}

In the above Bicep we added the parameter parSpnName which is used as input for the PowerShell script. Also, the script is modified to support parameters. We added the parameter block that contains $SpnName to supply the script with the given service principal name. To pass through the value of parSpnName the arguments property was added. This property corresponds with the parameter required by the PowerShell script.

If we check the Azure Portal we see our deployment script resource create-spn-for-kv. Under content and inputs we see the property arguments. We can see that the dynamic value (${parSpnName}) has been changed to the given parameter value. In this case the hardcoded value my-input-parameter.

Conclusion

This is how you can use deployment scripts in your Bicep templates. It offers new possibilities and flexibility regarding infrastructure automation. In this blog post we talked about creating a service principal, but it can also be used to look up IP-address data, for creating certificates and saving it in a key vault and so on. 

If you want to have a deployment script Bicep snippet check out my GitHub repository: bicep-snippets.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s