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.