<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Kiprin</title>
    <description>The latest articles on DEV Community by Roman Kiprin (@rokicool).</description>
    <link>https://dev.to/rokicool</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F811090%2F8658074f-318a-4af5-98ee-e355b1dbe4aa.jpg</url>
      <title>DEV Community: Roman Kiprin</title>
      <link>https://dev.to/rokicool</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rokicool"/>
    <language>en</language>
    <item>
      <title>Code review with private LLM? In pipeline? Simple!</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Tue, 16 Dec 2025 02:57:46 +0000</pubDate>
      <link>https://dev.to/rokicool/code-review-with-private-llm-in-pipeline-simple-28ee</link>
      <guid>https://dev.to/rokicool/code-review-with-private-llm-in-pipeline-simple-28ee</guid>
      <description>&lt;p&gt;When you push code to GitLab and plan to merge it into the main branch, having your changes reviewed is essential. This review process helps ensure that you don't introduce poorly written code or create new security vulnerabilities. &lt;/p&gt;

&lt;p&gt;Imagine receiving immediate code analysis that provides valuable insights into your code quality and potential issues and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### Safety Index: 6/10&lt;/span&gt;

The code changes introduce significant functionality improvements but also present several areas of concern that affect maintainability and potential security.

&lt;span class="gu"&gt;### Detailed Suggestions&lt;/span&gt;

&lt;span class="ge"&gt;**&lt;/span&gt;1: Memory leak risk in conversation history management in app/main.py lines 44-69. The in-memor

.....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover, this solution is cost-effective and compatible with various technologies. Rather than utilizing third-party LLM services that may train their models on your data, you can deploy your own private Large Language Model.&lt;/p&gt;

&lt;p&gt;While this may seem challenging, such solutions are not only feasible in today's AI landscape, but are also remarkably affordable. I'll demonstrate how this can be accomplished.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab for code, Azure for LLM, opencode for managing code review
&lt;/h2&gt;

&lt;p&gt;Allow me to introduce the infrastructure. &lt;/p&gt;

&lt;p&gt;&lt;a href="//gitlab.com"&gt;GitLab.com&lt;/a&gt; - the project holder and manages repository and pipelines.&lt;br&gt;
&lt;a href="https://opencode.ai/" rel="noopener noreferrer"&gt;opencode&lt;/a&gt; - one of the best (and the most 'open') agentic command line code generation utility.&lt;br&gt;
&lt;a href="https://azure.microsoft.com/en-us/products/ai-foundry" rel="noopener noreferrer"&gt;Microsoft Foundry&lt;/a&gt; - SaaS/PaaS solution to privately run LLMs and perform a lot of other stuff.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project to gather all together. It is an LLM chat app, of course.
&lt;/h2&gt;

&lt;p&gt;To demonstrate the setup in a near-production environment, I developed an AI-driven application. This application serves primarily as a sandbox for testing various technologies, with no intended practical use beyond that purpose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7titmsiag6j3dqvrryzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7titmsiag6j3dqvrryzb.png" alt="chat.roki.cool app" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the application at: &lt;a href="http://chat.roki.cool/" rel="noopener noreferrer"&gt;chat.roki.cool&lt;/a&gt;. Please note that hosting costs approximately $30 per month, so it may be unavailable at the time you're viewing it.&lt;/p&gt;

&lt;p&gt;Don't worry! While the application is entertaining, it's not the main focus of this article.&lt;/p&gt;
&lt;h2&gt;
  
  
  The schema
&lt;/h2&gt;

&lt;p&gt;Below is the diagram of the complete project. However, this article focuses specifically on the portion related to implementing LLMs in GitLab pipelines.&lt;/p&gt;

&lt;p&gt;Therefore, we'll be examining only the relevant section of the diagram, which is clear. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88xhknarg0nxvj4numnr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88xhknarg0nxvj4numnr.png" alt="Pipeline diagram" width="800" height="765"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me start with the model, OK?&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;em&gt;gpt-oss-120b&lt;/em&gt; large language model
&lt;/h2&gt;

&lt;p&gt;Why &lt;strong&gt;gpt-oss-120b&lt;/strong&gt;? To be honest, you can chose whatever LLM you like from the list that Microsoft provides you with. There is a huge variety. Models &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-sold-directly-by-azure?view=foundry&amp;amp;tabs=global-standard-aoai%2Cstandard-chat-completions%2Cglobal-standard&amp;amp;pivots=azure-openai" rel="noopener noreferrer"&gt;by OpenAi&lt;/a&gt;. Best models, provided &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-sold-directly-by-azure?view=foundry&amp;amp;tabs=global-standard-aoai%2Cstandard-chat-completions%2Cglobal-standard&amp;amp;pivots=azure-direct-others" rel="noopener noreferrer"&gt;by partners, like DeepSeek, Grok, Kimi, ollama&lt;/a&gt;... &lt;/p&gt;

&lt;p&gt;And more partners, like (arguably the best models) from &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-from-partners?view=foundry" rel="noopener noreferrer"&gt;Anthropic: claude sonnet, claude opus (including 4.5!)&lt;/a&gt; and more!  If that is not enough - you can &lt;a href="https://learn.microsoft.com/en-us/azure/machine-learning/how-to-deploy-models-from-huggingface?view=azureml-api-2" rel="noopener noreferrer"&gt;deploy models from huggingface&lt;/a&gt;! However, I did not try that.&lt;/p&gt;

&lt;p&gt;For this implementation, I chose &lt;strong&gt;gpt-oss-120b&lt;/strong&gt; primarily because of its cost-effectiveness. It is &lt;a href="https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" rel="noopener noreferrer"&gt;about 10-30 times cheaper&lt;/a&gt; than leading models from premium vendors.&lt;/p&gt;

&lt;p&gt;While I wouldn't recommend gpt-oss-120b for code generation tasks, it excels at code analysis, making it an ideal choice for this specific use case. The decision was straightforward given these considerations. &lt;/p&gt;

&lt;p&gt;By any means, if you are interested in investing more, you are welcome - the technology stays the same.&lt;/p&gt;
&lt;h2&gt;
  
  
  Microsoft Foundry
&lt;/h2&gt;

&lt;p&gt;To be honest, Microsoft Foundry is huge. Just look &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-from-partners?view=foundry" rel="noopener noreferrer"&gt;at its documentation&lt;/a&gt;. And Microsoft tries to bring consistency with that "new" look and feel.  &lt;/p&gt;

&lt;p&gt;For our purposes, we'll be using it simply as a hosting environment to run our preferred LLMs. This is similar to how you might use your own local machine with substantial resources - though most of us don't have access to 196GB of memory and multiple GPUs at home, which is exactly why services like Microsoft Foundry are valuable.&lt;/p&gt;

&lt;p&gt;To run workloads on Microsoft Foundry, you'll need a standard Azure subscription. Here's what you'll need to set up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the Foundry service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pdyti1osgbeo859g1c1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pdyti1osgbeo859g1c1.png" alt="Azure Marketplace select Foundry" width="800" height="894"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Foundry project within Foundry service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj37bcdg4mdmr5kxm436.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj37bcdg4mdmr5kxm436.png" alt="Foundry new project" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Foundry portal and have full control over the Foundry provided services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdizf4fdcmcalirt6qnrf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdizf4fdcmcalirt6qnrf.png" alt="Goto Foundry Portal" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have your LLM endpoint and secret via Foundry home &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlpex6il1i9grkgak5m9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlpex6il1i9grkgak5m9.png" alt="Welcome to foundry" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or via Azure Portal interface: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjh78x3qut6k9ta26fmba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjh78x3qut6k9ta26fmba.png" alt="Endpoints via Azure Portal" width="800" height="923"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;... and you are good to go. OK, almost good to go. Let me save you a couple of hours on your research.&lt;/p&gt;
&lt;h2&gt;
  
  
  The LLM endpoint and secret
&lt;/h2&gt;

&lt;p&gt;That is, probably, the trickiest part of the story. Microsoft provides different endpoints for various LLMs, and you'll need to locate the specific URI for your chosen model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxcz5lvkg02jb94uhg3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxcz5lvkg02jb94uhg3s.png" alt="LLM endpoint" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, I found that opencode does not work with this endpoint. (At least I failed to make it working.) So, I did my testing and discovered, that &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;most of the models are available via &lt;em&gt;different&lt;/em&gt; endpoints simultaneously &lt;/li&gt;
&lt;li&gt;opencode works with any "openai-compatible" endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means your LLM endpoint should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;name_of_your_ms_foundry_service&amp;gt;.services.ai.azure.com/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, you have to setup LLM_ENDPOINT and LLM_KEY as CI/CD variables in your project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mjwg9c2n4my7ai0xksb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mjwg9c2n4my7ai0xksb.png" alt="GitLab CICD Variables" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will use them in the GitLab pipeline later on!&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitLab pipeline
&lt;/h2&gt;

