Building with Bicep: A Beginner's Journey
Azure Infrastructure as Code and creating Azure Automation Accounts
A few years ago, I took some Terraform courses and really enjoyed learning about provisioning cloud infrastructure like virtual machines, containers, networking, and storage. This sparked my interest in networking since I struggled with configuring network resources.
Since then, I hadn’t explored Infrastructure as Code until recently, when I started experimenting with Bicep, a domain-specific Azure language. Bicep simplifies deployment without any complex logic.
I’ve been wanting to write about setting up Azure Automation Accounts and PowerShell runbooks, but it’s challenging to configure all components—like automation accounts, schedules, runtime environments (e.g., PowerShell 7.4), modules, and scripts. I realized using Bicep would make the process consistent and repeatable.
I’ve used PowerShell Runbooks for scheduled tasks such as writing files to SharePoint libraries, retrieving data and sending emails with Microsoft Graph and gathering Intune and Defender reports.
Requirements
To get started you will need to install Bicep, you can do that with Winget
winget install -e --id Microsoft.Bicep
You will also need the Bicep Extension for VS Code which provides IntelliSense, type validation, and syntax highlighting.
The Bicep FIle
Parameters
I sed an objects for the tags as I wanted to have a few. These tags can be specified when you run the command to deploy or even in a parameter file which override parameters in theBicep file.
Figuring out the scheduleStartTime took me a while and I relied on a mix of Google-fu and Copilot to help me. I needed to add at least 5 minutes to the current time.
param location string = resourceGroup().location
param runbookName string = 'MyRunbook'
param automationAccountName string
param resourceTags object = {
CostCentre: 'IT'
}
// Schedule parameters
param baseTime string = utcNow('u')
@description('The start time of the schedule must be at least 5 minutes after the time you create the schedule, PT1H is onehour')
param scheduleStartTime string = dateTimeAdd(baseTime, 'PT1H')
param scheduleName string = 'Weekly'
@minLength(36)
@maxLength(36)
@description('Used to link the schedule to the runbook')
param jobScheduleName string = guid(resourceGroup().id)
Notice you can use the @description
decorator to add useful info next to any parameters.
Also notice that the resourceTags parameter is an object so I can include a set of tags by using the parameter name in each resource where I want tags.
Automation Account
The first resource to create is the Azure Automation Account as the rest of the resources reference it’s symbolic name as the parent, in this case I chose the symbolic name automationAccount. The symbolic name isn't the same as the resource name, it is used to reference it in other parts of the Bicep file.
resource automationAccount 'Microsoft.Automation/automationAccounts@2023-11-01' = {
identity: {
type: 'SystemAssigned'
}
name: automationAccountName
location: location
properties: {
disableLocalAuth: false
publicNetworkAccess: true
sku: {
name: 'Free'
}
}
tags: resourceTags
}
Managed identity
You’ll see that I specified SystemAssigned, that means it will enable the Managed Identity for the resource.
You will be able to see the corresponding service principal in the Enterprise Apps section in the Entra portal:
Now we are able to assign permissions to this service principal, I’ll create another post with more info on that.
Automation Account variables
This is an example of variables you can add to your Automation Account to use in a runbook.
resource fromEmail 'Microsoft.Automation/automationAccounts/variables@2023-11-01' = {
parent: automationAccount
name: 'from'
properties: {
value: '"adrisg335@nypd.net"'
isEncrypted: false
}
}
Note that you have to include double quotes inside the single quotes, if not you’ll get an error to do with JSON formatting when deploying.
You can access any variables using the following command in your runbook script:
Get-AutomationVariable -Name 'from'
Runtime Environment
Later I will explain how to configure the runbook to use this Runtime Environment
// Add PowerShell runtime Environment
resource PowerShellruntimeEnvironment 'Microsoft.Automation/automationAccounts/runtimeEnvironments@2023-05-15-preview' = {
parent: automationAccount
location: location
name: 'PowerShell-74'
properties: {
description: 'PowerShell 7.4 Runtime Environment'
runtime: {
language: 'PowerShell'
version: '7.4'
}
}
tags: resourceTags
}
Runbook
The runbook is what executes a series of tasks, there are several types of runbooks.
resource runbook 'Microsoft.Automation/automationAccounts/runbooks@2023-11-01' = {
parent: automationAccount
name: runbookName
location: location
properties: {
logVerbose: false
logProgress: false
description: 'This is a sample runbook'
publishContentLink: {
contentHash: {
algorithm: 'SHA256'
value: '9CE84FFFA1EB8FED62639FA8C5CA2F6E4EB3CD7963635BCF38BF0D8B255EC76B'
}
uri: '{ Insert Link Here}'
version: '1.0.0'
}
runbookType: 'PowerShell'
}
}
The URI could be a file in your github repo. You will also need to include the hash of the file. You can get that with the Get-FileHash command, for example:
Get-FileHash .\runbook\test.ps1
If you do specify the runtime environment, you need to specify runbookType: 'PowerShell'
otherwise it won’t work, see below:
{"code":"BadRequest","message":"The property runtimeEnvironment cannot be configured for runbookType PowerShell72. Either use runbookType PowerShell or remove runtimeEnvironment from input."}
In a later section I’ll explain how to manually specify the runtime environment.
Schedule
This creates a simple weekly schedule
// Add schedule
resource schedule 'Microsoft.Automation/automationAccounts/schedules@2023-11-01' = {
parent: automationAccount
name: scheduleName
properties: {
frequency: 'week'
interval: 1
startTime: scheduleStartTime
timeZone: 'CET'
}
}
To link the schedule to your Runbook you would need to use:
resource jobSchedule 'Microsoft.Automation/automationAccounts/jobSchedules@2023-11-01' = {
parent: automationAccount
name: jobScheduleName
properties: {
runbook: {
name: runbook.name
}
schedule: {
name: schedule.name
}
}
}
Outputs
Any outputs you specify can be found after the deployment is finish from the Outputs menu item in the deployment section of the Resource group.
output automationAccountId string = automationAccount.id
Modules
// Add required modules
resource graphAuthModule 'Microsoft.Automation/automationAccounts/modules@2023-11-01' = {
parent: automationAccount
name: 'Microsoft.Graph.Authentication'
properties: {
contentLink: {
uri: 'https://www.powershellgallery.com/api/v2/package/Microsoft.Graph.Authentication'
}
}
}
In the Automation Account under Shared resources → Modules you will be able to see the modules, however I didn’t find a way to specify the runtime version or even assign it to the Runtime resource I specified. So I ended up removing it from the Bicep file until I can figure it out.
Deployment
Connect using this command, if you’re already logged into the Azure portal you just need to click on the preceding link and enter the code
connect-AzAccount -DeviceCode
[Login to Azure] To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code DNP365D79 to authenticate.
Next you’ll need to specify the subscription so that subsequent commands are executed in the context of the subscription.
Get-AzSubscription
$context = Get-AzSubscription -SubscriptionId {Your subscription ID}
Set-AzContext $context
If you set the default resource then you won’t need to specify it when you deploy the template. This command also creates the resource group if it doesn’t already exist.
Set-AzDefault -ResourceGroupName {Resource Group}
Finally deploy the template.
$deploymentParams = @{
TemplateFile = "main.bicep"
resourceTags = @{
StartDate = "2025-12-02"
CostCentre = 'IT'
Requester = 'John.McClane@nypd.net'
}
}
New-AzResourceGroupDeployment @deploymentParams
After executing this command, you will need to specify the Automation Account name. Keep in mind that if you update your Bicep file and redeploy, you must provide the same name to make changes to the existing Automation account. Since Bicep files are idempotent, you will achieve the same results regardless of how many times you run the file. However, changing a resource name will create a new resource. Any modifications or additions to the properties of an existing resource will update that resource accordingly.
The next level is to use parameter files to store values for a Bicep file. That way you can just have the details saved in file instead of including it in the command. The command is useful when you want to override whatever is specified in the parameter file.
Post deployment Tasks
After you see the deployment successful message in the resource group deployments pane or in your terminal you will need to publish the Runbook and choose the runtime environment.
Runbook runtime environment
You will also have to change to the runtime environment experience to be able to specify the Runbook runtime environment.
Once you do that you will see the Runtime Environment created using the Bicep file in the Runtime Environments (Preview) pane:
You will find Runtime Environments (Preview) under Process Automation in the left hand side menu of the portal.
Publish Runbook
From the Automation Account resource go to Process Automation → Runbooks and you should see that the Runtime environment is the Environment we specified in the Bicep file.
If necessary click on the Runbook name and then from the horizontal menu choose Edit → Edit in portal. You can edit the Runbook if you wish and then click on Publish.
Even if you had published before changing the Runtime experience it should change to the specified environment when you change from the Old experience to Runtime Environment Experience. The Old experience will default to PowerShell 5.1.
Summary
There’s more I need to learn but I feel I made a good start working with Bicep. I recommend the Microsoft Learning path for Bicep which takes you through everything you need to know.
I wasn’t able to deploy the runbook with the latest API version, the latest version has a runtimeEnvironment property where you can specify the runtime environment. (Didn’t seem to work though)
The error I get is:
No registered resource provider found for location 'East US' and API version '2024-10-23' ...
When I look at the supported API versions I cannot see the latest (2024-10-23):
PS C:\> ((Get-AzResourceProvider -ProviderNamespace 'Microsoft.Automation').ResourceTypes |
Where-Object -Property ResourceTypeName -EQ -Value 'automationAccounts/runbooks').ApiVersions
2023-11-01
2023-05-15-preview
2022-08-08
...
In the change log I saw that the previous release 2023-11-01
did not have the runtimeEnvironment property as it was removed but 2023-05-15-preview
did have it, so I used that one.