<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Deployment Scripts on BuiltWithCaffeine</title><link>https://blog.builtwithcaffeine.cloud/tags/deployment-scripts/</link><description>Recent content in Deployment Scripts on BuiltWithCaffeine</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>- BuiltWithCaffeine</copyright><lastBuildDate>Sat, 27 Jun 2026 08:00:00 +0000</lastBuildDate><atom:link href="https://blog.builtwithcaffeine.cloud/tags/deployment-scripts/rss.xml" rel="self" type="application/rss+xml"/><item><title>Bridging the Gap: Using Bicep Deployment Scripts to Create Entra Security Groups and Assign Entra Roles</title><link>https://blog.builtwithcaffeine.cloud/posts/bicep-entra-group-role-assignment-deployment-scripts/</link><pubDate>Sat, 27 Jun 2026 08:00:00 +0000</pubDate><guid>https://blog.builtwithcaffeine.cloud/posts/bicep-entra-group-role-assignment-deployment-scripts/</guid><description>&lt;p&gt;If you’ve been building out identity automation with Bicep and Azure Verified Modules (AVM), you’ve probably hit this at least once:&lt;/p&gt;
&lt;p&gt;You can deploy most Azure resources cleanly with modules, but some Entra ID scenarios still require a workaround.&lt;/p&gt;
&lt;p&gt;In my case, I needed to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;strong&gt;Microsoft Entra security group&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Assign an &lt;strong&gt;Entra role&lt;/strong&gt; to that group&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At the time of writing, the module path I wanted to use did not fully support this workflow end-to-end. So I used a pattern that has become one of my go-to options for edge cases: &lt;strong&gt;Bicep &lt;code&gt;deploymentScripts&lt;/code&gt;&lt;/strong&gt; with Microsoft Graph.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="why-deploymentscripts-works-well-here"&gt;Why deploymentScripts works well here
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;deploymentScripts&lt;/code&gt; gives you a controlled way to run script logic inside your IaC deployment.&lt;/p&gt;
&lt;p&gt;That means you can keep your deployment flow centralized in Bicep while still handling operations that currently need imperative logic.&lt;/p&gt;
&lt;p&gt;For Entra identity operations, this is especially useful because you often need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query if an object already exists&lt;/li&gt;
&lt;li&gt;Create it only when needed&lt;/li&gt;
&lt;li&gt;Fetch IDs for downstream assignments&lt;/li&gt;
&lt;li&gt;Call Microsoft Graph endpoints that don’t map 1:1 to current module coverage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short: &lt;strong&gt;Bicep stays the orchestrator, script handles the gap&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-design-pattern"&gt;The design pattern
&lt;/h2&gt;&lt;p&gt;Here’s the high-level flow I used (matching my &lt;code&gt;microsoft-graph/groups/AssignableToEntraRole&lt;/code&gt; module):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bicep deployment starts at subscription scope (or resource-group scope if preferred)&lt;/li&gt;
&lt;li&gt;A user-assigned managed identity is attached to the deployment script&lt;/li&gt;
&lt;li&gt;Script authenticates to Microsoft Graph using that managed identity&lt;/li&gt;
&lt;li&gt;Script checks for existing group by &lt;code&gt;mailNickname&lt;/code&gt; (idempotency key)&lt;/li&gt;
&lt;li&gt;Script creates the security group if missing&lt;/li&gt;
&lt;li&gt;Script assigns the Entra role to the group if not already assigned&lt;/li&gt;
&lt;li&gt;Script returns outputs (&lt;code&gt;resourceId&lt;/code&gt;, &lt;code&gt;displayName&lt;/code&gt;, &lt;code&gt;mailNickname&lt;/code&gt;) back to Bicep&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This keeps repeated deployments safe and predictable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;Before you run this pattern, ensure the identity used by &lt;code&gt;deploymentScripts&lt;/code&gt; has sufficient Graph permissions and directory privileges.&lt;/p&gt;
&lt;p&gt;At minimum, validate access for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Group creation and updates in Entra ID&lt;/li&gt;
&lt;li&gt;Group member management&lt;/li&gt;
&lt;li&gt;Reading application metadata when needed&lt;/li&gt;
&lt;li&gt;Entra directory role assignment operations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In my baseline module, the managed identity is granted Graph app roles such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Group.ReadWrite.All&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GroupMember.ReadWrite.All&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Application.Read.All&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on your tenant setup and governance model, you may need admin consent and privileged setup to grant these.&lt;/p&gt;
&lt;style type="text/css"&gt;
.box-shortcode {
display: flex;
gap: 0.75em;
padding: 1.6em;
padding-top: 1.4em;
line-height: 1.6;
margin: 1em 0 2em;
border-radius: 8px;
color: #000;
background: #f3ebe850;
align-items: flex-start;
}
[data-scheme="dark"] .box-shortcode {
color: #fff;
background: #333;
}
.box-shortcode.warning { background: #ff6b6b4f; }
.box-shortcode.info { background: #0089e41c; box-shadow: 3px 3px 5px #0089e410; }
.box-shortcode.important { background: #f7ec2c7d; }
.box-shortcode.tip { background: #a3ffa34d; box-shadow: 3px 3px 5px #0089e410; }
.icon-box {
display: inline-flex;
flex: 0 0 auto;
line-height: 1;
align-self: flex-start;
margin-top: 0.2em;
}
.icon-box svg {
height: 1.2em;
width: 1.2em;
fill: currentColor;
}
.box-shortcode .box-body {
flex: 1 1 auto;
min-width: 0;
}
.box-shortcode .box-body &gt; :first-child {
margin-top: 0;
}
.box-shortcode .box-body &gt; :last-child {
margin-bottom: 0;
}
.box-shortcode .sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.box-shortcode :not(pre) &gt; code {
border-radius: 4px;
padding: 0.15em 0.45em;
background: #f1f3f5;
border: 1px solid #e0e4e8;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
font-size: 0.85em;
line-height: 1.25em;
white-space: normal;
display: inline-block;
vertical-align: baseline;
}
.box-shortcode p &gt; code,
.box-shortcode li &gt; code,
.box-shortcode span &gt; code,
.box-shortcode div &gt; code,
.box-shortcode em &gt; code,
.box-shortcode strong &gt; code {
background: #f1f3f5;
border: 1px solid #e0e4e8;
padding: 0.15em 0.45em;
border-radius: 4px;
font-weight: 600;
}
[data-scheme="dark"] .box-shortcode :not(pre) &gt; code {
background: #2d333b;
border-color: #3b424b;
color: #e6edf3;
}
[data-scheme="dark"] .box-shortcode p &gt; code,
[data-scheme="dark"] .box-shortcode li &gt; code,
[data-scheme="dark"] .box-shortcode span &gt; code,
[data-scheme="dark"] .box-shortcode div &gt; code,
[data-scheme="dark"] .box-shortcode em &gt; code,
[data-scheme="dark"] .box-shortcode strong &gt; code {
background: #2d333b;
border-color: #3b424b;
color: #e6edf3;
font-weight: 600;
}
.box-shortcode strong,
.box-shortcode strong code,
.box-shortcode p strong code,
.box-shortcode li strong code {
font-weight: 600 !important;
}
.box-shortcode pre,
.box-shortcode .highlight {
margin: 1em auto;
overflow: hidden;
display: block;
border-radius: 6px;
box-sizing: border-box;
position: relative;
isolation: isolate;
max-width: 100%;
}
.box-shortcode .highlight {
padding-left: 0;
padding-right: 0;
margin-right: 0;
width: 100%;
max-width: 100%;
}
@media (min-width: 1100px) {
.box-shortcode pre,
.box-shortcode .highlight {
max-width: 900px;
}
}
.box-shortcode pre code,
.box-shortcode .highlight pre,
.box-shortcode .highlight code {
display: block;
border-radius: inherit;
background-clip: padding-box;
margin: 0;
}
.box-shortcode .highlight-wrapper {
margin: 1em auto;
border-radius: 6px;
overflow: hidden;
box-sizing: border-box;
isolation: isolate;
max-width: 100%;
}
@media (min-width: 1100px) {
.box-shortcode .highlight-wrapper {
max-width: 900px;
}
}
.box-shortcode .highlight-wrapper .highlight {
border-radius: inherit;
overflow: hidden;
}
.box-shortcode .highlight button.copy {
position: absolute;
top: 0.5em;
right: 0.5em;
z-index: 1;
border-radius: 4px;
}
&lt;/style&gt;
&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;symbol id="tip-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/&gt;
&lt;/symbol&gt;
&lt;symbol id="important-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/&gt;
&lt;/symbol&gt;
&lt;symbol id="warning-box" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/&gt;
&lt;/symbol&gt;
&lt;symbol id="info-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet"&gt;
&lt;path
d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/&gt;
&lt;/symbol&gt;
&lt;/svg&gt;&lt;div class="box box-shortcode info" data-variant="info" role="status"&gt;
&lt;span class="icon-box"&gt;
&lt;span class="sr-only"&gt;Info&lt;/span&gt;
&lt;svg&gt;&lt;use href="#info-box"&gt;&lt;/use&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="box-body"&gt;
&lt;p&gt;Use a dedicated managed identity for deployment scripts and scope permissions as tightly as possible. Least privilege is your friend here.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="baseline-example-role-assignable-group-module-call"&gt;Baseline example: role-assignable group module call
&lt;/h2&gt;&lt;p&gt;This is the same shape as my baseline example (&lt;code&gt;AssignableToEntraRole/examples/basic/main.bicep&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bicep" data-lang="bicep"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;userManagedIdentityName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;roleAssignableGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;../../main.bicep&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;create-role-assignable-group-example&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;create-role-assignable-group-example&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Contoso SQL Server Admins&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mailNickname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;contoso-sql-server-admins&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;memberObjectIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;11111111-1111-1111-1111-111111111111&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;entraRoleDefinitionIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;88d8e3e3-8f55-4a1e-953a-9b9898b8876b&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;entraRoleDirectoryScopeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;userManagedIdentityName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;userManagedIdentityName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;resourceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;roleAssignableGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;resourceId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;displayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;roleAssignableGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;displayName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mailNickname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;roleAssignableGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mailNickname&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The module wraps a &lt;code&gt;Microsoft.Resources/deploymentScripts&lt;/code&gt; resource under the hood.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="baseline-script-behavior-inside-the-module"&gt;Baseline script behavior (inside the module)
&lt;/h2&gt;&lt;p&gt;The module uses &lt;code&gt;kind: 'AzureCLI'&lt;/code&gt; and drives Microsoft Graph with &lt;code&gt;az rest&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The key implementation points are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Resolve existing group by &lt;code&gt;mailNickname&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create group with &lt;code&gt;isAssignableToRole: true&lt;/code&gt; if missing&lt;/li&gt;
&lt;li&gt;Add members idempotently (with retries for Entra replication delay)&lt;/li&gt;
&lt;li&gt;Assign requested directory roles when missing&lt;/li&gt;
&lt;li&gt;Write outputs to &lt;code&gt;$AZ_SCRIPTS_OUTPUT_PATH&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;existing_group_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;az rest &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --method GET &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --url &lt;span class="s2"&gt;&amp;#34;https://graph.microsoft.com/v1.0/groups?\$filter=mailNickname eq &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;mail_nickname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;amp;\$select=id,displayName,mailNickname&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --query &lt;span class="s2"&gt;&amp;#34;value[0].id&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --output tsv &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;existing_group_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;create_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;jq -n &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --arg displayName &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;display_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --arg mailNickname &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;mail_nickname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; displayName: $displayName,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; mailEnabled: false,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; mailNickname: $mailNickname,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; securityEnabled: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; isAssignableToRole: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; groupTypes: []
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;group_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;az rest --method POST --url &lt;span class="s2"&gt;&amp;#34;https://graph.microsoft.com/v1.0/groups&amp;#34;&lt;/span&gt; --headers &lt;span class="s2"&gt;&amp;#34;Content-Type=application/json&amp;#34;&lt;/span&gt; --body &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;create_body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;group_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;group_json&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.id&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;group_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;existing_group_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is the same workaround pattern documented in the module README and is ideal when native Graph Bicep support for role-assignable groups is still limited.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="idempotency-and-safety-notes"&gt;Idempotency and safety notes
&lt;/h2&gt;&lt;p&gt;This pattern is powerful, but identity changes are sensitive. A few practical guardrails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prefer &lt;code&gt;mailNickname&lt;/code&gt; (or another immutable key) for idempotent lookup&lt;/li&gt;
&lt;li&gt;Add a stable marker (for example, description prefix or custom naming convention)&lt;/li&gt;
&lt;li&gt;Validate role assignment state before writing&lt;/li&gt;
&lt;li&gt;Keep script timeout realistic for directory propagation delays&lt;/li&gt;
&lt;li&gt;Capture script logs to troubleshoot tenant permission issues quickly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also remember that Entra replication can be eventually consistent, so small delays between create/read operations are normal.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="when-to-use-this-approach-and-when-not-to"&gt;When to use this approach (and when not to)
&lt;/h2&gt;&lt;p&gt;Use this when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need Entra identity operations not fully covered by current module support&lt;/li&gt;
&lt;li&gt;You want to keep deployments inside a Bicep-first pipeline&lt;/li&gt;
&lt;li&gt;You need deterministic, repeatable setup logic in CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid this when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native module support exists and is stable for your exact scenario&lt;/li&gt;
&lt;li&gt;You can decouple identity bootstrap from infrastructure deployment&lt;/li&gt;
&lt;li&gt;Your security team requires a dedicated identity provisioning workflow/toolchain&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="wrap-up"&gt;Wrap up
&lt;/h2&gt;&lt;p&gt;Until full module support lands for every Entra scenario, &lt;code&gt;deploymentScripts&lt;/code&gt; is a practical bridge.&lt;/p&gt;
&lt;p&gt;You get to keep the benefits of IaC while still handling real-world identity provisioning requirements—like creating Entra security groups and assigning Entra roles—without manual portal steps.&lt;/p&gt;
&lt;p&gt;If you’re already using Bicep in production, this pattern is worth adding to your toolbox.&lt;/p&gt;
&lt;p&gt;In a follow-up post, I’ll share a complete reusable module pattern (parameters, script file layout, and CI validation) so this can be dropped into any landing-zone deployment.&lt;/p&gt;</description></item></channel></rss>