&lt;p&gt;Not really. The entire pipeline definition is &lt;a href="https://gitlab.com/rokicool/test-opencode-agent/-/blob/main/.gitlab-ci.yml" rel="noopener noreferrer"&gt;available in my gitlab repository&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Here I will show you only the job, related to the opencode setup and execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;validate_job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22-slim&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Installing opencode"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install --global opencode-ai&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Installing glab"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export GITLAB_TOKEN=$GITLAB_TOKEN_OPENCODE&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get update --quiet &amp;amp;&amp;amp; apt-get install --yes curl wget gpg git &amp;amp;&amp;amp; rm --recursive --force /var/lib/apt/lists/*&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get install --yes glab jq&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Configuring glab"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.email "opencode@gitlab.com"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.name "Opencode"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITLAB_HOST=https://gitlab.com&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITLAB_TOKEN=$CI_JOB_TOKEN&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo $GITLAB_HOST&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Creating opencode auth configuration"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir --parents ~/.local/share/opencode&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;cat &amp;gt; ~/.local/share/opencode/opencode.json &amp;lt;&amp;lt; EOF&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"$schema": "https://opencode.ai/config.json",&lt;/span&gt;
        &lt;span class="s"&gt;"model": "back/gpt-oss-120b",&lt;/span&gt;
        &lt;span class="s"&gt;"provider": {&lt;/span&gt;
          &lt;span class="s"&gt;"back": {&lt;/span&gt;
            &lt;span class="s"&gt;"npm": "@ai-sdk/openai-compatible",&lt;/span&gt;
            &lt;span class="s"&gt;"options": {&lt;/span&gt;
              &lt;span class="s"&gt;"baseURL": "$LLM_ENDPOINT",&lt;/span&gt;
              &lt;span class="s"&gt;"apiKey": "$LLM_KEY"&lt;/span&gt;
            &lt;span class="s"&gt;},&lt;/span&gt;
            &lt;span class="s"&gt;"models": {&lt;/span&gt;
              &lt;span class="s"&gt;"gpt-oss-120b": {&lt;/span&gt;
                &lt;span class="s"&gt;"name": "gpt-oss-120b",&lt;/span&gt;
                &lt;span class="s"&gt;"tool_call": true,&lt;/span&gt;
                &lt;span class="s"&gt;"reasoning": true,&lt;/span&gt;
                &lt;span class="s"&gt;"limit": {&lt;/span&gt;
                  &lt;span class="s"&gt;"context": 128000,&lt;/span&gt;
                  &lt;span class="s"&gt;"output": 128000&lt;/span&gt;
                &lt;span class="s"&gt;},&lt;/span&gt;
                &lt;span class="s"&gt;"options": {&lt;/span&gt;
                  &lt;span class="s"&gt;"tools": true,&lt;/span&gt;
                  &lt;span class="s"&gt;"reasoning": true&lt;/span&gt;
                &lt;span class="s"&gt;}&lt;/span&gt;
              &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;EOF&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Running Opencode"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opencode -version&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PROMPT1=$(cat ./gitlab-ci.prompt1.md)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo -e "\n \n"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opencode run "$PROMPT1"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CURRENT_SAFETY_SCORE=$(cat ./smell_analysis.json | jq .safety_score)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Safety score $CURRENT_SAFETY_SCORE. It is supposed to be better than $SAFETY_SCORE to continute."&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;if [ $CURRENT_SAFETY_SCORE -le $SAFETY_SCORE ]; then&lt;/span&gt;
         &lt;span class="s"&gt;echo -e "The safety score of the changes is lower than our standards allow. This is a blocker. \n \n \033[31mYou shall not pass!!!\033[0m  \n \n"&lt;/span&gt;
         &lt;span class="s"&gt;exit 1&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll outline and explain each component of this process.&lt;/p&gt;

&lt;p&gt;First, we install opencode into a Docker image and set up the default Git configuration. This enables the LLM that opencode communicates with to access the Git repository, &lt;strong&gt;should the AI determine it's necessary&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Installing opencode"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install --global opencode-ai&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Installing glab"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export GITLAB_TOKEN=$GITLAB_TOKEN_OPENCODE&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get update --quiet &amp;amp;&amp;amp; apt-get install --yes curl wget gpg git &amp;amp;&amp;amp; rm --recursive --force /var/lib/apt/lists/*&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get install --yes glab jq&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Configuring glab"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.email "opencode@gitlab.com"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.name "Opencode"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITLAB_HOST=https://gitlab.com&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GITLAB_TOKEN=$CI_JOB_TOKEN&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo $GITLAB_HOST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll explain how we dynamically configure opencode. As I mentioned earlier, you need to provide the LLM_ENDPOINT and LLM_KEY variables for the pipeline to function properly. This job therefore requires both CI/CD variables to be properly initialized.&lt;/p&gt;

&lt;p&gt;The "model" property specifies the default model for opencode. It's composed of the provider name ("back" in this case) and the specific LLM name ("gpt-oss-120b").&lt;/p&gt;

&lt;p&gt;You can customize additional parameters such as allow reasoning or use tools. &lt;a href="https://opencode.ai/docs/config/" rel="noopener noreferrer"&gt;Here is the link to the official opencode config documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Creating opencode auth configuration"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir --parents ~/.local/share/opencode&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;cat &amp;gt; ~/.local/share/opencode/opencode.json &amp;lt;&amp;lt; EOF&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"$schema": "https://opencode.ai/config.json",&lt;/span&gt;
        &lt;span class="s"&gt;"model": "back/gpt-oss-120b",&lt;/span&gt;
        &lt;span class="s"&gt;"provider": {&lt;/span&gt;
          &lt;span class="s"&gt;"back": {&lt;/span&gt;
            &lt;span class="s"&gt;"npm": "@ai-sdk/openai-compatible",&lt;/span&gt;
            &lt;span class="s"&gt;"options": {&lt;/span&gt;
              &lt;span class="s"&gt;"baseURL": "$LLM_ENDPOINT",&lt;/span&gt;
              &lt;span class="s"&gt;"apiKey": "$LLM_KEY"&lt;/span&gt;
            &lt;span class="s"&gt;},&lt;/span&gt;
            &lt;span class="s"&gt;"models": {&lt;/span&gt;
              &lt;span class="s"&gt;"gpt-oss-120b": {&lt;/span&gt;
                &lt;span class="s"&gt;"name": "gpt-oss-120b",&lt;/span&gt;
                &lt;span class="s"&gt;"tool_call": true,&lt;/span&gt;
                &lt;span class="s"&gt;"reasoning": true,&lt;/span&gt;
                &lt;span class="s"&gt;"limit": {&lt;/span&gt;
                  &lt;span class="s"&gt;"context": 128000,&lt;/span&gt;
                  &lt;span class="s"&gt;"output": 128000&lt;/span&gt;
                &lt;span class="s"&gt;},&lt;/span&gt;
                &lt;span class="s"&gt;"options": {&lt;/span&gt;
                  &lt;span class="s"&gt;"tools": true,&lt;/span&gt;
                  &lt;span class="s"&gt;"reasoning": true&lt;/span&gt;
                &lt;span class="s"&gt;}&lt;/span&gt;
              &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this step, we execute a prompt from the &lt;a href="https://gitlab.com/rokicool/test-opencode-agent/-/blob/main/gitlab-ci.prompt1.md?ref_type=heads" rel="noopener noreferrer"&gt;gitlab-ci.prompt1.md&lt;/a&gt; file using opencode. More about the prompt in the following section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Running Opencode"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opencode -version&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PROMPT1=$(cat ./gitlab-ci.prompt1.md)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo -e "\n \n"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opencode run "$PROMPT1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we analyze the ./smell_analysis.json file and determine whether we should proceed or if the 'safety_score' is too low to allow the code to be merged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CURRENT_SAFETY_SCORE=$(cat ./smell_analysis.json | jq .safety_score)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Safety score $CURRENT_SAFETY_SCORE. It is supposed to be better than $SAFETY_SCORE to continute."&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;if [ $CURRENT_SAFETY_SCORE -le $SAFETY_SCORE ]; then&lt;/span&gt;
         &lt;span class="s"&gt;echo -e "The safety score of the changes is lower than our standards allow. This is a blocker. \n \n \033[31mYou shall not pass!!!\033[0m  \n \n"&lt;/span&gt;
         &lt;span class="s"&gt;exit 1&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final stage of this pipeline job requires the ./smell_analysis.json file to be present. This file should be generated by opencode, but the question is: how does that happen?&lt;/p&gt;

&lt;p&gt;Here comes...&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;

&lt;p&gt;The key lies in the prompt. This is what opencode executes to perform code analysis and generate recommendations, including a 'safety_score' that's formatted for easy parsing with jq.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Code Review Analysis Prompt for ClaudeCode&lt;/span&gt;

&lt;span class="gu"&gt;## Objective&lt;/span&gt;
You are an AI assistant tasked with evaluating code changes in a GitLab environment to identify code quality issues and assess their impact on the codebase.

&lt;span class="gu"&gt;## Primary Tasks&lt;/span&gt;

&lt;span class="gu"&gt;### 1. Code Difference Analysis&lt;/span&gt;
Analyze the output of &lt;span class="sb"&gt;`git diff main`&lt;/span&gt; to identify:
&lt;span class="p"&gt;-&lt;/span&gt; Code quality issues and "code smells"
&lt;span class="p"&gt;-&lt;/span&gt; Potential problems that changes could introduce
&lt;span class="p"&gt;-&lt;/span&gt; Risks to existing workflows or data flows
&lt;span class="p"&gt;-&lt;/span&gt; Complications for future codebase maintenance

&lt;span class="gu"&gt;### 2. Safety Index Calculation&lt;/span&gt;
Compute a &lt;span class="gs"&gt;**safety index**&lt;/span&gt; on a scale of &lt;span class="gs"&gt;**0 to 10**&lt;/span&gt;:
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**10**&lt;/span&gt;: Exemplary code with no identifiable issues
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**7-9**&lt;/span&gt;: Good code with minor improvements needed
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**4-6**&lt;/span&gt;: Acceptable code with moderate concerns
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**1-3**&lt;/span&gt;: Poor code with significant issues
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**0**&lt;/span&gt;: Critical problems, violations, or data risks present

&lt;span class="gu"&gt;### 3. Actionable Recommendations&lt;/span&gt;
Generate a comprehensive list of specific, actionable suggestions to improve the safety score.

&lt;span class="gu"&gt;## Output Requirements&lt;/span&gt;

&lt;span class="gu"&gt;### Documentation Quality&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Provide &lt;span class="gs"&gt;**thoughtful, detailed explanations**&lt;/span&gt; for every suggestion
&lt;span class="p"&gt;-&lt;/span&gt; Each suggestion must include:
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**At least 3 sentences**&lt;/span&gt; of explanation
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Specific file references**&lt;/span&gt; whenever possible
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="gs"&gt;**Line number citations**&lt;/span&gt; where applicable
&lt;span class="p"&gt;  -&lt;/span&gt; Clear reasoning for why the change matters&lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gu"&gt;### Screen Output&lt;/span&gt;

Print the analysis results to the standard output using Markdown text formatting. Make the text easy readable in a log file. Add two empty lines before and after the analysis output. 

&lt;span class="gu"&gt;### File Output&lt;/span&gt;
Save the analysis results to a local file named &lt;span class="sb"&gt;`smell_analysis.json`&lt;/span&gt; in the following JSON format:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"safety_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&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 class="s2"&gt;"1: Remove all instances of hardcoded configuration values in src/config.py lines 15-23. These values should be moved to environment variables or a configuration file to improve security and deployment flexibility. Hardcoded values make it difficult to maintain different configurations across environments and pose security risks."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"2: Ensure that all new dependencies in requirements.txt are properly versioned and pinned to specific versions. Using unpinned or loosely versioned dependencies can lead to unexpected behavior when packages are updated. This is particularly important for the newly added 'requests' and 'pandas' libraries."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"3: Add comprehensive unit tests for all new code in the user_service.py module lines 45-120. The new authentication logic introduces critical security functionality that must be thoroughly tested. Include tests for edge cases, error handling, and security scenarios to prevent vulnerabilities."&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Important Notes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Focus on substantive issues that affect code quality, security, maintainability, or performance
&lt;span class="p"&gt;-&lt;/span&gt; Prioritize suggestions by impact (most critical first)
&lt;span class="p"&gt;-&lt;/span&gt; Be specific and constructive in all feedback
&lt;span class="p"&gt;-&lt;/span&gt; Reference concrete examples from the diff whenever possible
&lt;span class="p"&gt;-&lt;/span&gt; Before executing any bash tool command show the command you are going to execute

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The output
&lt;/h2&gt;

&lt;p&gt;That is how it looks when the pipeline fails: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0c713n3tk2gqteylo8b4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0c713n3tk2gqteylo8b4.png" alt="You shall not pass" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a real example of the output when no serious issues were found in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;
&lt;span class="gu"&gt;### Safety Index: 6/10&lt;/span&gt;

The code changes introduce significant functionality improvements but also present several areas of concern that affect maintainability and potential security.

&lt;span class="gu"&gt;### Detailed Suggestions&lt;/span&gt;

&lt;span class="gs"&gt;**1: Memory leak risk in conversation history management in app/main.py lines 44-69. The in-memory conversation_history dictionary will grow indefinitely as new session IDs are created, potentially consuming all available memory over time. This should be replaced with a proper database or implement session expiration/cleanup mechanisms to prevent memory exhaustion in production environments.**&lt;/span&gt;

&lt;span class="gs"&gt;**2: Insufficient input validation and sanitization in app/main.py lines 118-119. The HTML tag removal using regex `re.sub(r"&amp;lt;[^&amp;gt;]+&amp;gt;", "", message)` is inadequate for preventing XSS attacks and could miss malicious content. This should be replaced with a proper HTML sanitization library like bleach or html-sanitizer that provides comprehensive XSS protection.**&lt;/span&gt;

&lt;span class="gs"&gt;**3: Hardcoded session management limitations in app/main.py lines 167-170. The history limit of 13 messages is arbitrary and may not suit all use cases or model contexts. This should be configurable through environment variables and include proper validation to ensure the limit doesn't exceed the model's context window capabilities.**&lt;/span&gt;

&lt;span class="gs"&gt;*** skipping for clarity **&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt; 

&lt;span class="gs"&gt;**8: Missing rate limiting per session in app/main.py. The current rate limiting is only IP-based, which could be bypassed by using different IPs or proxy networks. This should be supplemented with session-based rate limiting to prevent abuse even when IP addresses change.**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most interesting part is that you can copy-paste this analysis to your current coding agent and improve your posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://chat.roki.cool/" rel="noopener noreferrer"&gt;http://chat.roki.cool/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://opencode.ai/docs/config/" rel="noopener noreferrer"&gt;https://opencode.ai/docs/config/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-from-partners?view=foundry" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-from-partners?view=foundry&lt;/a&gt;&lt;br&gt;
&lt;a href="https://gitlab.com/rokicool/test-opencode-agent" rel="noopener noreferrer"&gt;https://gitlab.com/rokicool/test-opencode-agent&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PS
&lt;/h2&gt;

&lt;p&gt;Ideas, suggestions, comments? Don't hesitate! &lt;/p&gt;

&lt;p&gt;And by any means this code is not written in stone. Everything might be rewriten, improved or removed. &lt;/p&gt;

</description>
      <category>opencode</category>
      <category>gitlab</category>
      <category>microsoft</category>
      <category>llm</category>
    </item>
    <item>
      <title>HCP Terraform to Azure Cloud using OIDC</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Fri, 07 Mar 2025 00:30:58 +0000</pubDate>
      <link>https://dev.to/rokicool/hcp-terraform-to-azure-cloud-using-oidc-2i28</link>
      <guid>https://dev.to/rokicool/hcp-terraform-to-azure-cloud-using-oidc-2i28</guid>
      <description>&lt;h2&gt;
  
  
  The usecase
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0rrtikkztpk70s0kqq4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0rrtikkztpk70s0kqq4x.png" alt="The usecase" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need our HCP Terraform Workspace to deploy resources to Microsoft Azure without using secrets, tokens, passwords, or anything similar.&lt;/p&gt;

&lt;p&gt;Challenging? Not really. We just need to configure OpenID Connect. Continue reading if you need details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Obviously, we need to obtain: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Azure subscription (with the Owner role access), &lt;/li&gt;
&lt;li&gt;the HCP Terraform Workspace, &lt;/li&gt;
&lt;li&gt;a GitLab (or any other supported by HCP Terraform VCS) project with the Terraform code. (However, the GitLab integration is slightly outside of the scope of this post.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's pretend we have HCP Terraform workspace&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrsd8h9j67hgbmhccpm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrsd8h9j67hgbmhccpm1.png" alt="HCP Terraform Workspace" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is crucial to know &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the name of the organization: &lt;code&gt;rokicool-tf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the project: &lt;code&gt;dev-to&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the name of the workspace: &lt;code&gt;azure-automation-account-dev&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use these identifiers later.&lt;/p&gt;

&lt;p&gt;And imagine we have a new shiny Azure subscription:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyudfaqi8qgbw6l2v7uj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyudfaqi8qgbw6l2v7uj.png" alt="Azure subscription" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to know the Azure subscription ID and the Tenant ID. If you are not sure if your subscription is located in the root management group or not, just open the Entra ID interface in the Azure Portal. The first thing you'll see will be the Tenant ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwdr1befgao2shd9trq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwdr1befgao2shd9trq1.png" alt="Tenant" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscription ID &lt;code&gt;971307eb-329d-47a2-b3f2-d424f4da5461&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tenant ID &lt;code&gt;86abb60f-b63e-475e-85dd-9fa6e1d9f754&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Service Principal to represent HCP Terraform Workspace
&lt;/h2&gt;

&lt;p&gt;Our HCP Terraform Workspace should have identity to access the Azure subscription to deploy/destroy resources.&lt;/p&gt;

&lt;p&gt;Let's create one. &lt;/p&gt;

&lt;p&gt;Azure portal, Entra ID, App Registrations, + New Registration...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqbr45quumlms6rcj07q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqbr45quumlms6rcj07q.png" alt="New Registration" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entering it's name... &lt;code&gt;sp-azure-automation-account-deployer-tfc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1xr9wix27497ezbfsc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1xr9wix27497ezbfsc3.png" alt="Add the name" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here it is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncx5c0r2ytfo9glccdv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncx5c0r2ytfo9glccdv9.png" alt="Service Principal is ready" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only significant part is...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Client ID &lt;code&gt;348c0fdc-b20f-4fe7-a79a-872068ff22fd&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Give it a role!
&lt;/h2&gt;

&lt;p&gt;The identity cannot do much (access Entra ID with read permissions) by default. You need to assign it a role on the necessary scope.&lt;/p&gt;

&lt;p&gt;The details about the possible role and the correct scope are far beyond this post. So, let's say we want to give it Contributor on the scope of subscription.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqqtvxfu23ykgzzyqhy5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqqtvxfu23ykgzzyqhy5.gif" alt="Assign a role" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Federated Credentials for the Service Principal
&lt;/h2&gt;

&lt;p&gt;Everything before this point was a preparation. Now the magic comes!&lt;/p&gt;

&lt;p&gt;HashiCorp provides a very comprehensive document &lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/azure-configuration" rel="noopener noreferrer"&gt;Use dynamic credentials with the Azure provider&lt;/a&gt; which explains a lot. Especially &lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/azure-configuration#configure-microsoft-entra-id-application-to-trust-a-generic-issuer" rel="noopener noreferrer"&gt;Configure Microsoft Entra ID Application to Trust a Generic Issuer&lt;/a&gt; section. &lt;/p&gt;

&lt;p&gt;However, it's outdated a little! We are going to use one flexible federated credential instead of two static ones (as the official document suggests).&lt;/p&gt;

&lt;p&gt;First, we need to build a string that is called &lt;code&gt;Subject identifier&lt;/code&gt;. The template is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;organization:&amp;lt;my-org-name&amp;gt;:project:&amp;lt;my-project-name&amp;gt;:workspace:&amp;lt;my-workspace-name&amp;gt;:run_phase:plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our example, the string will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;organization:rokicool-tf:project:dev-to:workspace:azure-automation-account-dev:run_phase:plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;organization:rokicool-tf:project:dev-to:workspace:azure-automation-account-dev:run_phase:apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... for the second federated identity credentials.&lt;/p&gt;

&lt;p&gt;But!&lt;/p&gt;

&lt;p&gt;We are going to use &lt;em&gt;Claims matching expression&lt;/em&gt; instead of the &lt;em&gt;Explicit subject identifier&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;Therefore, our string will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claims['sub'] matches 'organization:rokicool-tf:project:dev-to:workspace:azure-automation-account-dev:run_phase:*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you see? It is an expression, and the "*" symbol matches both &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; phases!&lt;/p&gt;

&lt;p&gt;The rest of the fields are described in the mentioned document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Federated credential scenario: Must be set to &lt;code&gt;Other issuer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Issuer: The address of HCP Terraform (e.g., &lt;code&gt;https://app.terraform.io&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Name: A name for the federated credential, such as `tfc-azure-automation-account-dev'&lt;/li&gt;
&lt;li&gt;Audience: &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt; by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's put them in the correct place.&lt;/p&gt;

&lt;p&gt;Portal - Entra ID - App registrations - search for you sp - Manage/Certificates &amp;amp; secrets - Federated credentials - +Add credential&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp31t3spcmadaj98adb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp31t3spcmadaj98adb5.png" alt="Add federated credentials" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what you should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6qriyp23gzs1tkkhvx5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6qriyp23gzs1tkkhvx5.png" alt="Federated Credentials details" width="800" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Believe me, I am tired too! But bear with me for one more step!&lt;/p&gt;

&lt;h2&gt;
  
  
  HCP Terraform Workspace Variables
&lt;/h2&gt;

&lt;p&gt;The Azure and Entra ID sides are ready. The next thing is to define several variables for your HCP Terraform workspace to inform Terraform Cloud that you want it using OIDC.&lt;/p&gt;

&lt;p&gt;But before doing this, I need to mention another caveat that HashiCorp mentions in their &lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/azure-configuration#configure-the-azurerm-or-microsoft-entra-id-provider" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the quote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure that you’re passing values for the subscription_id and tenant_id arguments into the provider configuration block or setting the ARM_SUBSCRIPTION_ID and ARM_TENANT_ID variables in your workspace.&lt;/p&gt;

&lt;p&gt;Make sure that you’re not setting values for client_id, use_oidc, or oidc_token in the provider or setting any of ARM_CLIENT_ID, ARM_USE_OIDC, ARM_OIDC_TOKEN.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, simply speaking, your &lt;code&gt;provider&lt;/code&gt; block should look like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;provider "azurerm" {&lt;br&gt;
 features {}&lt;/p&gt;

&lt;p&gt;tenant_id       = var.tenant_id&lt;br&gt;
 subscription_id = var.subscription_id&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Unfortunately, dev.to stopped supporting my &lt;code&gt;code block&lt;/code&gt;, so I had to use quotes above.)&lt;/p&gt;

&lt;p&gt;If it is, we are ready to set up the variables!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TFC_AZURE_PROVIDER_AUTH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enable OIDC authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TFC_AZURE_RUN_CLIENT_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;348c0fdc-b20f-4fe7-a79a-872068ff22fd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The Client ID of the Service Principal you created earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tenant_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;86abb60f-b63e-475e-85dd-9fa6e1d9f754&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Entra ID Tenant ID, which you use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subscription_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;971307eb-329d-47a2-b3f2-d424f4da5461&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ID of the Azure subscription you use&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is how it looks within HCP Terraform Workspace interface:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvv284fo7q7zn852mbfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvv284fo7q7zn852mbfw.png" alt="Workspace variables" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And... Congrats! You have done it!&lt;/p&gt;

&lt;p&gt;Now every push to your GitLab dev-tfc branch will initiate an HCP Terraform Workspace pipeline run, which will definitely deploy your resources.&lt;/p&gt;

&lt;p&gt;And an unexpectedly nice thing... you will see an additional stage in your GitLab pipeline that will show the status of the HCP Terraform pipeline run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyew2fcplbbgxsevbwqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyew2fcplbbgxsevbwqq.png" alt="GitLab pipeline run" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What? You did not set up the VCS GitLab integration? Sorry, bro. Next time!&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;That is how success looks on the HCP Terraform side:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii30e7yv7to9ufgqgurk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii30e7yv7to9ufgqgurk.png" alt="HCP Terraform after success" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the resource in Azure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfeu14aje9grk9pvgw1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfeu14aje9grk9pvgw1v.png" alt="Azure deployment success" width="800" height="844"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful documents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/azure-configuration" rel="noopener noreferrer"&gt;Use dynamic credentials with the Azure provider&lt;/a&gt;&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-flexible-federated-identity-credentials?tabs=gitlab" rel="noopener noreferrer"&gt;Flexible federated identity credentials (preview)&lt;/a&gt;&lt;/p&gt;




&lt;h5&gt;
  
  
  About identities and the services. For those with harmful intentions.
&lt;/h5&gt;

&lt;p&gt;I am absolutely sure that knowledge of the IDs of Tenant, Subscription, and even Service Principal will not help to get access to this infrastructure.&lt;/p&gt;

&lt;p&gt;Despite that, I usually delete the identities before publishing my post. So, don't bother.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>oidc</category>
    </item>
    <item>
      <title>Wildcards in Federated Credentials Entra ID (OIDC)</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sun, 29 Dec 2024 02:44:07 +0000</pubDate>
      <link>https://dev.to/rokicool/wildcards-in-federated-credentials-entra-id-oidc-d7e</link>
      <guid>https://dev.to/rokicool/wildcards-in-federated-credentials-entra-id-oidc-d7e</guid>
      <description>&lt;p&gt;That is a gorgeous present for Christmas and New Year from Microsoft! &lt;/p&gt;

&lt;p&gt;As of the end of 2024, Microsoft Entra ID started the public preview of the new feature Federated Credentials! &lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-flexible-federated-identity-credentials?tabs=gitlab" rel="noopener noreferrer"&gt;the official Microsoft documentation&lt;/a&gt; about that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi5psbqk313vo14imaur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi5psbqk313vo14imaur.png" alt=" " width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Federated Credentials and why is it important?
&lt;/h3&gt;

&lt;p&gt;That is a set of properties and technology that allow to define which external security credentials can be trusted.&lt;/p&gt;

&lt;p&gt;Simple!&lt;/p&gt;

&lt;p&gt;In Entra ID, you can create a Security Principal identity. Then you can assign this identity with rights: Roles or Permissions within both Entra ID and Azure Cloud. &lt;/p&gt;

&lt;p&gt;Any outside service that wants to use the identity must provide credentials. Secrets are the easiest way to do it, but they are also the least safe.&lt;/p&gt;

&lt;p&gt;Entra ID supports secretless authentication via Federated Credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, we can create identities and use them without secrets, right?
&lt;/h3&gt;

&lt;p&gt;Precisely! I wrote an article &lt;a href="https://dev.to/rokicool/gitlab-azure-opentofu-and-no-secrets-38o6"&gt;GitLab, Azure, OpenTofu, and NO secrets!&lt;/a&gt; about that.&lt;/p&gt;

&lt;p&gt;The most important thing is that you can define a &lt;code&gt;subject&lt;/code&gt; field with precise reference to external identity. And that will allow your external identity to associate itself with identity in Entra ID. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjx89bdqnahenprneehp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjx89bdqnahenprneehp2.png" alt=" " width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That subject definition allows a GitLab pipeline from the &lt;code&gt;rokicool/gitlab-azure-oidc-opentofu&lt;/code&gt; project that is executed from the &lt;code&gt;main&lt;/code&gt; branch to access our Entra ID identity.&lt;/p&gt;

&lt;p&gt;Can you see the name of the project and the branch name in the &lt;code&gt;subject&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;project_path:rokicool/gitlab-azure-oidc-opentofu:ref_type:branch:ref:main&lt;/code&gt;&lt;br&gt;
?&lt;/p&gt;

&lt;p&gt;Don't be scared. If you feel overwhelmed, just look over the &lt;a href="https://dev.to/rokicool/gitlab-azure-opentofu-and-no-secrets-38o6"&gt;mentioned article&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  OK. That Federated Credentials thing has been here for years. That is the fuss now?
&lt;/h3&gt;

&lt;p&gt;We are close! Bear with me! &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;subject&lt;/code&gt; of the Federated Credentials requires explicit, exact reference.&lt;/p&gt;

&lt;p&gt;That works perfectly with the &lt;code&gt;main&lt;/code&gt; branch! Your pipeline from the main branch can authenticate itself easily. &lt;/p&gt;

&lt;p&gt;What if you need to authenticate a pipeline from a feature branch?&lt;/p&gt;

&lt;p&gt;Here is the use case. Your developer works on a feature AAA and creates a branch 'feat-JIRA-AAA'. How to authenticate a GitLab pipeline in Entra ID using Federated Credentials? And remember that Entra ID supports only 20 federated credentials per identity.&lt;/p&gt;

&lt;p&gt;I can hear your deep sights. &lt;/p&gt;
&lt;h3&gt;
  
  
  Rejoice! Here are wildcards now!
&lt;/h3&gt;

&lt;p&gt;Instead of an explicit subject, you can define a matching expression.&lt;/p&gt;

&lt;p&gt;In our example, it will be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claims['sub'] matches 'project_path:rokicool/gitlab-azure-oidc-opentofu:ref_type:branch:ref:feat-JIRA-*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can you see that the name of the branch is dynamic? So every brach in this project whose name starts with &lt;code&gt;feat-JIRA-&lt;/code&gt; will be authenticated! Using one record in the Federated Credentials! &lt;/p&gt;

&lt;p&gt;Isn't it cool?&lt;/p&gt;

&lt;h3&gt;
  
  
  Complicaitons
&lt;/h3&gt;

&lt;p&gt;Wildcards might be tricky, and they might provide access when you don't expect it. That's your responsibility now.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AUD field. GitLab and Microsoft have different vision
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;audiences&lt;/code&gt; field is part of the Federated Credentials and OIDC protocol. &lt;/p&gt;

&lt;p&gt;Microsoft &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-flexible-federated-identity-credentials?tabs=github#set-up-federated-identity-credentials-through-microsoft-graph" rel="noopener noreferrer"&gt;claims&lt;/a&gt; that &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This field is mandatory and should be set to &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt; for Microsoft Entra ID&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the same time, GitLab always puts &lt;code&gt;https://gitlab.com&lt;/code&gt; to this field.&lt;/p&gt;

&lt;p&gt;But. You can write &lt;code&gt;https://gitlab.com&lt;/code&gt; there, and your authentication will work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvzf8nq2z3cpbs3eeqf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvzf8nq2z3cpbs3eeqf3.png" alt=" " width="800" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using command line instead of the Portal
&lt;/h3&gt;

&lt;p&gt;Just as it was written in the Microsoft documentation, you can use the &lt;code&gt;az rest&lt;/code&gt; command to create the Federated Credentials.&lt;/p&gt;

&lt;p&gt;Of course, you should be authenticated as owner of the service principal or higher.&lt;/p&gt;

&lt;p&gt;Here is the example that worked for me perfectly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export OBJECT_ID=&amp;lt;OBJECT_ID (not APP_ID!!!) of your applicaiton or Service Principal&amp;gt;

az rest --method post \
    --url https://graph.microsoft.com/beta/applications/$OBJECT_ID/federatedIdentityCredentials \
    --body "{'name': 'FlexFic2', 'issuer': 'https://gitlab.com', 'audiences': ['https://gitlab.com'], 'claimsMatchingExpression': {'value': 'claims[\'sub\'] matches \'project_path:rokicool/gitlab-azure-oidc-opentofu:ref_type:branch:ref:feat-JIRA-*\'', 'languageVersion': 1}}"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good luck!&lt;/p&gt;

</description>
      <category>entraid</category>
      <category>azure</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Flex Consumption is not cheap (when in private VNET)</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Thu, 19 Dec 2024 02:08:11 +0000</pubDate>
      <link>https://dev.to/rokicool/flex-consumption-is-not-cheap-when-in-private-vnet-1m5f</link>
      <guid>https://dev.to/rokicool/flex-consumption-is-not-cheap-when-in-private-vnet-1m5f</guid>
      <description>&lt;p&gt;I have been working with Flex Consumption plan-based Azure Function Apps for several months and was super enthusiastic about the value.&lt;/p&gt;

&lt;p&gt;The Flex Consumption plan produces stable funding for script-defined automation and &lt;a href="https://azure.microsoft.com/en-us/pricing/details/functions/" rel="noopener noreferrer"&gt;costs pennies&lt;/a&gt;. It allows integration with your private VNET and could be used within a private network environment.&lt;/p&gt;

&lt;p&gt;Is it really that good?&lt;/p&gt;

&lt;p&gt;Unfortunately, there is no direct answer, and let me explain why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Function App uses Storage account
&lt;/h2&gt;

&lt;p&gt;Yep. Every Azure Function App utilizes the Azure Storage account to (ha-ha) function. The code of the Function App is located on the Storage account and should be available to the Azure Function App at every moment.&lt;/p&gt;

&lt;p&gt;Which is fine. The price of the Storage accounts is reasonable and depends on the amount of data. And that could not be a lot. So the problem is not the Storage account itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Private Endpoints are the problem!
&lt;/h2&gt;

&lt;p&gt;Again, when you run your Azure Function App in private, that literally means all the services must run in private and cannot be accessed from the Internet.&lt;/p&gt;

&lt;p&gt;As I explained in the previous articles (&lt;a href="https://dev.to/rokicool/azure-function-app-flex-conumption-in-private-vnet-via-iac-1j62"&gt;Azure Function App (Flex Consumption) in private VNET via IaC&lt;/a&gt;), you have to build an internal connection between the Azure Function App and the Azure Storage account. &lt;/p&gt;

&lt;p&gt;How? You have to setup (at least) two private endpoints for the &lt;code&gt;blob&lt;/code&gt; and &lt;code&gt;file&lt;/code&gt; services of the Storage account. It is not difficult, but there are some details that must be addressed. That is precisely the point of the mentioned article.&lt;/p&gt;

&lt;p&gt;Every private endpoint is billed for time. It costs 1 cent per hour. Which seems to be close to nothing... But...&lt;/p&gt;

&lt;p&gt;1 cent per hour sums to about $7.2 per month. And there are two of them, so ~$15 per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here comes Durable Functions stuff
&lt;/h2&gt;

&lt;p&gt;What Durable Functions? Yep. That is another really great technology that deserves much longer article. &lt;/p&gt;

&lt;p&gt;Basically, Durable Functions fix the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-scale#timeout" rel="noopener noreferrer"&gt;natural limitations of the Azure Functions&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;For example, any HTTP-triggered function must return something within 230 seconds. And what if you need more than 240 seconds to gather the results? Or, the grace period of Azure Function is 60 minutes. What if you need more time to execute your automation?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=in-process%2Cnodejs-v3%2Cv1-model&amp;amp;pivots=powershell" rel="noopener noreferrer"&gt;Durable Functions&lt;/a&gt; resolve that. They bring the 'context' term into Azure Functions. That gives the possibility of the longer run, control of the run, statuses of the run, and more good things.&lt;/p&gt;

&lt;h2&gt;
  
  
  But how is that related to this post?
&lt;/h2&gt;

&lt;p&gt;Directly! Durable Functions libraries use the &lt;code&gt;queue&lt;/code&gt; and the &lt;code&gt;table&lt;/code&gt; services of the Azure Storage account! &lt;/p&gt;

&lt;p&gt;From our ("Azure Function in the Private") standpoint, that means we have to create, support, and pay for two more private endpoints for these services! &lt;/p&gt;

&lt;h2&gt;
  
  
  Final calculation
&lt;/h2&gt;

&lt;p&gt;So, these four private endpoints will cost you ~$7.2 x 4 = ~$30 per month.&lt;/p&gt;

&lt;p&gt;Which is still very reasonable, but not cheap.&lt;/p&gt;

&lt;p&gt;And don't forget that you probably need to support at least two environments: &lt;code&gt;DEV&lt;/code&gt; and &lt;code&gt;PROD&lt;/code&gt;. Which makes it $60.&lt;/p&gt;

&lt;h2&gt;
  
  
  In case you are not afraid
&lt;/h2&gt;

&lt;p&gt;It is still a very robust solution. &lt;/p&gt;

&lt;p&gt;And in this 🦊GitLab repo (&lt;a href="https://gitlab.com/rokicool/azure-function-app" rel="noopener noreferrer"&gt;azure-function-app&lt;/a&gt;), I created an end-to-end automated solution that deploys two infrastructures, builds the PowerShell-based Azure Function App, and deploys it to your private VNET. &lt;/p&gt;

&lt;p&gt;And yes, the Durable Functions are supported too! Just do not forget to set CI/CD variable AZURE_IS_DURABLE_FUNCTION to 'true'. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>azurefunctions</category>
      <category>flexconsumpiton</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Azure Function App (Flex Consumption) PowerShell Modules solution</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sat, 07 Dec 2024 23:49:02 +0000</pubDate>
      <link>https://dev.to/rokicool/azure-function-app-flex-consumption-powershell-modules-solution-1hfg</link>
      <guid>https://dev.to/rokicool/azure-function-app-flex-consumption-powershell-modules-solution-1hfg</guid>
      <description>&lt;h2&gt;
  
  
  Prewords
&lt;/h2&gt;

&lt;p&gt;In this article, I explain a technique that allows embedding PowerShell modules in Azure Function based on the Flex Consumption plan.&lt;/p&gt;

&lt;p&gt;The fully working example is available in my GitLab project &lt;a href="https://gitlab.com/rokicool/azure-function-app" rel="noopener noreferrer"&gt;azure-function-app&lt;/a&gt; which is devoted to the Flex Consumption plan Azure Functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the article
&lt;/h2&gt;

&lt;p&gt;I have to admit that PowerShell is not suitable for high-load applications. &lt;/p&gt;

&lt;p&gt;This scripting language is excellent for automating tasks. It offers numerous options for data management, API access, output, logging, error handling, and so much more.&lt;/p&gt;

&lt;p&gt;Evidently, the bare language does not support millions of possible operations. You need to use PowerShell modules.&lt;/p&gt;

&lt;p&gt;When you organize your PowerShell scripts within Azure Function Apps, things with PowerShell modules become complicated.&lt;/p&gt;

&lt;p&gt;Basically, you can use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Important-Module&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Import-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Important-Module&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it will work... but. You probably don't want to do it. Every Azure Function execution will trigger that operation. This implies that each execution will involve the download and unpacking of the modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  PowerShell Azure Function Apps and modules
&lt;/h2&gt;

&lt;p&gt;And that is why the Azure Function PowerShell template has file &lt;code&gt;requirements.psd1&lt;/code&gt; which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This file enables modules to be automatically managed by the Functions service.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# See https://aka.ms/functionsmanageddependency for additional information.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&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 class="c"&gt;# For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. &lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# To use the Az module in your function app, please uncomment the line below.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# 'Az' = '13.*'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is super useful—you only need to request the modules. The hidden engine will install and cache them.&lt;/p&gt;

&lt;p&gt;However, that is not exactly correct when you use the Flex Consumption Plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is with Flex Consumption?
&lt;/h2&gt;

&lt;p&gt;Flex Consumption Plan uses a different approach. Your function does not reserve any computing power; you only pay for executions and resources. This is an excellent feature! You don't pay for reservations. &lt;/p&gt;

&lt;p&gt;However, at the same time, your function loses the ability to cache modules. &lt;/p&gt;

&lt;p&gt;Should we return to dynamic installation of them? No! Of course not!&lt;/p&gt;

&lt;h2&gt;
  
  
  And what is the issue with PowerShell modules?
&lt;/h2&gt;

&lt;p&gt;Microsoft suggests that your PowerShell Azure Function should include the necessary modules in the Azure Function distribution package. &lt;/p&gt;

&lt;p&gt;That means both your function and the modules will be installed within the same filesystem in the backend Storage account.&lt;/p&gt;

&lt;p&gt;It is like'manual caching'. Everything is reachable by the Azure Function App. &lt;/p&gt;

&lt;p&gt;There is another problem, though. You have to include the modules in your function repository. And that is a manual process. &lt;/p&gt;

&lt;h2&gt;
  
  
  Possible solution is...
&lt;/h2&gt;

&lt;p&gt;That is why I wrote all of it. &lt;/p&gt;

&lt;p&gt;Why don't we use the pipeline that builds the Azure Function distribution package to automatically include all the necessary PowerShell modules?&lt;/p&gt;

&lt;p&gt;Here in my repository: &lt;a href="https://gitlab.com/rokicool/azure-function-app/-/tree/main/azure-function/Modules?ref_type=heads" rel="noopener noreferrer"&gt;rokicool/azure-function-app@gitlab&lt;/a&gt;, you can find an example.&lt;/p&gt;

&lt;p&gt;There is a &lt;code&gt;requiredModules.ps1&lt;/code&gt; file that defines the requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# requiredModules.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# List the modules that will be embedded into Azure Function App deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$requiredModules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="s2"&gt;"Az.Accounts"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.0.5"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there is a &lt;code&gt;installModules.ps1&lt;/code&gt; PowerShell script that is expected to be used in &lt;a href="https://gitlab.com/rokicool/azure-function-app/-/blob/main/.gitlab-ci.yml?ref_type=heads" rel="noopener noreferrer"&gt;the GitLab pipeline&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;build:
  stage: build
  image: 
    name: mcr.microsoft.com/azure-powershell:mariner-2
  script:
    - tdnf &lt;span class="nb"&gt;install &lt;/span&gt;zip &lt;span class="nt"&gt;-y&lt;/span&gt;
    - pwsh ./azure-function/Modules/installModules.ps1 &lt;span class="c"&gt;## &amp;lt;-- HERE&lt;/span&gt;
    &lt;span class="c"&gt;# Build the zip file with the code of the Azure Function&lt;/span&gt;
    - &lt;span class="nb"&gt;mkdir &lt;/span&gt;dist
    - &lt;span class="nb"&gt;cd&lt;/span&gt; ./azure-function
    - zip &lt;span class="nt"&gt;-r&lt;/span&gt; ../dist/azure-function.zip ./&lt;span class="k"&gt;*&lt;/span&gt;
  artifacts:
    paths:
      - dist/azure-function.zip
    name: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARM_AZFUNC_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;_artifact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, the distribution package includes all the requested modules. You just need to provide names and the versions.&lt;/p&gt;

&lt;p&gt;Let's step back and think!&lt;/p&gt;

&lt;p&gt;Instead of requesting and installing the PowerShell modules dynamically, this technique puts the modules in the package alone with your Azure Function scripts! &lt;/p&gt;

&lt;p&gt;It is even better from the reliability perspective. Your modules will be with your Azure function as long as your function exists.&lt;/p&gt;

&lt;p&gt;Win-Win.&lt;/p&gt;




&lt;p&gt;BTW. Since you spent your time on this article, you might be interested in the previous one that explains about &lt;a href="https://dev.to/rokicool/azure-function-app-flex-conumption-in-private-vnet-via-iac-1j62"&gt;Azure Function App (Flex Consumption) in private VNET via IaC&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>powershell</category>
      <category>azurefunctions</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Azure Function App (Flex Consumption) in private VNET via IaC</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sat, 30 Nov 2024 16:53:03 +0000</pubDate>
      <link>https://dev.to/rokicool/azure-function-app-flex-conumption-in-private-vnet-via-iac-1j62</link>
      <guid>https://dev.to/rokicool/azure-function-app-flex-conumption-in-private-vnet-via-iac-1j62</guid>
      <description>&lt;p&gt;A lot of technical terms in the name of the article, right? Let's dive into it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Azure Function App?
&lt;/h2&gt;

&lt;p&gt;It is not a serious question! Azure Function App is a fantastic serverless infrastructure that allows you to execute your code within a secure environment. &lt;/p&gt;

&lt;p&gt;What if you need to perform a maintenance task by schedule? Or execute a PowerShell script within your Azure subscripton to perform a specific operation? &lt;/p&gt;

&lt;p&gt;Azure Function App might perform it for you!&lt;/p&gt;

&lt;p&gt;There are challenges, of course. This article explains how to overcome them and have an automated pipeline that creates infrastructure and then builds and deploys Azure Function to the correct place!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why private VNET?
&lt;/h2&gt;

&lt;p&gt;That is the weirdest part. However, some companies prohibit the use of any Azure service with a publicly accessible interface!&lt;/p&gt;

&lt;p&gt;There will be more on that later in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Flex Consimption?
&lt;/h2&gt;

&lt;p&gt;Microsoft &lt;a href="https://azure.microsoft.com/en-us/pricing/details/functions/" rel="noopener noreferrer"&gt;provides several plans&lt;/a&gt; for the Azure Function Apps. They are reasonable. &lt;/p&gt;

&lt;p&gt;If you have heavy traffic and a lot of executions, your choice is the Premium Plan: about $200 per month... &lt;/p&gt;

&lt;p&gt;What if you need to execute your function only several times per day? &lt;/p&gt;

&lt;p&gt;So, with all possible options, we don't have a choice. Only Flex Consumption costs about $3 a month (with less than 1000 executions, remember?) And a Flex Consumption Plan-based App might be integrated into a VNET.&lt;/p&gt;

&lt;p&gt;2024.12 UPD: Unfortunately, not $3 but $30 per month. Here is the post about that &lt;a href="https://dev.to/rokicool/flex-consumption-is-not-cheap-when-in-private-vnet-1m5f"&gt;Flex Consumption is not cheap (when in private VNET)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some technical internals of Azure Function App
&lt;/h2&gt;

&lt;p&gt;To explain the challenge, I need to surface several technical details. Azure Function App is not exactly a separate service.&lt;/p&gt;

&lt;p&gt;The usual Azure Function App has to use other Azure services. Some of them are optional, and some of them are mandatory. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqozdt3nifkskb3xgpps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqozdt3nifkskb3xgpps.png" alt=" " width="800" height="901"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having an Azure Storage Account is a must. The Azure Function App uses the Storage Account to store the Azure Function Code. However, Azure Storage Account is a publicly available service! &lt;/p&gt;

&lt;p&gt;The Function App must be able to access the green circle on the next schema. But that means absolutely everyone (who has the permission, of course) can access it too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ufa9aa0d8jfny8e96oz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ufa9aa0d8jfny8e96oz.png" alt=" " width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we need to comply with the company's policy, we must turn off that green public interface of the Storage Account.&lt;/p&gt;

&lt;p&gt;But how does the Azure Function App reach the Storage Account for the function code?&lt;/p&gt;

&lt;p&gt;We should use a private VNET! That means a lot of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we should create Storage Account private endpoints for both Blob and File services;&lt;/li&gt;
&lt;li&gt;we should integrate Azure Function App with one of the subnets in private VNET;&lt;/li&gt;
&lt;li&gt;we should create DNS zone and DNS names that would allow Azure Function App to find out the private IP addresses of the Storage Account services;&lt;/li&gt;
&lt;li&gt;we should provide Azure Function App with the correct DEPLOYMENT_STORAGE_CONNECTION_STRING;&lt;/li&gt;
&lt;li&gt;we should allow Azure Function App identity to access the Storage Account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6e4aco3zm05rab9a5a8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6e4aco3zm05rab9a5a8a.png" alt=" " width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More details you can find in the official Microsoft document &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/configure-networking-how-to?tabs=portal" rel="noopener noreferrer"&gt;How to use a secured storage account with Azure Functions&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab pipeline
&lt;/h2&gt;

&lt;p&gt;A lot of technical things that must be performed for every Azure Function App deployment... So, that must be automated!&lt;/p&gt;

&lt;p&gt;Here is my GitLab project that deploys an Azure Function App based on the Consumption Flex plan: &lt;a href="https://gitlab.com/rokicool/azure-function-app" rel="noopener noreferrer"&gt;azure-function-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline first creates infrastructure according to all the steps mentioned above. Then, the pipeline deploys the code of the Azure Function to the infrastructure created on the first step.&lt;/p&gt;

&lt;p&gt;The same pipeline supports two environments: DEV and PROD. The plan is to push all changes to the &lt;code&gt;dev&lt;/code&gt; or &lt;code&gt;dev_bicep&lt;/code&gt; branches to the DEV environment. Any changes made to the &lt;code&gt;main&lt;/code&gt; branch will then be sent automatically to both the DEV and PROD environments.&lt;/p&gt;

&lt;p&gt;You might find more details in the &lt;a href="https://gitlab.com/rokicool/azure-function-app/-/blob/main/.gitlab-ci.yml?ref_type=heads" rel="noopener noreferrer"&gt;pipeline definition&lt;/a&gt; and the projects README.md file.&lt;/p&gt;

&lt;p&gt;BTW, this pipeline does not use secrets. The authentication is based on OIDC (Open ID Connect). I have written an article that provides a detailed explanation of this method: &lt;a href="https://dev.to/rokicool/gitlab-azure-opentofu-and-no-secrets-38o6"&gt;GitLab, Azure, OpenTofu, and NO secrets!&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppxqn18tcvl36qma52q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppxqn18tcvl36qma52q8.png" alt=" " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why was this article written?
&lt;/h2&gt;

&lt;p&gt;I tried to automate the Azure Function App (Flex Consumption Plan) deployment behind firewalls using Terraform, and I failed. &lt;/p&gt;

&lt;p&gt;As of November 2024, Terraform providers don't support subnet integration with the Azure Function App based on the Flex Consumption Plan. &lt;/p&gt;

&lt;p&gt;You can find my Terraform-based failing deployment in the &lt;code&gt;dev&lt;/code&gt; branch of &lt;a href="https://gitlab.com/rokicool/azure-function-app/-/tree/dev/terraform?ref_type=heads" rel="noopener noreferrer"&gt;azure-function-app&lt;/a&gt; project. &lt;/p&gt;

&lt;p&gt;I really prefer to use Terraform, and as soon as it supports Flex Consumption network integration, I will try to revive this project.&lt;/p&gt;

&lt;p&gt;So, Terraform fails. But you can set up that Azure Function App configuration manually via the Portal. This implies that either the ARM Template or Bicep should function properly. I took the &lt;a href="https://github.com/Azure-Samples/azure-functions-flex-consumption-samples/tree/main/IaC/bicep" rel="noopener noreferrer"&gt;Microsoft Official Bicep Template&lt;/a&gt;, modified it to support network integration, and here it is, up and running.&lt;/p&gt;

&lt;p&gt;As a result, I spent so many hours setting everything up, so I decided to share the findings and working example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions, suggestions...
&lt;/h2&gt;

&lt;p&gt;As you may have noticed, there are numerous areas for improvement. Or I could make a mistake that slipped out of my consciousness. &lt;/p&gt;

&lt;p&gt;Please feel free to ask questions or share your suggestions. In fact, I need your feedback to understand where to dig next!&lt;/p&gt;

</description>
      <category>azure</category>
      <category>azurefunctions</category>
      <category>gitlab</category>
      <category>powershell</category>
    </item>
    <item>
      <title>GitLab, Azure, OpenTofu, and NO secrets!</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sat, 28 Sep 2024 21:51:51 +0000</pubDate>
      <link>https://dev.to/rokicool/gitlab-azure-opentofu-and-no-secrets-38o6</link>
      <guid>https://dev.to/rokicool/gitlab-azure-opentofu-and-no-secrets-38o6</guid>
      <description>&lt;p&gt;Believe it or not, you can deploy any resource using Terraform from the GitLab pipeline to the Azure cloud without any secrets!&lt;/p&gt;

&lt;p&gt;Imagine... No secrets, no maintenance, no KeyVaults, no hassle. &lt;/p&gt;

&lt;p&gt;Let me show you how!&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitLab project
&lt;/h2&gt;

&lt;p&gt;To demonstrate the technique I will use this project &lt;a href="https://gitlab.com/rokicool/gitlab-azure-oidc-opentofu" rel="noopener noreferrer"&gt;gitlab-azure-oidc-opentofu&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What does it do? It creates a Resource Group in one of the Azure Subscriptions. Is it too simple? Yes, it is. However, the main goal is to show how to set up an OpenID Connect (OIDC) authentication.&lt;/p&gt;

&lt;p&gt;Everything was set up and running. But as you read this, the Azure subscription has probably been canceled. Just in case somebody manages to merge terraform code that pushes 100 nodes Azure Kubernetes cluster :).&lt;/p&gt;

&lt;p&gt;And one more thing to mention.&lt;/p&gt;

&lt;p&gt;The OIDC technology works perfectly well with Terraform. In this project, however, I chose to use OpenTofu. It is integrated into the &lt;a href="https://gitlab.com/components/opentofu" rel="noopener noreferrer"&gt;component that is supported by GitLab&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;So, the pipeline in this project might be a good example of using the OpenTofu component along with GitLab Terraform state service. &lt;/p&gt;

&lt;p&gt;Let's start from the end... &lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/rokicool/gitlab-azure-oidc-opentofu/-/blob/main/terraform/providers.tf?ref_type=heads" rel="noopener noreferrer"&gt;Here is&lt;/a&gt; the full &lt;code&gt;providers.tf&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Have a look at the terraform code, shall we?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  features {}

  tenant_id       = var.tenant_id
  subscription_id = var.subscription_id
  client_id       = var.client_id

  use_oidc        = true
  oidc_token      = var.oidc_token
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All these provider settings are used for authentication. And the two last lines make Terraform (OpenTofu) use OIDC!&lt;/p&gt;

&lt;p&gt;Where are these variables set up and what values are used? Great question! &lt;/p&gt;

&lt;h2&gt;
  
  
  Gitlab pipeline code
&lt;/h2&gt;

&lt;p&gt;The variables are set up within the pipeline code! &lt;a href="https://gitlab.com/rokicool/gitlab-azure-oidc-opentofu/-/blob/main/.gitlab-ci.yml?ref_type=heads" rel="noopener noreferrer"&gt;Here is the link to the pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are several details that I would like to highlight. &lt;/p&gt;

&lt;h3&gt;
  
  
  OIDC token
&lt;/h3&gt;

&lt;p&gt;Yes. I lied in the title. There is a secret used in this technology. However, this secret is orchestrated by technology itself. OpenID Connect provides your code with a temporary OIDC token. This is precisely what is supposed to be put into the &lt;code&gt;oidc_token&lt;/code&gt; variable. But how?&lt;/p&gt;

&lt;p&gt;Simple! &lt;/p&gt;

&lt;p&gt;Every job that requires authentication has these several lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  id_tokens:
    TF_VAR_oidc_token:
      aud: https://gitlab.com
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines create an environment variable &lt;code&gt;TF_VAR_oidc_token&lt;/code&gt; and fill it with the token value.&lt;/p&gt;

&lt;p&gt;And that is all you need! From the Terraform code perspective. OK. Almost all you need.&lt;/p&gt;

&lt;p&gt;Since the environment variable is set as &lt;code&gt;TF_VAR_&amp;lt;something&amp;gt;&lt;/code&gt;, Terraform (OpenTofu) will take its value and set an internal variable with the &lt;code&gt;&amp;lt;something&amp;gt;&lt;/code&gt; name. It will be &lt;code&gt;oidc_token&lt;/code&gt; in our case. &lt;/p&gt;

&lt;p&gt;And this is exactly what our Terraform code expects! &lt;/p&gt;

&lt;h3&gt;
  
  
  Other Terraform (OpenTofu) variables
&lt;/h3&gt;

&lt;p&gt;Right. It is not enough to set up only a token. The code requires   &lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;subscription_id&lt;/code&gt;, and &lt;code&gt;client_id&lt;/code&gt;. Where were these set up?&lt;/p&gt;

&lt;p&gt;Simple. These are global variables of the pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variables:
...
  TF_VAR_client_id: $AZURE_CLIENT_ID
  TF_VAR_tenant_id: $AZURE_TENANT_ID
  TF_VAR_subscription_id: $AZURE_SUBSCRIPTION_ID
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here I use the same &lt;code&gt;TF_VAR_&amp;lt;something&amp;gt;&lt;/code&gt; notation. So, Terraform (OpenTofu) will take anything that goes after 'TF_VAR_' and create an internal variable with the &lt;code&gt;&amp;lt;something&amp;gt;&lt;/code&gt; name. &lt;/p&gt;

&lt;p&gt;For example, Terraform (OpenTofu) will take&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TF_VAR_client_id: $AZURE_CLIENT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;line, create variable &lt;code&gt;client_id&lt;/code&gt; and fill it with the value of &lt;code&gt;$AZURE_CLIENT_ID&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;Great! But, what are &lt;code&gt;$AZURE_CLIENT_ID&lt;/code&gt;, &lt;code&gt;$AZURE_TENANT_ID&lt;/code&gt;, &lt;code&gt;$AZURE_SUBSCRIPTION_ID&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;A very good question. Again! Ten points to Gryffindor! But I am from Slytherin! OK. Minus ten points to Slytherin!&lt;/p&gt;

&lt;p&gt;First, let me explain where these variables are set up. And then where their values are taken from.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitLab CI/CD Variables
&lt;/h3&gt;

&lt;p&gt;These are GitLab CI/CD Variables. To create them, you need to use the &lt;code&gt;Settings / CI/CI / Variables&lt;/code&gt; interface of GitLab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foi96a5qbqpujn2oo5en3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foi96a5qbqpujn2oo5en3.png" alt=" " width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You probably have another couple of questions. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why do you use CI/CD Variables?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not a real question. There is a concept of separation of &lt;br&gt;
data and logic. It's about 75 years old. :) &lt;/p&gt;

&lt;p&gt;Yes, it is possible to put these IDs directly into the code. That makes the code unique, and less flexible and limits the usage of the code. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;And why don't you create them in &lt;code&gt;TF_VAR_&amp;lt;something&amp;gt;&lt;/code&gt; notation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is another concept. Never use global variables in a subroutine. :) &lt;/p&gt;

&lt;p&gt;It was possible to set up CI/CD Variables with &lt;code&gt;TF_VAR_&amp;lt;something&amp;gt;&lt;/code&gt; notation. However, it would make these variables hidden. One should guess the source of data. Now everything is visible and it is much simpler to understand the code.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Azure or rather Entra ID side
&lt;/h2&gt;

&lt;p&gt;Let us dive into the meaning and source of the &lt;code&gt;$AZURE_CLIENT_ID&lt;/code&gt;, &lt;code&gt;$AZURE_TENANT_ID&lt;/code&gt;, &lt;code&gt;$AZURE_SUBSCRIPTION_ID&lt;/code&gt; variables. &lt;/p&gt;

&lt;p&gt;I believe &lt;code&gt;Subscription ID&lt;/code&gt; and &lt;code&gt;Tenant ID&lt;/code&gt; are self-explanatory, right? Just in case, the &lt;code&gt;Subscription ID&lt;/code&gt; is visible almost on every interface of portal.azure.com. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvg5zm165lh0ptwb84zuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvg5zm165lh0ptwb84zuq.png" alt="Subscription ID" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tenant ID is slightly harder to find out. The quickest way is to search for 'Entra ID' in the portal, open 'Entra ID' and you will see it immediately:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff59yg9m15beul42s8p5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff59yg9m15beul42s8p5a.png" alt="Tenant ID" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't close it yet.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is the &lt;code&gt;$AZURE_CLIENT_ID&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;It is an identifier of the Service Principal (or App Registration) you created to authenticate your pipeline.&lt;/p&gt;

&lt;p&gt;If you did not, here is how to do it. Open your Entra ID portal, find the &lt;code&gt;App Registrations&lt;/code&gt; blade, and press &lt;code&gt;New Registration&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wfd8tsnncrpiq29r4bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wfd8tsnncrpiq29r4bi.png" alt="Register Application aka Service Principal" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then provide a meaningful name and press 'Register'.&lt;/p&gt;

&lt;p&gt;In my case, the name is &lt;code&gt;sp_azure_subscription_contributor&lt;/code&gt;. You can always find your Service Principal in the &lt;code&gt;App Registrations&lt;/code&gt; interface of Entra ID.&lt;/p&gt;

&lt;p&gt;This is the place where you can find out the Client ID of your Service Principal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqjxrq47cb08heq0iyuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqjxrq47cb08heq0iyuw.png" alt="Information about Service Principal" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How and where do I provide the Service Principal with access to the Azure subscription?
&lt;/h3&gt;

&lt;p&gt;You created the identity and it must have permission to access your subscription. How to do it? Simple. &lt;/p&gt;

&lt;p&gt;Open your subscription in the portal, open the &lt;code&gt;Access control (IAM)&lt;/code&gt; blade, and &lt;code&gt;Add role assignment&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gt24vi0lx0dqlkbiqi9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gt24vi0lx0dqlkbiqi9.png" alt=" " width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then choose the &lt;code&gt;Contributor&lt;/code&gt; Role and, find and choose your newly created Service Principal and assign it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xuatw1yubpircwgr5qw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xuatw1yubpircwgr5qw.png" alt=" " width="800" height="923"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, for now, we have everything except the last and most important OIDC ingredient. &lt;/p&gt;
&lt;h2&gt;
  
  
  The Federated Identity Credentials is Open ID Connect magic
&lt;/h2&gt;

&lt;p&gt;And there is no magic at all. You will need to create the &lt;code&gt;Federated Credentials&lt;/code&gt; to allow your pipeline to access your Service Principal credentials without a shared secret. &lt;/p&gt;

&lt;p&gt;Open your &lt;code&gt;App Registrations&lt;/code&gt; information about your Service Principal. Open the &lt;code&gt;Certificates &amp;amp; Secrets&lt;/code&gt; blade. Click on the &lt;code&gt;Federated credentials&lt;/code&gt; tab, and press &lt;code&gt;+ Add credential&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqvqa8k72crjtx79m63h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqvqa8k72crjtx79m63h.png" alt=" " width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the 'magic sauce':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24d5ilz1dznz5uw9mpiv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24d5ilz1dznz5uw9mpiv.png" alt=" " width="800" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can enter whatever you want in the name or description. But it is crucial to provide the correct information in the next fields:&lt;/p&gt;

&lt;p&gt;Issuer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://gitlab.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subject identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project_path:&amp;lt;gitlab_user_name&amp;gt;/&amp;lt;gitlab_project_name&amp;gt;:ref_type:branch:ref:&amp;lt;pipeline_branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project_path:&amp;lt;gitlab_project_group&amp;gt;/&amp;lt;gitlab_project_name&amp;gt;:ref_type:branch:ref:&amp;lt;pipeline_branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project_path:rokicool/gitlab-azure-oidc-opentofu:ref_type:branch:ref:main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Audience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://gitlab.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is all. Here is the Resource Group created by the demo pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69t9n9h22uzhqzulcdyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69t9n9h22uzhqzulcdyn.png" alt=" " width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.gitlab.com/ee/ci/cloud_services/azure/#create-azure-ad-federated-identity-credentials" rel="noopener noreferrer"&gt;GitLab Official Azure AD Federated Identity Credentials Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://gitlab.com/components/opentofu" rel="noopener noreferrer"&gt;GitLab OpenTofu Component&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;UPD 2024.12: Microsoft started supporting wildcards! Here is my post about that: &lt;a href="https://dev.to/rokicool/wildcards-in-federated-credentials-entra-id-oidc-d7e"&gt;Wildcards in Federated Credentials Entra ID (OIDC)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>openidconnect</category>
      <category>azure</category>
      <category>terraform</category>
    </item>
    <item>
      <title>How do you automate PIM for Groups? (Part 3. Expiration time, Policies, and experiments)</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Wed, 24 Apr 2024 19:53:24 +0000</pubDate>
      <link>https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-3-expiration-time-policies-and-experiments-35d2</link>
      <guid>https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-3-expiration-time-policies-and-experiments-35d2</guid>
      <description>&lt;p&gt;That is the last part of the posts related to 'PIM for Groups' technology.&lt;/p&gt;

&lt;p&gt;The links are here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/rokicool/lets-talk-about-pim-42lo"&gt;Let's talk about PIM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-1-setup-pl5"&gt;How do you automate PIM for Groups? (Part 1 - Setup)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-2-playing-with-pim-for-groups-via-api-lli"&gt;How do you automate PIM for Groups? (Part 2. Playing with PIM for Groups via API)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am far from proclaiming that these posts cover the entire technology and API. However, my access to the Trial P2 Entra ID license is going to expire, so I need to put the final dot or exclamation mark. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let us have a look at the expiration
&lt;/h2&gt;

&lt;p&gt;Both 'the PIM eligibility assignment' and 'the activation of the PIM eligibility assignment' have an expiration time. &lt;/p&gt;

&lt;p&gt;Let's talk about the 'PIM eligibility assignment expiration.'&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you get the PIM eligibility expiration time?
&lt;/h3&gt;

&lt;p&gt;Assume that you need to know when the users' PIM eligibility expires. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5csg54qzfjl049rvu7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5csg54qzfjl049rvu7w.png" alt=" " width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To access that information, you should make a call to &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-list-eligibilityschedules?view=graph-rest-1.0&amp;amp;tabs=powershell" rel="noopener noreferrer"&gt;List eligibilitySchedules&lt;/a&gt; API using PowerShell function with the ridiculously long name: &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/get-mgidentitygovernanceprivilegedaccessgroupeligibilityschedule?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"groupId eq '{0}' and principalId eq '{1}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Expiration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndDateTime&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Sunday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;April&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10:38:29&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between Portal and PowerShell results is 5 hours. It's probably because PowerShell returns Greenwich time, but Portal shows my local (Central Time). &lt;/p&gt;

&lt;h3&gt;
  
  
  How do we extend PIM eligibility assignment expiration time?
&lt;/h3&gt;

&lt;p&gt;What if we understand that the expiration time of the PIM eligibility assignment must be postponed? &lt;/p&gt;

&lt;p&gt;The answer is... the same &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-post-eligibilityschedulerequests?view=graph-rest-1.0&amp;amp;tabs=powershell" rel="noopener noreferrer"&gt;Create eligibilityScheduleRequest&lt;/a&gt; via PowerShell &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/new-mgidentitygovernanceprivilegedaccessgroupeligibilityschedulerequest?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;accessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;principalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adminExtend"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;scheduleInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;startDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AfterDateTime"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;endDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$((&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;AddDays&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="err"&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 class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;justification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Expire the PIM eligibility in two weeks."&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;ApprovalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CompletedDateTime&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;CustomData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;IsValidationOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Justification&lt;/span&gt;&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;AccessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupId&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;Princip&lt;/span&gt;&lt;span class="w"&gt;
                                                                                                                                                                                                                                       &lt;/span&gt;&lt;span class="n"&gt;alId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------&lt;/span&gt;&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;adminExtend&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:11:08&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:11:07&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;0d44080f-8c56-41c1-a2ee-730eeff3e397&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Expire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eligibility&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;two&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;weeks.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Provisioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c88163&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"groupId eq '{0}' and principalId eq '{1}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Expiration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndDateTime&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Sunday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:11:06&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! &lt;/p&gt;

&lt;p&gt;And the last thing for today...&lt;/p&gt;

&lt;h3&gt;
  
  
  How do we assign PIM eligibility permanently?
&lt;/h3&gt;

&lt;p&gt;What if you need to assign PIM eligibility with no expiration? That is tricky. &lt;/p&gt;

&lt;p&gt;By default, Entra ID supports a policy that disallows PIM eligibility longer than one year.&lt;/p&gt;

&lt;p&gt;What to do? Correct, edit the policy assigned to the PIM group!&lt;/p&gt;

&lt;p&gt;To perform that we will require another PowerShell module&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Identity.SignIns&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Untrusted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;installing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;untrusted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repository.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trust&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;its&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InstallationPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Set-PSRepository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmdlet.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'PSGallery'&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Suspend&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Import-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Identity.SignIns&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, prepare the variables and update the policy based on &lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/graph/api/unifiedrolemanagementpolicyrule-update?view=graph-rest-1.0&amp;amp;tabs=powershell#example-1-update-a-rule-defined-for-a-policy-in-pim-for-microsoft-entra-roles" rel="noopener noreferrer"&gt;Update a rule defined for a policy in PIM for Microsoft Entra roles&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/expirationpattern?view=graph-rest-1.0" rel="noopener noreferrer"&gt;expirationPattern resource type&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgPolicyRoleManagementPolicyAssignment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"scopeId eq '{0}' and scopeType eq 'Group' and RoleDefinitionId eq 'member'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$unifiedRoleManagementPolicyId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PolicyId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$unifiedRoleManagementPolicyRuleId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Expiration_Admin_Eligibility"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="s2"&gt;"@odata.type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#microsoft.graph.unifiedRoleManagementPolicyExpirationRule"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Expiration_Admin_Eligibility"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;isExpirationRequired&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="s2"&gt;"@odata.type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"microsoft.graph.unifiedRoleManagementPolicyRuleTarget"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Admin"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;operations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="s2"&gt;"All"&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="nx"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Eligibility"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;inheritableSettings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;enforcedSettings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 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 class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Update-MgPolicyRoleManagementPolicyRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UnifiedRoleManagementPolicyId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$unifiedRoleManagementPolicyId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UnifiedRoleManagementPolicyRuleId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$unifiedRoleManagementPolicyRuleId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Expiration_Admin_Eligibility&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ta-da!&lt;/p&gt;

&lt;p&gt;And now we are allowed to assign PIM eligibility permanently!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;accessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;principalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AdminAssign"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;scheduleInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;startDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noExpiration"&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 class="nx"&gt;justification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assign eligible request."&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;ApprovalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CompletedDateTime&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;CustomData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;IsValidationOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Justification&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;AccessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupId&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;PrincipalId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;            
&lt;/span&gt;&lt;span class="n"&gt;adminAssign&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:43:25&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:43:24&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;48624c8b-a008-4499-ab60-1922eab76da6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eligible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Provisioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c8816325-d172-44f5-b72&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at the PIM eligibility assignment via the portal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z6j4am5uykfexttsghf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z6j4am5uykfexttsghf.png" alt=" " width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here it is!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;Of course, there are many topics outside of this series, and I never wanted to cover everything. My idea was to cover the 'first steps'—the 'initial direction.' &lt;/p&gt;

&lt;p&gt;When I started, I found 0 (zero) explanations, and the documentation was in beta. I spent significant time trying and retrying the API calls to understand how they work. &lt;/p&gt;

&lt;p&gt;I recently found a lovely post about PIM for Groups: &lt;a href="https://dikkekip.github.io/posts/pim-for-groups-1/" rel="noopener noreferrer"&gt;Automate Assignments with the GraphAPI!&lt;/a&gt;. If I had seen it earlier, I would not have written mine. But I believe mine is slightly deeper. :) &lt;/p&gt;

&lt;p&gt;Thank you for being with me for that long. &lt;/p&gt;

&lt;p&gt;PS. All the tenants, subscriptions, and users from the examples are gone. Please don't bother to search for them. :)&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>microsoftgraph</category>
      <category>entraid</category>
      <category>pim</category>
    </item>
    <item>
      <title>How do you automate PIM for Groups? (Part 2. Playing with PIM for Groups via API)</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sun, 21 Apr 2024 15:16:29 +0000</pubDate>
      <link>https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-2-playing-with-pim-for-groups-via-api-lli</link>
      <guid>https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-2-playing-with-pim-for-groups-via-api-lli</guid>
      <description>&lt;p&gt;In my previous text, we sufficiently discussed the PIM for Group's technology. In the first part of this article &lt;a href="https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-1-setup-pl5"&gt;How do you automate PIM for Groups? (Part 1 - Setup)&lt;/a&gt; we prepared our 'infrastructure' and defined our plans.&lt;/p&gt;

&lt;p&gt;Let us start with something meaningful! :)&lt;/p&gt;

&lt;h3&gt;
  
  
  How do we make the user (pim-user-play-01) PIM eligible to activate membership in the PIM Group (PIM-GROUP-PLAY-01)?
&lt;/h3&gt;

&lt;p&gt;You remember that "PIM eligible" means "provided with the ability to activate something via PIM," right? &lt;/p&gt;

&lt;p&gt;Here is a .gif video of this action performed 'manually', using Azure Portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfink0weuj5dn9qqo6nq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfink0weuj5dn9qqo6nq.gif" alt=" " width="720" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's take the same action via Microsoft Graph API. &lt;/p&gt;

&lt;p&gt;Here is the link to the API call documentation: &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-post-eligibilityschedulerequests?view=graph-rest-1.0&amp;amp;tabs=powershell" rel="noopener noreferrer"&gt;Create eligibilityScheduleRequest&lt;/a&gt; and the PowerShell module documentation: &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/new-mgidentitygovernanceprivilegedaccessgroupeligibilityschedulerequest?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="w"&gt;                                                             

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;Mail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UserPrincipalName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pim-user-play-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c8816325-d172-44f5-b72d-a1b8de5673c2&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;pim-user-play-01&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;Selflearning527.onmicrosoft.com&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;MailNickname&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;GroupTypes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;accessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;principalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AdminAssign"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;scheduleInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;startDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AfterDateTime"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;endDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$((&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;AddDays&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="err"&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 class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;justification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; always deserved to be part of &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;! Stand up, Sir &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;. You have time until &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.AddDays(7))!"&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;ApprovalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CompletedDateTime&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;CustomData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;IsValidationOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Justification&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------&lt;/span&gt;&lt;span class="w"&gt;                                                                                             
&lt;/span&gt;&lt;span class="n"&gt;adminAssign&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;4/20/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:18:51&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4/20/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:18:51&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;55067776-3b6d-44eb-8337-b0a2ad101bae&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;pim-user-play-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deserved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Stand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pim-user-play-01.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hav&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at the PIM interface in the Azure Portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhlo8jq0u188zd0tbpfg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhlo8jq0u188zd0tbpfg.gif" alt=" " width="760" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we make ALL security group members (GROUP-PLAY-01) be PIM eligible to activate membership in the PIM Group (PIM-GROUP-PLAY-01)?
&lt;/h2&gt;

&lt;p&gt;That operation is very similar to the previous one. However, instead of assigning PIM eligibility to a User, you can do the same to a Group. &lt;/p&gt;

&lt;p&gt;Here is how the 'manual' process looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp0p4c19783apgglsmuy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp0p4c19783apgglsmuy.gif" alt=" " width="720" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will use the same API call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$g01&lt;/span&gt;&lt;span class="w"&gt;                                                               

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;MailNickname&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;GroupTypes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d8800de8-1e79-4881-8cb3-814c0f6cd935&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;MailNickname&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;GroupTypes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;accessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;principalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$g01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AdminAssign"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;scheduleInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;startDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AfterDateTime"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;endDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$((&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;AddDays&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="err"&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 class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;justification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Members of &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$g01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; always deserved to be part of &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;! You have time until &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.AddDays(7))!"&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;ApprovalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CompletedDateTime&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;CustomData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;IsValidationOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Justification&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------&lt;/span&gt;&lt;span class="w"&gt;                                                                                             
&lt;/span&gt;&lt;span class="n"&gt;adminAssign&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;4/20/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:48:15&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4/20/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:48:15&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;ed7561c6-54af-4a71-8d7e-f31cee64fc19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deserved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;until&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;04/27/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's have a look at the Azure Portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlr9x0ma5vhpwwpchw4i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlr9x0ma5vhpwwpchw4i.gif" alt=" " width="760" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice! Both the user (pim-user-play-01) and the group (GROUP-PLAY-01) are now PIM eligible to activate membership in PIM group (PIM-GROUP-PLAY-01).&lt;/p&gt;

&lt;p&gt;I had to open the Azure Portal and take screenshots to prove this. But is it not possible to perform that operation via Microsoft Graph API? The question urges us to the next topic!&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we check whether the specific user or group is PIM eligible?
&lt;/h2&gt;

&lt;p&gt;To perform that, we will call to &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-list-eligibilityschedules?view=graph-rest-1.0&amp;amp;tabs=powershell" rel="noopener noreferrer"&gt;List eligibilitySchedules&lt;/a&gt; using PowerShell function &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/get-mgidentitygovernanceprivilegedaccessgroupeligibilityschedule?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"groupId eq '{0}' and principalId eq '{1}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;CreatedUsing&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                                                               &lt;/span&gt;&lt;span class="nx"&gt;ModifiedDateTime&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;AccessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupId&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;MemberType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Principal&lt;/span&gt;&lt;span class="w"&gt;
                                                                                                                                                                                                                                     &lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                                                               &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;---------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;/20/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:18:51&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;55067776-3b6d-44eb-8337-b0a2ad101bae&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d_member_55067776-3b6d-44eb-8337-b0a2ad101bae&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1/1/0001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8:00:00&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Provisioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;direct&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;c8816325&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"groupId eq '{0}' and principalId eq '{1}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$g01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;CreatedUsing&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                                                               &lt;/span&gt;&lt;span class="nx"&gt;ModifiedDateTime&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;AccessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupId&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;MemberType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Principal&lt;/span&gt;&lt;span class="w"&gt;
                                                                                                                                                                                                                                     &lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                                                               &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;---------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;/20/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;11:48:15&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ed7561c6-54af-4a71-8d7e-f31cee64fc19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d_member_ed7561c6-54af-4a71-8d7e-f31cee64fc19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1/1/0001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8:00:00&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Provisioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;direct&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;d8800de8&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes! We have the same results as the 'manual' path already shown. &lt;/p&gt;

&lt;h2&gt;
  
  
  How do we activate PIM eligibility?
&lt;/h2&gt;

&lt;p&gt;This is precisely why all the technology was created: temporarily activating a role or group membership.&lt;/p&gt;

&lt;p&gt;Here is the .gif video of how a user can activate his/her PIM eligibility:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtrv8rr7j0l5ib8zdjh6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtrv8rr7j0l5ib8zdjh6.gif" alt=" " width="720" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how activation is performed via Microsoft Graph API. Here is a link to Graph API Documentation: &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-post-assignmentschedulerequests?view=graph-rest-1.0&amp;amp;tabs=powershell" rel="noopener noreferrer"&gt;Create assignmentScheduleRequest&lt;/a&gt; and PowerShell Documentation &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/new-mgidentitygovernanceprivilegedaccessgroupassignmentschedulerequest?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;New-MgIdentityGovernancePrivilegedAccessGroupAssignmentScheduleRequest&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;accessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;principalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adminAssign"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;scheduleInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;startDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"afterDuration"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PT2H"&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 class="nx"&gt;justification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Always wanted to try this group!"&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-MgIdentityGovernancePrivilegedAccessGroupAssignmentScheduleRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;ApprovalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CompletedDateTime&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;CustomData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;IsValidationOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Justification&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;AccessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupId&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;PrincipalId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;      
&lt;/span&gt;&lt;span class="n"&gt;adminAssign&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2:25:19&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2:25:18&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AM&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;a24ebfc9-45ac-49bc-ad81-d0d7d3eb6d51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wanted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Provisioned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c8816325-d172-44&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here it is! The membership in PIM-GROUP-PLAY-01 is activated!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjq1ddiqapbv3whtewihp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjq1ddiqapbv3whtewihp.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect! The next question is...&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we remove PIM eligibility?
&lt;/h2&gt;

&lt;p&gt;What if we need to revoke the ability to activate membership? Is it possible? &lt;/p&gt;

&lt;p&gt;Of course! Here is how one could do it 'manually':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxnklfw2zun7g4n9nrbr8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxnklfw2zun7g4n9nrbr8.gif" alt=" " width="720" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In automation, Microsoft Graph API call &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-post-eligibilityschedulerequests?view=graph-rest-1.0&amp;amp;tabs=powershell" rel="noopener noreferrer"&gt;Create eligibilityScheduleRequest&lt;/a&gt; or PowerShell function &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/new-mgidentitygovernanceprivilegedaccessgroupeligibilityschedulerequest?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/a&gt; will help you. &lt;/p&gt;

&lt;p&gt;And yes, I have not made a mistake. The same call and PowerShell function created the PIM eligibility! The difference in the parameters of the call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;accessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;principalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;groupId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adminRemove"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;justification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"It is time to go."&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BodyParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;ApprovalId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CompletedDateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CreatedDateTime&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;CustomData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;IsValidationOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Justification&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;AccessId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupId&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;PrincipalId&lt;/span&gt;&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="nx"&gt;Target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ScheduleId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;---------------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;--------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;adminRemove&lt;/span&gt;&lt;span class="w"&gt;                              &lt;/span&gt;&lt;span class="nx"&gt;4/21/2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2:41:21&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AM&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;9f1bd851-83a9-4a29-b1f7-ff4e8e362b14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;go.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Revoked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c8816325-d172-44f5-b72d-a1b8de5673c2&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The exciting bonus stuff!
&lt;/h3&gt;

&lt;p&gt;About that... You know the post is already embarrassingly long. &lt;/p&gt;

&lt;p&gt;Let's meet again in '&lt;a href="https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-3-expiration-time-policies-and-experiments-35d2"&gt;Part 3 - The Bonus stuff&lt;/a&gt;' post! &lt;/p&gt;

&lt;p&gt;:)&lt;/p&gt;

&lt;p&gt;Meanwhile, I don't pretend to cover everything; I am sure there might be mistakes or typos. Please don't hesitate to comment!&lt;/p&gt;

&lt;p&gt;All the '.gif videos' are made with &lt;a href="https://www.cockos.com/licecap/" rel="noopener noreferrer"&gt;LICEcap&lt;/a&gt;&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>microsoftgraph</category>
      <category>entraid</category>
      <category>pim</category>
    </item>
    <item>
      <title>How do you automate PIM for Groups? (Part 1 - Setup)</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sun, 21 Apr 2024 15:13:14 +0000</pubDate>
      <link>https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-1-setup-pl5</link>
      <guid>https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-1-setup-pl5</guid>
      <description>&lt;p&gt;Recently, I wrote a &lt;a href="https://dev.to/rokicool/lets-talk-about-pim-42lo"&gt;boring text explaining the advantages of PIM for Groups&lt;/a&gt;. Today, I want to step further and show how to automate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What? Automate PIM for Groups? Why?
&lt;/h2&gt;

&lt;p&gt;Yep. You can have a list of security group members from your PowerShell script, can you? You can administer a security group membership, right? But can you assign PIM eligibility for a user without expiration from a PowerShell script? I doubt that. However, that is a very similar operation and should be performed easily. &lt;/p&gt;

&lt;h2&gt;
  
  
  What permissions do we need to begin?
&lt;/h2&gt;

&lt;p&gt;Let us assume that we have an EntraID tenant in which you can have one of two roles: &lt;a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator" rel="noopener noreferrer"&gt;Global Administrator&lt;/a&gt; or &lt;a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#privileged-role-administrator" rel="noopener noreferrer"&gt;Privileged Role Administrator&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To read PIM-related data from Entra ID, you might get out with &lt;a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-reader" rel="noopener noreferrer"&gt;Global Reader&lt;/a&gt;, but making any change is only allowed for the two mentioned above. &lt;/p&gt;

&lt;h2&gt;
  
  
  PowerShell and modules?
&lt;/h2&gt;

&lt;p&gt;The entire post is about using PowerShell to automate PIM for Groups. Technically, you can use any supported language or none since everything is available via Microsoft Graph API. However, the examples here are written in PowerShell.&lt;/p&gt;

&lt;p&gt;Today, the latest version &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.4" rel="noopener noreferrer"&gt;is 7.4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To play with the examples, you will need to install several PowerShell modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the required modules&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;install-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Groups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;install-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Users&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;install-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Identity.Governance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;install-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Identity.DirectoryManagement&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="c"&gt;# Import them&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;import-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Groups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;import-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Users&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;import-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Identity.Governance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;import-module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Graph.Identity.DirectoryManagement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Are we ready? Can we start?
&lt;/h2&gt;

&lt;p&gt;Yes. Almost.&lt;/p&gt;

&lt;p&gt;We need to authenticate the PowerShell session against Microsoft Graph and request permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Connect-MgGraph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scopes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Group.ReadWrite.All"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User.ReadWrite.All"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Domain.Read.All"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RoleManagementPolicy.ReadWrite.AzureADGroup"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Welcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Connected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;via&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;delegated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;14d82eec-204b-4c2f-b7e8-296a70dab67e&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Readme:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://aka.ms/graph/sdk/powershell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;SDK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Docs:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://aka.ms/graph/sdk/powershell/docs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Docs:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://aka.ms/graph/docs&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;NOTE:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoWelcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;suppress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;message.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;get-mgContext&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;ClientId&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="nx"&gt;14d82eec-204b-4c2f-b7e8-296a70dab67e&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;TenantId&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="nx"&gt;c23a6ba9-4536-4e44-a7b8-9c643e365d2f&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Scopes&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="n"&gt;Group.ReadWrite.All&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;AuthType&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="nx"&gt;Delegated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;TokenCredentialType&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="nx"&gt;InteractiveBrowser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;CertificateThumbprint&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="n"&gt;CertificateSubjectName&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="n"&gt;Account&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="nx"&gt;RomanKiprin&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;Selflearning527.onmicrosoft.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;AppName&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="nx"&gt;Microsoft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Graph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Tools&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ContextScope&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="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Certificate&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="n"&gt;PSHostVersion&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="nx"&gt;7.4.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ManagedIdentityId&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="n"&gt;ClientSecret&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="n"&gt;Environment&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="nx"&gt;Global&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why do we need these particular permissions?
&lt;/h2&gt;

&lt;p&gt;The short answer - trust me! &lt;/p&gt;

&lt;p&gt;If, for some reason, you don't, here is a screenshot of one of the Microsoft Graph API calls &lt;a href="https://learn.microsoft.com/en-us/graph/api/privilegedaccessgroup-list-assignmentschedulerequests?view=graph-rest-1.0&amp;amp;tabs=powershell#permissions" rel="noopener noreferrer"&gt;List assignmentScheduleRequests&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gbd93odv0z2p9zm4p88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gbd93odv0z2p9zm4p88.png" alt=" " width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every &lt;a href="https://learn.microsoft.com/en-us/graph/overview?context=graph%2Fapi%2F1.0&amp;amp;view=graph-rest-1.0" rel="noopener noreferrer"&gt;Microsoft Graph API&lt;/a&gt; call has a "Permissions" paragraph, and you can perform your research. &lt;/p&gt;

&lt;p&gt;You can also look at &lt;a href="https://learn.microsoft.com/en-us/graph/permissions-reference" rel="noopener noreferrer"&gt;Microsoft Graph permissions reference&lt;/a&gt;. But in this post, I will stop with permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can we start playing with PIM already?
&lt;/h2&gt;

&lt;p&gt;I know, I know. Let's set up global variables and create/check our Entra ID object!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$groupName01&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GROUP-PLAY-01"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pimGroupName01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PIM-GROUP-PLAY-01"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$userName01&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pim-user-play-01"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then, let's create a couple of groups and ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$g01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DisplayName eq '{0}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$groupName01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$g01&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 class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$groupName01&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;MailEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$False&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;MailNickName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$groupName01&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;SecurityEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$True&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PIM for Groups tests"&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;$g01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-MgGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;params&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DisplayName eq '{0}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pimGroupName01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pg01&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 class="nv"&gt;$params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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 class="nx"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pimGroupName01&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;MailEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$False&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;MailNickName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$pimGroupName01&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;SecurityEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$True&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PIM for Groups tests"&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;$pg01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-MgGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;params&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$g01&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;MailNickname&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;GroupTypes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d8800de8-1e79-4881-8cb3-814c0f6cd935&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pg01&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;MailNickname&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;GroupTypes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;853d7402-51b4-4cd4-9b8d-9f159311859d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM-GROUP-PLAY-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PIM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... a user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgDomain&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-MgUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DisplayName eq '{0}'"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$userName01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u01&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 class="nv"&gt;$PasswordProfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;here-should-be-password&amp;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;$params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$userName01&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;AccountEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;MailNickName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$userName01&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;PasswordProfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PasswordProfile&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nx"&gt;UserPrincipalName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$userName01&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&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 class="nv"&gt;$u01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-MgUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;params&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="n"&gt;pwsh&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u01&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nx"&gt;Mail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UserPrincipalName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;-----------&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="o"&gt;----&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----------------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pim-user-play-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c8816325-d172-44f5-b72d-a1b8de5673c2&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;pim-user-play-01&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;Selflearning527.onmicrosoft.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can confirm the groups...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvqq99s111mr6rleen8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvqq99s111mr6rleen8c.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;... and the user are visible via the Portal&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ur6yur68ddz8bnd1yhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ur6yur68ddz8bnd1yhg.png" alt=" " width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What will we do?
&lt;/h2&gt;

&lt;p&gt;I spent quite some time preparing the stage for the demonstration, and the goal of this text might have slipped out a little. &lt;/p&gt;

&lt;p&gt;My previous text provides a profound explanation of the PIM for Group technology. Based on its terms, let's say what we will do. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We will make a user (pim-user-play-01) PIM eligible to activate the membership of the PIM Group (PIM-GROUP-PLAY-01).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We will make ALL members of the security group (GROUP-PLAY-01) PIM eligible to activate the membership of the PIM Group (PIM-GROUP-PLAY-01).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We will check if the specific user or group is PIM-eligible. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We will activate PIM eligibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We will remove PIM eligibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And perform some additional bonus stuff, too.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for the "&lt;a href="https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-2-playing-with-pim-for-groups-via-api-lli"&gt;Part 2. Playing with PIM for Groups via API&lt;/a&gt;"!&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>microsoftgraph</category>
      <category>entraid</category>
      <category>pim</category>
    </item>
    <item>
      <title>Let's talk about PIM</title>
      <dc:creator>Roman Kiprin</dc:creator>
      <pubDate>Sun, 31 Mar 2024 17:46:05 +0000</pubDate>
      <link>https://dev.to/rokicool/lets-talk-about-pim-42lo</link>
      <guid>https://dev.to/rokicool/lets-talk-about-pim-42lo</guid>
      <description>&lt;h3&gt;
  
  
  Yes, Privileged Identity Management. PIM.
&lt;/h3&gt;

&lt;p&gt;This is a technology that Microsoft provides in Entra ID (former Azure AD). I believe this will be an industry standard, just like they did with Active Directory. &lt;/p&gt;

&lt;p&gt;Everyone who works with authentication and system integration should know about it! &lt;/p&gt;

&lt;h3&gt;
  
  
  Let me explain why you might need it!
&lt;/h3&gt;

&lt;p&gt;Imagine that you work with some system. You need to perform administrative tasks with a higher level of access from time to time. However, it is unsafe always to access the system with such permissions: you can devastate it with one wrong command or misclick of a mouse. Or a virus can access the system using your permissions, causing many problems. &lt;/p&gt;

&lt;p&gt;Working with Windows Domains, it was a good practice to have two different accounts: one for ordinary stuff and the other for Administrative purposes only. &lt;/p&gt;

&lt;p&gt;It was not super convenient, but it helped a lot with segregating the routines and permissions. So, I had a 'user' role most of the time and an 'administrator' when required. &lt;/p&gt;

&lt;p&gt;Microsoft has developed that Role assignment significantly since these days in Entra ID (former Azure AD) and Azure Cloud. &lt;/p&gt;

&lt;h3&gt;
  
  
  So, Entra ID (former Azure AD). Here is the thing.
&lt;/h3&gt;

&lt;p&gt;Any user is allowed to be assigned any Role in Entra ID. &lt;a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/concept-understand-roles" rel="noopener noreferrer"&gt;There are about 60 buil-in Roles in it&lt;/a&gt;. A role is just a set of permissions to perform operations (or REST API calls if you go more profound).&lt;/p&gt;

&lt;p&gt;To avoid providing permissions permanently, Microsoft invented PIM. PIM creates a simple but effective concept. &lt;/p&gt;

&lt;p&gt;Instead of a permanent Role, the user is provided with PIM eligibility to activate the Role when necessary. &lt;/p&gt;

&lt;p&gt;You can use the PIM interface to activate your Global Administrator Entra ID Role when performing a high-level administration task. I'll skip some details... The user's account will receive the requested permissions in a few minutes. &lt;/p&gt;

&lt;p&gt;And then, for example, 4 hours later, the Role permissions disappear. Of course, all activations are logged in to your favorite SEIM. &lt;/p&gt;

&lt;p&gt;Cool?&lt;/p&gt;

&lt;p&gt;OK. Let's assume there is only one nerd here—myself. But I think it is incredibly cool. &lt;/p&gt;

&lt;p&gt;You might think that is all. Nope. It is only the beginning! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0fign18ntqnegtpec5x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0fign18ntqnegtpec5x.gif" alt=" " width="400" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if you need to elevate a user's permissions not in Entra ID but in some other system that uses Entra ID for authentication? &lt;/p&gt;

&lt;h3&gt;
  
  
  Here is the honey!
&lt;/h3&gt;

&lt;p&gt;Microsoft developed PIM and another technology called &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/concept-pim-for-groups" rel="noopener noreferrer"&gt;PIM for Groups&lt;/a&gt;. These are similar but different technologies. &lt;/p&gt;

&lt;p&gt;Instead of providing users with Roles in Entra ID - this technology provides users with temporary membership in Entra ID security groups!  &lt;/p&gt;

&lt;h3&gt;
  
  
  PIM for Groups example
&lt;/h3&gt;

&lt;p&gt;Imagine you have two user groups: "Operators" and "Administrators." Membership in "Operators" gives access to The Best AI-Based Infosystem (TBAIBI). Membership in "Administrators" provides both operator and management-level access to the same TBAIBI. &lt;/p&gt;

&lt;p&gt;You can make most of the users members of the "Operators" group and some members of the "Administrators" group... &lt;/p&gt;

&lt;p&gt;However, you don't want the administrators always to have their permissions active. &lt;/p&gt;

&lt;p&gt;What to do?&lt;/p&gt;

&lt;p&gt;You make all the users members of "Operators," and some users, via the same PIM interface of Entra ID, are provided with &lt;em&gt;PIM eligibility&lt;/em&gt; to temporarily activate membership of the "Administrators" group. &lt;/p&gt;

&lt;p&gt;So, when a user needs to perform administrative operations, he/she goes to the Entra ID Privileged Identity Interface (PIM) and activates membership in a higher permissions group! Entra ID starts considering a user an "Administrator" group member in a few minutes. Since then, the external system (TBAIBI) believes the user is an administrator. &lt;/p&gt;

&lt;p&gt;Everything is logged, limited by time, managed by policies, filled with settings, and provided with notifications. And everything works without ANY additional functionality in your precious TBAIBI! &lt;/p&gt;

&lt;p&gt;Isn't it cool? &lt;/p&gt;

&lt;p&gt;I refuse to believe somebody could read the text until this line and think it was not! &lt;/p&gt;

&lt;p&gt;There are several not-so-bright things to mention, though. &lt;/p&gt;

&lt;h3&gt;
  
  
  The shortfalls of PIM for Groups
&lt;/h3&gt;

&lt;p&gt;Nothing comes free and free from issues, so...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This functionality is only available when you buy an &lt;a href="https://learn.microsoft.com/en-us/entra/fundamentals/licensing#licenses-you-must-have-for-pim" rel="noopener noreferrer"&gt;Entra ID P2 (or similar) license&lt;/a&gt;, which are &lt;a href="https://www.microsoft.com/en-us/security/business/microsoft-entra-pricing" rel="noopener noreferrer"&gt;sold for 9$ per user per month&lt;/a&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is tough to predict when the elevated permissions will take effect. All process layers are cached, and the caches' expiration is a mystery. (You might want to open a new Incognito window to start a new session and get access to elevated permissions without waiting.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This new technology went public several months ago, and automation is not widely available.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Something about automation
&lt;/h3&gt;

&lt;p&gt;Our infrastructure uses Terraform to deploy to Azure Cloud and perform operations with Entra ID security groups and users. &lt;/p&gt;

&lt;p&gt;Unfortunately, Terraform (provider for AzureAD) does not support PIM for Groups. &lt;/p&gt;

&lt;p&gt;Why bother? Let's use PowerShell to fill the gaps, right?&lt;/p&gt;

&lt;p&gt;You could start giggling and smiling at this question only a couple of months ago. By today, Microsoft has released the necessary PowerShell modules to the public, and they have documentation. &lt;/p&gt;

&lt;p&gt;Microsoft migrated Entra ID management under the umbrella of Microsoft Graph API. If you managed to ignore it until today, you might want to start paying attention. &lt;/p&gt;

&lt;p&gt;There is only one thing that I would like to mention. Microsoft stays fair to itself. :)&lt;/p&gt;

&lt;p&gt;Let's assume you must perform an automated call to make a user eligible to activate a privileged security group membership. What name would you give such a call?&lt;/p&gt;

&lt;p&gt;I am sure you guessed wrong! &lt;/p&gt;

&lt;p&gt;It is called &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/new-mgidentitygovernanceprivilegedaccessgroupeligibilityschedulerequest?view=graph-powershell-1.0" rel="noopener noreferrer"&gt;New-MgIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think about what name you would give a reverse operation API. You'll never guess! &lt;/p&gt;

&lt;p&gt;But I will write about it in the following article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some missed details
&lt;/h3&gt;

&lt;p&gt;I intentionally missed some details to avoid going into the weeds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both the PIM eligibility and the activation of the PIM eligibility might have an expiration&lt;/li&gt;
&lt;li&gt;Microsoft did not invent Roles. I remember srvctl / as sysdba on the tips of my thingers. Hello to you, Tom Kyte's readers! &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And much more that I might miss unintentionally. Please fill in the gaps in the comments!&lt;/p&gt;

&lt;p&gt;UPD. If you are ready to try some PowerShell here is the link to the continuation: &lt;a href="https://dev.to/rokicool/how-do-you-automate-pim-for-groups-part-1-setup-pl5"&gt;How do you automate PIM for Groups? (Part 1 - Setup)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>pim</category>
      <category>entraid</category>
    </item>
  </channel>
</rss>
