Microsoft announced last spring that Bicep templates for Microsoft Graph are coming in public preview.
What does this mean now?
Bicep support can now be used to create a declarative Infrastructure as Code (IaC) capability for Microsoft Graph resources. If you are already using Bicep for Azure resources, for example, then you will find your way around relatively quickly. In addition, PowerShell or Graph REST requests will no longer be necessary for certain activities in your automation scripts.
Important to mention
This is still a public preview which is very limited.
The aim of this blog series is to show you what is already possible with Bicep for Microsoft Graph and what is not. I will be posting step-by-step examples over the next few weeks. In Part 1 we focus on the setup, the service user and groups.
Overview
The following resources are supported in the current v1.0 version of Bicep for Microsoft Graph:
Service | Microsoft Graph Bicep resource type |
---|---|
Applications | Microsoft.Graph/applications |
App role assignments | Microsoft.Graph/appRoleAssignedTo |
Federated identity credentials | Microsoft.Graph/applications/federatedIdentityCredentials |
Groups | Microsoft.Graph/groups |
OAuth2 permission grants (delegated permission grants) | Microsoft.Graph/oauth2PermissionGrants |
Service principals | Microsoft.Graph/servicePrincipals |
Users | Microsoft.Graph/users |
prerequisites
The following prerequisites must be met in order to use Bicep for Microsoft Graph successfully.
- Azure CLI installation on your machine (for deployment purposes)
- VSCode Bicep extension (v0.27.1 or later)
- Correct permissions to run tasks according to service docs
Bicep preview feature
To be able to use Bicep for Microsoft Graph at all, it is absolutely necessary to activate the preview features in the bicepconfig.json file.
{ "experimentalFeaturesEnabled": { "extensibility": true } }
Microsoft Graph Bicep extension
In order for Bicep to understand the Microsoft Graph resources, the extension must also be specified in bicepconfig.json.
{ "extensions": { "microsoftGraphV1": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.9-preview" } }
It was possible to define the provider until January 2025. This is no longer possible, see the following Microsoft article.
Final Bicep config file
The first bicepconfig.json file should now look like this:
{ "experimentalFeaturesEnabled": { "extensibility": true }, "extensions": { "microsoftGraphV1": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.9-preview" } }
Services
Users
As of today, the service users can only «read» and cannot create or mutate new Entra identities.
Your app registration requires the following least privileged Microsoft Graph permission:
Permission type | Least privileged permissions | |
---|---|---|
Delegated (work or school account) | User.Read | |
Delegated (personal Microsoft account) | User.Read | |
Application | User.Read.All |
The following resource format is used to read a user object using Bicep.
resource symbolicname 'Microsoft.Graph/[email protected]' existing = { userPrincipalName: 'string' }
All available properties can be found here.
Groups
The situation is slightly different for the groups. You can read and also create new groups.
The resource format looks like this and all properties can be found here.
resource symbolicname 'Microsoft.Graph/[email protected]' = { classification: 'string' description: 'string' displayName: 'string' groupTypes: [ 'string' ] isAssignableToRole: bool isManagementRestricted: bool mailEnabled: bool mailNickname: 'string' members: [ 'string' ] membershipRule: 'string' membershipRuleProcessingState: 'string' owners: [ 'string' ] preferredDataLocation: 'string' preferredLanguage: 'string' securityEnabled: bool theme: 'string' uniqueName: 'string' visibility: 'string' }
You need the following Microsoft Graph permission to be able to execute the whole process:
Permission type | Least privileged permissions | |
---|---|---|
Delegated (work or school account) | User.Read | |
Delegated (personal Microsoft account) | User.Read | |
Application | User.Read.All |
Example
So that the theory can now be applied and you can see a result, I have packed the two Microsoft Graph Bicep resources Users and Groups together.In the following example, a new Entra group is created and filled with existing members in the tenant. I will intentionally not show the complete code here, as it would go beyond the scope. I am happy to share with you the Github repository where you can find the code.
The two resources were created in separate bicep modules so that the whole thing is clearer in the main.bicep file.
users.bicep
extension microsoftGraphV1 @description('List of User Principal Names (UPNs)') param upnList array var upnListLength = length(upnList) resource userList 'Microsoft.Graph/[email protected]' existing = [for i in range(0, upnListLength): { userPrincipalName: upnList[i] }] output userIds array = [for i in range(0, upnListLength): userList[i].id] output userPrincipalNames array = upnList
groups.bicep
extension microsoftGraphV1 @description('Name of the group to be created') param groupName string @description('List of User IDs to be added as members') param userIds array resource group 'Microsoft.Graph/[email protected]' = { displayName: groupName mailEnabled: false mailNickname: uniqueString(groupName) securityEnabled: true uniqueName: groupName members: userIds } output groupId string = group.id output groupDisplayName string = group.displayName
main.bicep
extension microsoftGraphV1 // Parameters @description('Name of the group to be created') param groupName string @description('List of User Principal Names (UPNs)') param upnList array // Module invocations module users 'modules/users.bicep' = { name: 'users' params: { upnList: upnList } } module group 'modules/groups.bicep' = { name: 'group' params: { groupName: groupName userIds: users.outputs.userIds } } // Outputs output addedUserList array = users.outputs.userPrincipalNames output groupName string = group.outputs.groupDisplayName output groupId string = group.outputs.groupId
main.bicepparam
using './main.bicep' param groupName = 'contoso' param upnList = [ '[email protected]' '[email protected]' '[email protected]' ]
Conclusion
To summarise, we can say that Microsoft is taking another step in the right direction with the support of Graph in Bicep. The current integration of Microsoft Graph into existing automation pipelines in particular is not ideal, as at least a second language has to be used and does not really follow the declarative approach.
To be fair, however, it has to be said that almost a year after the public preview was communicated, no real progress has been made here. The samples provided by Microsoft employees are still relatively few and my example in particular has shown that they don’t always work.
Nevertheless, I look forward to blogging more about this in the coming weeks and finding out what works and what doesn’t. I’m also already looking forward to the first GA release of Microsoft Graph for Bicep.