Quickstart: Use MSBuild to convert Bicep to JSON
Learn the process of utilizing MSBuild for the conversion of Bicep files to Azure Resource Manager JSON templates (ARM templates). Additionally, MSBuild can be utilized for the conversion of Bicep parameter files to Azure Resource Manager parameter files with the NuGet packages version 0.23.x or later. The provided examples demonstrate the use of MSBuild from the command line with C# project files for the conversion. These project files serve as examples that can be utilized in an MSBuild continuous integration (CI) pipeline.
Prerequisites
You need the latest versions of the following software:
- Visual Studio, or Visual Studio Code. The Visual Studio community version, available for free, installs .NET 6.0, .NET Core 3.1, .NET SDK, MSBuild, .NET Framework 4.8, NuGet package manager, and C# compiler. From the installer, select Workloads > .NET desktop development. With Visual Studio Code, you also need the extensions for Bicep and Azure Resource Manager (ARM) Tools
- PowerShell or a command-line shell for your operating system.
If your environment doesn't have nuget.org configured as a package feed, depending on how nuget.config
is configured, you might need to run the following command:
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
In certain environments, using a single package feed helps prevent problems arising from packages with the same ID and version containing different contents in different feeds. For Azure Artifacts users, this can be done using the upstream sources feature.
MSBuild tasks and Bicep packages
From your continuous integration (CI) pipeline, you can use MSBuild tasks and CLI packages to convert Bicep files and Bicep parameter files into JSON. The functionality relies on the following NuGet packages:
Package Name | Description |
---|---|
Azure.Bicep.MSBuild | Cross-platform MSBuild task that invokes the Bicep CLI and compiles Bicep files into ARM JSON templates. |
Azure.Bicep.CommandLine.win-x64 | Bicep CLI for Windows. |
Azure.Bicep.CommandLine.linux-x64 | Bicep CLI for Linux. |
Azure.Bicep.CommandLine.osx-x64 | Bicep CLI for macOS. |
You can find the latest version from these pages. For example:
The latest NuGet package versions match the latest Bicep CLI version.
Azure.Bicep.MSBuild
When included in project file's
PackageReference
property, theAzure.Bicep.MSBuild
package imports the Bicep task used for invoking the Bicep CLI.<ItemGroup> <PackageReference Include="Azure.Bicep.MSBuild" Version="0.24.24" /> ... </ItemGroup>
The package transforms the output of Bicep CLI into MSBuild errors and imports the
BicepCompile
target to streamline the usage of the Bicep task. By default, theBicepCompile
runs after theBuild
target, compiling all @(Bicep) items and @(BicepParam) items. It then deposits the output in$(OutputPath)
with the same filename and a .json extension.The following example shows project file setting for compiling main.bicep and main.bicepparam files in the same directory as the project file and places the compiled main.json and main.parameters.json in the
$(OutputPath)
directory.<ItemGroup> <Bicep Include="main.bicep" /> <BicepParam Include="main.bicepparam" /> </ItemGroup>
You can override the output path per file using the
OutputFile
metadata onBicep
items. The following example recursively finds all main.bicep files and places the compiled .json files in$(OutputPath)
under a subdirectory with the same name in$(OutputPath)
:<ItemGroup> <Bicep Include="**\main.bicep" OutputFile="$(OutputPath)\%(RecursiveDir)\%(FileName).json" /> <BicepParam Include="**\main.bicepparam" OutputFile="$(OutputPath)\%(RecursiveDir)\%(FileName).parameters.json" /> </ItemGroup>
More customizations can be performed by setting one of the following properties to the
PropertyGroup
in your project:Property Name Default Value Description BicepCompileAfterTargets
Build
Used as AfterTargets
value for theBicepCompile
target. Change the value to override the scheduling of theBicepCompile
target in your project.BicepCompileDependsOn
None Used as DependsOnTargets
value for theBicepCompile
target. This property can be set to targets that you wantBicepCompile
target to depend on.BicepCompileBeforeTargets
None Used as BeforeTargets
value for theBicepCompile
target.BicepOutputPath
$(OutputPath)
Set this property to override the default output path for the compiled ARM template. OutputFile
metadata onBicep
items takes precedence over this value.For the
Azure.Bicep.MSBuild
to operate, it's required to have an environment variable namedBicepPath
set. See the next bullet item for configuringBicepPath
.Azure.Bicep.CommandLine
The
Azure.Bicep.CommandLine.*
packages are available for Windows, Linux, and macOS. The following example references the package for Windows.<ItemGroup> <PackageReference Include="Azure.Bicep.CommandLine.win-x64" Version="__LATEST_VERSION__" /> ... </ItemGroup>
When referenced in a project file, the
Azure.Bicep.CommandLine.*
packages automatically set theBicepPath
property to the full path of the Bicep executable for the platform. The reference to this package can be omitted if Bicep CLI is installed through other means. For this case, instead of referencing anAzure.Bicep.Commandline
package, you can either configure an environment variable calledBicepPath
or addBicepPath
to thePropertyGroup
, for example on Windows:<PropertyGroup> <BicepPath>c:\users\john\.Azure\bin\bicep.exe</BicepPath> ... </PropertyGroup>
On Linux:
<PropertyGroup> <BicepPath>/usr/local/bin/bicep</BicepPath> ... </PropertyGroup>
Project file examples
The following examples show how to configure C# console application project files for converting Bicep files and Bicep parameter files to JSON. Replace __LATEST_VERSION__
with the latest version of the Bicep NuGet packages in the following examples. See MSBuild tasks and Bicep packages for finding the latest version.
SDK-based example
The .NET Core 3.1 and .NET 6 examples are similar. But .NET 6 uses a different format for the Program.cs file. For more information, see .NET 6 C# console app template generates top-level statements.
.NET 6
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <RootNamespace>net6-sdk-project-name</RootNamespace> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Azure.Bicep.CommandLine.win-x64" Version="__LATEST_VERSION__" /> <PackageReference Include="Azure.Bicep.MSBuild" Version="__LATEST_VERSION__" /> </ItemGroup> <ItemGroup> <Bicep Include="**\main.bicep" OutputFile="$(OutputPath)\%(RecursiveDir)\%(FileName).json" /> <BicepParam Include="**\main.bicepparam" OutputFile="$(OutputPath)\%(RecursiveDir)\%(FileName).parameters.json" /> </ItemGroup> </Project>
The
RootNamespace
property contains a placeholder value. When you create a project file, the value matches your project's name.
.NET Core 3.1
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Azure.Bicep.CommandLine.win-x64" Version="__LATEST_VERSION__" /> <PackageReference Include="Azure.Bicep.MSBuild" Version="__LATEST_VERSION__" /> </ItemGroup> <ItemGroup> <Bicep Include="**\main.bicep" OutputFile="$(OutputPath)\%(RecursiveDir)\%(FileName).json" /> <BicepParam Include="**\main.bicepparam" OutputFile="$(OutputPath)\%(RecursiveDir)\%(FileName).parameters.json" /> </ItemGroup> </Project>
NoTargets SDK example
The Microsoft.Build.NoTargets MSBuild project SDK allows project tree owners the ability to define projects that don't compile an assembly. This SDK allows creation of standalone projects that compile only Bicep files.
<Project Sdk="Microsoft.Build.NoTargets/__LATEST_MICROSOFT.BUILD.NOTARGETS.VERSION__">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Bicep.CommandLine.win-x64" Version="__LATEST_VERSION__" />
<PackageReference Include="Azure.Bicep.MSBuild" Version="__LATEST_VERSION__" />
</ItemGroup>
<ItemGroup>
<Bicep Include="main.bicep"/>
<BicepParam Include="main.bicepparam"/>
</ItemGroup>
</Project>
The latest Microsoft.Build.NoTargets
version can be found at https://www.nuget.org/packages/Microsoft.Build.NoTargets. For Microsoft.Build.NoTargets, specify a version like Microsoft.Build.NoTargets/3.7.56
.
<Project Sdk="Microsoft.Build.NoTargets/3.7.56">
...
</Project>
Classic framework example
Use the classic example only if the previous examples don't work for you. In this example, the ProjectGuid
, RootNamespace
and AssemblyName
properties contain placeholder values. When you create a project file, a unique GUID is created, and the name values match your project's name.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{11111111-1111-1111-1111-111111111111}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ClassicFramework</RootNamespace>
<AssemblyName>ClassicFramework</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<Bicep Include="main.bicep" />
<BicepParam Include="main.bicepparam" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Bicep.CommandLine.win-x64">
<Version>__LATEST_VERSION__</Version>
</PackageReference>
<PackageReference Include="Azure.Bicep.MSBuild">
<Version>__LATEST_VERSION__</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Convert Bicep to JSON
These examples demonstrate the conversion of a Bicep file and a Bicep parameter file to JSON using MSBuild. Start by creating a project file for .NET, .NET Core 3.1, or the Classic framework. Then, generate the Bicep file and the Bicep parameter file before running MSBuild.
Create project
Build a project in .NET with the dotnet CLI.
Open Visual Studio Code and select Terminal > New Terminal to start a PowerShell session.
Create a directory named msBuildDemo and go to the directory. This example uses C:\msBuildDemo.
Set-Location -Path C:\ New-Item -Name .\msBuildDemo -ItemType Directory Set-Location -Path .\msBuildDemo
Run the
dotnet
command to create a new console with the .NET 6 framework.dotnet new console --framework net6.0
The command creates a project file using the same name as your directory, msBuildDemo.csproj. For more information about how to create a console application from Visual Studio Code, see the tutorial.
Open msBuildDemo.csproj with an editor, and replace the content with the .NET 6 or NoTargets SDK example, and also replace
__LATEST_VERSION__
with the latest version of the Bicep NuGet packages.Save the file.
Create Bicep file
You need a Bicep file and a BicepParam file to be converted to JSON.
Create a main.bicep file in the same folder as the project file, for example: C:\msBuildDemo directory, with the following content:
@allowed([ 'Premium_LRS' 'Premium_ZRS' 'Standard_GRS' 'Standard_GZRS' 'Standard_LRS' 'Standard_RAGRS' 'Standard_RAGZRS' 'Standard_ZRS' ]) @description('Storage account type.') param storageAccountType string = 'Standard_LRS' @description('Location for all resources.') param location string = resourceGroup().location var storageAccountName = 'storage${uniqueString(resourceGroup().id)}' resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' = { name: storageAccountName location: location sku: { name: storageAccountType } kind: 'StorageV2' } output storageAccountNameOutput string = storageAccount.name
Create a main.bicepparam file in the C:\msBuildDemo directory with the following content:
using './main.bicep' param prefix = '{prefix}'
Replace
{prefix}
with a string value used as a prefix for the storage account name.
Run MSBuild
Run MSBuild to convert the Bicep file and the Bicep parameter file to JSON.
Open a Visual Studio Code terminal session.
In the PowerShell session, go to the folder that contains the project file. For example, the C:\msBuildDemo directory.
Run MSBuild.
MSBuild.exe -restore .\msBuildDemo.csproj
The
restore
parameter creates dependencies needed to compile the Bicep file during the initial build. The parameter is optional after the initial build.To use the .NET Core:
dotnet build .\msBuildDemo.csproj
or
dotnet restore .\msBuildDemo.csproj
Go to the output directory and open the main.json file that should look like the following example.
MSBuild creates an output directory based on the SDK or framework version:
- .NET 6: \bin\Debug\net6.0
- .NET Core 3.1: \bin\Debug\netcoreapp3.1
- NoTargets SDK: \bin\Debug\net48
- Classic framework: \bin\Debug
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", "version": "0.8.9.13224", "templateHash": "12345678901234567890" } }, "parameters": { "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS", "metadata": { "description": "Storage account type." }, "allowedValues": [ "Premium_LRS", "Premium_ZRS", "Standard_GRS", "Standard_GZRS", "Standard_LRS", "Standard_RAGRS", "Standard_RAGZRS", "Standard_ZRS" ] }, "location": { "type": "string", "defaultValue": "[resourceGroup().location]", "metadata": { "description": "Location for all resources." } } }, "variables": { "storageAccountName": "[format('storage{0}', uniqueString(resourceGroup().id))]" }, "resources": [ { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2023-04-01", "name": "[variables('storageAccountName')]", "location": "[parameters('location')]", "sku": { "name": "[parameters('storageAccountType')]" }, "kind": "StorageV2" } ], "outputs": { "storageAccountNameOutput": { "type": "string", "value": "[variables('storageAccountName')]" } } }
The main.parameters.json file should look like:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"prefix": {
"value": "mystore"
}
}
}
If you make changes or want to rerun the build, delete the output directory so new files can be created.
Clean up resources
When you're finished with the files, delete the directory. For this example, delete C:\msBuildDemo.
Remove-Item -Path "C:\msBuildDemo" -Recurse
Next steps
- For more information about MSBuild, see MSBuild reference and .NET project files.
- To learn more about MSBuild properties, items, targets, and tasks, see MSBuild concepts.
- For more information about the .NET CLI, see .NET CLI overview.