<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Joe Loveless Blog</title>
        <link>https://joeloveless.com/blog</link>
        <description>Joe Loveless Blog</description>
        <lastBuildDate>Sun, 17 May 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[How to deal with Intune profile exceptions]]></title>
            <link>https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions</link>
            <guid>https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions</guid>
            <pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[What we learned in dealing with Intune profiles, and our way of dealing with security exceptions.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/how-to-deal-with-intune-profile-exceptions-665c5da249c8b54d57003444e4493e5b.png" width="1200" height="630" class="img_ev3q"></p>
<p>Good day everyone. It's now been over a week since MMS MOA 2026, my brain is still drowning from all the information that was given (and fun that was had). It was great to catch up with co-workers, meet some new co-workers, and meet new people from around the world at MMS. I came away with all sorts of ideas on how we can improve our setup. It's interesting to see the Microsoft stance on things (cloud only, Intune only, blah blah blah), and then see what is actually happening out in the real world. I always have this feeling we are really behind, but then I see so many other people still running Configuration Manager and still have devices bound to Active Directory. I know MMS MOA has a little bit of bias, being historically a Configuration Manager conference, but I consider the people speaking and going to this event to be the best of the best when it comes to device management. To see people struggle with the same issues that we deal with daily....I guess that's refreshing?? It would be nice to see said issues get addressed at some point by Microsoft.</p>
<p>While I didn't get to do a fishing session at MMS, since I've been back home, I went on a fishing session with my son.</p>
<p><img decoding="async" loading="lazy" alt="Fishing Session" src="https://joeloveless.com/assets/images/fishing_session-51088d37dac8da7fc30642df34d202bd.jpeg" width="1440" height="1920" class="img_ev3q"></p>
<p>Look at this massive fish I reeled in Saturday! Was quite the fight to reel it in. Before I got side tracked showing off my fish, I was talking about the issues we deal with regarding Microsoft Intune. That's my inspiration for this post.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-group-policy-did-right">What Group Policy Did Right?<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#what-group-policy-did-right" class="hash-link" aria-label="Direct link to What Group Policy Did Right?" title="Direct link to What Group Policy Did Right?" translate="no">​</a></h2>
<p>A little bit of history, for those that have never dealt with Group Policy. With Group Policy, we had an order to how GPO's were processed. GPO's were processed in the following order:</p>
<ul>
<li class="">Group Policy Objects (GPO) are processed in the following order:<!-- -->
<ul>
<li class="">The local GPO is applied.</li>
<li class="">GPOs linked to sites are applied.</li>
<li class="">GPOs linked to domains are applied.</li>
<li class="">GPOs linked to organizational units (OUs) are applied. In a nested OU structure, GPOs linked to parent OUs are applied first, followed by GPOs linked to the child OUs.</li>
</ul>
</li>
</ul>
<p><em>Source: <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-policy/group-policy-processing" target="_blank" rel="noopener noreferrer" class="">Group Policy Processing</a></em></p>
<p>Also with GPO, we had the concept of link order.</p>
<p><em>The GPO link with the lowest link order in the Group Policy Object Links list has precedence by default.</em></p>
<p>Lowest link order = lowest number</p>
<p><img decoding="async" loading="lazy" alt="Link Order" src="https://joeloveless.com/assets/images/gpo_linkorder-5cdd057236cff7016cbc830d0a54b8a2.png" width="759" height="85" class="img_ev3q"></p>
<p>In this example, the GPO <strong>JoeLoveless - MicrosoftEdge - Overwrite</strong> would win out over <strong>JoeLoveless - MicrosoftEdge</strong></p>
<p>In the policy <strong>JoeLoveless - Microsoft Edge</strong>, I have this configured:</p>
<p><img decoding="async" loading="lazy" alt="GPO Default Setting" src="data:image/png;base64,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" width="777" height="163" class="img_ev3q"></p>
<p>In my overwrite policy, I am changing that setting to be <strong>Enabled</strong>. Then I would apply that to a targeted security group. Any workstation in that security group would then have the cast icon on the toolbar.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-does-intune-handle-this">How Does Intune handle this?<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#how-does-intune-handle-this" class="hash-link" aria-label="Direct link to How Does Intune handle this?" title="Direct link to How Does Intune handle this?" translate="no">​</a></h2>
<p>With Intune, this isn't the case. While other Microsoft cloud products have the concept of link order (Office Admin Center, Edge Admin Center, and I think Defender...), Intune doesn't follow this novel concept. Intune will just tell you there is a conflict in the policies, and leave it at that.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="our-attempts">Our Attempts<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#our-attempts" class="hash-link" aria-label="Direct link to Our Attempts" title="Direct link to Our Attempts" translate="no">​</a></h2>
<p>Anyone familiar with security benchmarks probably know the pain some of this can cause. You try your best to harden the workstations, then you find Group A needs this for some legacy workflow, Group B needs another of this for another legacy workflow, then you have devices that need to be in Kiosk mode so they have their own workflow. Fun times.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="attempt-1-monolithic-profile">Attempt 1: Monolithic Profile<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#attempt-1-monolithic-profile" class="hash-link" aria-label="Direct link to Attempt 1: Monolithic Profile" title="Direct link to Attempt 1: Monolithic Profile" translate="no">​</a></h3>
<p>Our first attempt, and this was a of a figure it out on the fly type project. We created a monolithic profile that contained all of our baseline settings. We did this with Group Policy with our core security settings, worked okay there, should work okay here too? Right? Wrong.</p>
<p>Denying profiles in GP is pretty consistent, if you know where the policies are, there is a good chance they'll fall off correctly. With Intune, settings are a bit all over the place and how they apply. Some write to the former GP registry values, some don't. Some write to the PolicyManager section, some don't. Some tattoo, some don't. Awfulness.</p>
<p>So we had a profile set this way, we would then duplicate the profile with all the settings still in and then change the settings we needed to change...this kinda worked. But we eventually decided the monolithic profile wasn't the best way. I'll explain what went wrong with Attempt 1 and Attempt 2 together.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="attempt-2-broken-down-profiles">Attempt 2: Broken Down Profiles<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#attempt-2-broken-down-profiles" class="hash-link" aria-label="Direct link to Attempt 2: Broken Down Profiles" title="Direct link to Attempt 2: Broken Down Profiles" translate="no">​</a></h3>
<p>I make fun of ITIL verbiage a lot. They say to "progress iteratively". Well that's what we did. Our next step was to break down the profiles into smaller chunks. Using the CIS baselines, we now broke the settings out by section.</p>
<p><img decoding="async" loading="lazy" alt="CIS Sections" src="https://joeloveless.com/assets/images/cis_sections-9ecbf93481736f577a7037d4eaa2c4b0.png" width="370" height="562" class="img_ev3q"></p>
<p><em>Note: CIS makes this a lot easier if you are a CIS SecureSuite member. You can now import the JSON files into Intune</em></p>
<p>Following the same concept as Attempt #1, our process was to duplicate the profile, and modify any settings that would need to be changed:</p>
<p><img decoding="async" loading="lazy" alt="CIS Exceptions" src="data:image/png;base64,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" width="332" height="85" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-this-failed">Where This Failed<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#where-this-failed" class="hash-link" aria-label="Direct link to Where This Failed" title="Direct link to Where This Failed" translate="no">​</a></h2>
<p>The ultimate goal is not to have exceptions. That would be security working with whomever to make sure the processes follow the security standard...ideally. Or you just pull the settings that need exceptions and call it a day. The 2nd one probably isn't the best choice. Our goal is to continue to apply the settings no matter what, to as many devices as possible. Our process broke down when the following happened:</p>
<ul>
<li class="">Group A<!-- -->
<ul>
<li class="">COMP-1</li>
<li class="">COMP-2</li>
<li class="">COMP-3</li>
<li class="">COMP-4</li>
<li class="">COMP-5</li>
</ul>
</li>
<li class="">Group B<!-- -->
<ul>
<li class="">COMP-6</li>
<li class="">COMP-7</li>
<li class="">COMP-2</li>
<li class="">COMP-3</li>
<li class="">COMP-8</li>
</ul>
</li>
</ul>
<p>Group A needs this setting changed:</p>
<p><img decoding="async" loading="lazy" alt="CIS Exception" src="https://joeloveless.com/assets/images/cis_exception1-9364137161ca670d57a9b93282c6e367.png" width="489" height="443" class="img_ev3q"></p>
<ul>
<li class="">Exclude Group A from WIN_D_CISL1_AdministrativeTemplates_PROD</li>
<li class="">Add Group A to WIN_D_CISL1_AdministrativeTemplates-EXC_PROD</li>
</ul>
<p>Group B needs this setting changed:</p>
<p><img decoding="async" loading="lazy" alt="CIS Exception" src="https://joeloveless.com/assets/images/cis_exception2-f6d09b3e4adcf6e60268d8f98c63a601.png" width="411" height="537" class="img_ev3q"></p>
<ul>
<li class="">Exclude Group B from WIN_D_CISL1_AdministrativeTemplates-WindowsComponents_PROD</li>
<li class="">Add Group B to WIN_D_CISL1_AdministrativeTemplates-WindowsComponents-EXC_PROD</li>
</ul>
<p>COMP-2 and COMP-3 are in both Group A and Group B. The rest of Group B does not need the exception that is applying to Group A. This is where we started to see the flaws in our logic.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution">The Solution<a href="https://joeloveless.com/blog/how-to-deal-with-intune-profile-exceptions#the-solution" class="hash-link" aria-label="Direct link to The Solution" title="Direct link to The Solution" translate="no">​</a></h2>
<p>What we have landed on, is this workflow. Note, this does create more Intune profiles, but to date, we haven't found a better way of doing this. I'm open to suggestions should anyone have any.</p>
<ol>
<li class="">Exception needed<!-- -->
<ol>
<li class="">Remove setting from main profile, create a "secondary profile" containing the default value.</li>
<li class="">Duplicate the "secondary profile", create a new "exception profile" containing the changed value.</li>
</ol>
</li>
</ol>
<p>In final, the Intune profiles would look like this:</p>
<p><img decoding="async" loading="lazy" alt="CIS Final Exception" src="data:image/png;base64,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" width="471" height="102" class="img_ev3q"></p>
<ul>
<li class="">WIN_D_CISL1_AdminTemplates-System_PROD<!-- -->
<ul>
<li class="">Removed the setting <strong>Turn on convenience PIN sign-in</strong></li>
</ul>
</li>
<li class="">WIN_D_CISL1_AdminTemplates-System-ConveniencePINSignIn_PROD
<img decoding="async" loading="lazy" alt="WIN_D_CISL1_AdminTemplates-System-ConveniencePINSignIn_PROD" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlAAAAF2CAIAAAAunTGkAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAASdEVYdFNvZnR3YXJlAEdyZWVuc2hvdF5VCAUAAB++SURBVHhe7dz/c1TXffDx/huSEmxjQui3X4CS/GB72v6QojQ2Jn1iB9aKmZYEtCOYjDExDggwGCQUItZ6NDYiretCSg2WHlka+QEHJ7IgWdkG4hpE0hBJtiTzPQEkeaXd/ekZ99P5zHnOubtIQhJafd4zrx+ku+fevbueuW/OuVf+k5LiIgAAZr0/CTcBADD7EDwAgAkEDwBgAsEDAJhA8AAAJhA8AIAJBA8AYALBAwCYQPAAACZMJHgrnnyi6/y51GfD2Uw69dnwwddeC8fMYnNXN8xd3VBSXDRv04l5m06EAwAAM9C4g7d40cLbt26mPhtuPHrkQMP+ttaWxjeOhsOmTmzlir5PPn6zuSl8aVLM33NxQUNKzN9zMRygwVNzlpQuaEiFIwEAM8e4g7d+XUU2k37t1VfDl6aHnEBba0v40qSYv+finCWl4XZF8ACgEI07eLGVK7KZdH9/3yMPP+S9tOvFndeuXslm0qMjKV3nbDx65Patm7L4+V5ncvGihSXFRW2tLdlMeteLOwf6+7KZ9I3r13Zs3y7jly9f1nX+3OhIanQk1XX+nIxX9fV12UxarV9XUbllc98nH8uvA/19y5cvk5E7tm+/cf2aHPytt9qymXR9fZ2cz/DQYDaTHh4a3LnjhfADRgZPkiZzPndJc+7qhvsf3aAzQi+EAICZY9zBKykuev3wYQlSR0e7Zi+xrzabSV/83X9tq6ysq3tJgidhe/v4sfLyNW8fP5bNpD94v1O3X71yeU9V1Z6qqtu3bg4PDZaWLl28aGF/f9/tWzc3Pruhprp6dCR16uRJ960fefihF3fuyGbSJ078rCwWW7xoYeMbRw8dPFgWix06eDCbSZ89c7qkuGjVqqdHR1JXr1yWk5Hi1tfXVe/enc2k29t/XhaLtbW2SAI9kcFb0JCSjVI+N3jM8ACgIEwkeCXFRfH42q7z57KZ9O1bN5955vslxUU9v7/4xxvXvQnZ57O0cx/pr8lf/SqbSZeWLpXg1dRUy/bXXn01m0kn9tVKNXX7qZMnR0f8kORZ0rw0MHBpYKCkuOjN5qZsJv38cz9wj+8Gb+nffU3uR4YHce/hSc/mrm5wH07xZngEDwAKwgSDJ+LxtcNDgz2///zJjtGRlMyuVPnaNV6ZpHPr11XoD7JdFirr6+tku8d7Uy948fjaD3999g83rstToxK8D97vdHfU48s5yMiu8+fCVdnIGZ53047gAUAhuqvglRQX/eeHv5ZJWH9/3/VrV71Xwxne6Ehq8aKFErbKLZtle+PRI9lMunr3bpnhvZTYVxaLKe+YbvBKS5eOjqQudJ0vL1/z+LJHdYYnN+00qHJ8XcBcvGihrH++ffyYd/BcwXNneNo5ggcABWTcwUvsq73Qdf5Aw/4DDfvd23KvHz6czaR/+5sL7j28UydP6j281jebs5m03JOT4F369NONz26Qe2wSy9LSpbdv3ZR7b3Jb7qeHDnknIBPHC13na6qrf3LggPxcFotJ1SR4zz/3A3myZltl5YGGBpnS1dfXJfbVNh49UhaL1VRXjz140jPu4QFAQRt38H646bnLlz4dHUnJg44dHe16J+ynhw7J4yHDQ4MHGvbLXKqttUWfimxrbXGf0jx08KC8NNDft+LJJ+Qg3129Wp+6vHb1SvXu3eE5dHS0y2Of5WvXdHS0yxM0bx8/pjO8kuKi/a+8LAfvOn9O5nOJfbU/3PRc+Miox72HpxnTRzHDpzRlwLxNJ3hKEwBmsnEHb1J49/CmWnv7z7OZdPnaNeFLAAAjZm3wfvubC3uqqvTPIS50nQ/HAADsmLXB6/vkY113fa8zGflAJgDAjnsTPAAAphnBAwCYQPAAACYQPACACQQPAGACwQMAmEDwAAAmEDwAgAkEDwBgAsEDAJhA8AAAJhA8AIAJBA8AYALBAwCYQPAAACYQPACACQQPAGACwQMAmEDwAAAmEDwAgAkEDwBgAsEDAJhA8AAAJhA8AIAJBA8AYALBAwCYQPAAACYQPACACQQPAGACwQMAmEDwAAAmEDwAgAkEDwBgAsEDAJhA8AAAJkxh8L76lSX/9q//Em6fLOVrv1e+9nvhdmCGGB4aHB4aTCRqw5dyGR4arIjHw+0A7t5EgiclE1u3bA4HuMPC7Xkk9v34m48vC7dHigze1i2bw43A3evt6ZaAjTFjzU2NnclkuD0/DV5nMtnc1BgOADBh4w6eZOyrX1kiv+apC8HDbNLb0+3OvYaHBvP3rLmpcQLFInjA1JlI8BL7fhxuLyku0mmf5NANnjspjNzlm48v05/1+N4B3eMk9v04DF5i34+9d9Ff3ZHum+oMdeuWzeFIQHnB85YfdeanudItEj/3Vy+HFfF4b0+3e0x3NhmeCYCJGXfwpCvhSqZOzrRzbvA0Wt98fJnbM91FfnBneOEB3ePIxjBO7gzvm48vc48gO7pvoUuy5Wu/F34iwBUGTydhvT3dssJZEY9HJk3ngp3JpPycP3jM8ICpMJHg6XxII+FmTKLy1a8s0VB5OZGN7uzK3VFqFHlA7zjhDC/PkqYcwTusnoO3HQiFwZNoJRK1mit3WOSSZiJRS/CAe2WCwRNbt2yWTrgLkrpa6AbPe1XqFZbJDV54QG+XyCN4wXMXOSV4bjLdX+UkyR5yCYPXmUwmErWJRK37MIs+z+ImzR1D8IB75a6C586cwumaG7ywTJGriG7wwle9XSInc+5Gb4E0nOGF5xBuAYQXPF291EmbR5PmTgGZ4QH30LiDpzfGvHtjekNOeTfz9MET99U89/ByHXDs9/C8weF5hn9WwdomcnGDJ7XTv0yI/CsFTZr79wl6D8+tYG9PN8EDpsG4gxeuE8pGfX5SFwbdh03cJUptjLuLPtLi7e4e0D1O5FOa7l7eYJnheYfVeaS76Bp+XiD8Ozz3Jemf0HS5czjdV4PnbnTjp8HTY4ZnAmBiJhK8WSMymQCAWclu8CIXWgEAs5W54OnSZXiPEAAwi5kLHgDAJoIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMCECQbvkYcf6uhov33rZjaTzmbSly99Go4Zo/2vvDw8NJjNpF/711d/f/G/jh17KxwzRZYvX3b50qf/8s8/CV+6J+rr6y5f+nTVqqfDl6bHgoaU/nD/oxvCAQBQuCYSvOXLl/3xDzeymfSFrvMHGvY3Hj3S29MdDhuL2MoV2Uy6v7+vvHxN2VNP3b5184P3O8Nhk6ip8Y3bt27Kz+vXVYyOpN5sbgqHTZHYyhV9n3zsvmP3xd+915mUn99sbhodSa1fVxHuOCkWNKTUvE0nIgd4W+5/dMP8PRfDkQBQcCYSvAtd50dHUs9t3Bi+NF7r11VkM+m21pbwpSly9szpbCYdbp8e4efNZtJnz5wOR06FsGd3HEDwAMwa4w6eXLLfPn4sfKmkuOi7q1d3X/zd6Egqm0lfu3plx/btsl0u628fPzY6kkp9Nvz64cOygicrosK7+u/Yvv3G9WvZTPrG9WtvvdWWzaTr6+tKiosuDQxcGhhwT0b6ISV7/fDh0ZFUW2vLqqe/o2dy4/q1Z575vuyrb3f2zGkvP/tfefna1SvZTHp0JNV1/tzy5cvynLyn8egRWZgdHhrcueMFmQd3nT83OpKSoy1etND7vIf+7TX31/r6urbWlmwmLTM8+Tg7tm+Xw8oRSoqLFi9a+F5nUg7b0dGu38biRQs7OtpTnw1nM+nbt27GVq4ITzLsWUlx0bxNJ2TOd/+jG9wlzTlLSueubtAZISucAArduIMnV21pj2f58mW3b928euXynqqqbZWVV69c1gU6qUhba8vGZzf09/dlM+nYyhWPPPzQizt3ZDPpEyd+VhaLucFbterp0ZHU1SuXt1VW1tW9JDcLxxK8s2dOSxgS+2rfPn6sLBarqa4eHUnJLt9+4lvnPvowm0mXxWKPL3vU3V0+1wfvd258dsOhgwdHR1L9/X3yLpEn737w6t27s5l0e/vPy2KxttaW+vq6xYsW9vf33b51c+OzG+QETp086X3er5eWlsVi2Uz63EcflsVijzz8UBi83/7mQnn5mlMdHdLykuKiUydPyj84ysvXfPB+ZzaTlo/2+uHD2Uz6Pw7/e1ks9sH7nZHromHw5q5u0LVNKZ+OnLOklBkegNlkMoP3ZnNTNpN+/rkfyK+rVj2dzaTfeefz62k2k/64t0e273/lZT2CN8fS4HmHeu3VV8cYvMiZjbuM6f7s7t7f33f92lXdpfHokWwmXbllc56TVxq8pX/3NZlsJfbVZjPpmppqGXDq5MnRkc9bkn9JMwyePMCyeNFCHfb5fPHcR7r7H29c94L3yMMPyS7uGQr3Hp7M2DRsOkB/IHgAZplxB69yy+ZsJt149Ej4kkw43C16mXYv624ycwXPO5S7S/7g6S4rnnzi5Lvv/uHGdVkSvGPwRkdS7r009x1znbyrrbVFlhO7zp/TuZon/Lx3DJ43rHztGm93d0nTXerMFbz8WwgegFls3MErKS66fu3q8NDgiief8LZHzvDk6pyrGbmCJzftdF1O5luyS39/3x9vXJftzzzz/VzBu37t6qWBgY3Pbvj2E9+a8AxP12MjT96zeNHCQwcPynqjzPBeSuwri8VU+HnHGzx3qifvODw0qPmXPxd5550T2Uz6n39yIDzDyODpDG/OklKCB2AWm0jwqnfvlpnEe51J+bOEgf++3aU33uQeXn9/3+hIShblcjUjV/Cef+4H8ucK2yorDzQ0yORJdpGbWO4dtcjgyX278vI1dXUvyaMrsl3mjnV1L9XX17nvLkuCcg/vQEPD6Eiq5/f/c6HPdfIqsa+28egRuV8owSstXSq3M7dVVpbFYocOHvzpoUMlxUUyRbvQdb6murp87Ro9z22VlTt3vHDH4JUUF3Wd+yibSbe+2bzx2Q1d5z7S25ONbxytq3upLBb7j8P/Pvbgzdt0gnt4AIyYSPBKiovi8bX6DGTqs+Hui7/T7X2ffCwreH2ffByPr5XtuZqRK3juH6R3nT8nM6fEvlp5NGbgvzs3PDQo2yOD9/rhw5LJs2dO/+eHv9aXnnnm+/JHhL88dcp79wMNDfJ0TOqz4fc6k3IzLM/Jqx9ues7dUZYTv7t6tX4V165eqd69WwZ3dLTLSAmePFY6OpLau7dmLMGThz/l4+9/5eVLAwPycM2Bhv36mKg7g3S59/A0Y/P3XMz1lKY7gKc0ARS6CQZvmrW3/zybSUshoJYvXyaz0vAlAIBn5gbvt7+5sKeqqrx8zdvHj8kyYDjGoMY3jjYePVJevmZPVdXVK5f1UVIAQH4zN3h9n3wsS6bDQ4PuAqNxBxr2u/8L010v7gzHAABCMzd4AABMIoIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMCEcQdv1XeeAgCg4Iw7eAAAFCKCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwITCCF5FPD48NJhI1IYvqeGhwYp4PNweqTOZ7Ewmw+1K3nHsBxyX5qbG3p7ucPsY9fZ0Dw8Nuu7maLk0NzXm/4oAoLAURvCkT/mvv1PXp9AEYtCZTDY3NYbb78aUfuQ7fsap+EQAMHWmI3j1/7tu65Yt4faxGx4avOP1Pf+rk+uOMQhNRR6m9CPf8TNOxScCgKkz5cHbW1PzZvP/+cU7J7ZtqwxfHYtEolauvOEVVhf3mpsa9eovV+rOZFJeki06THZ0r+ayWKrLg3pw94Duq3pkXWXVE5BjukuO3nnKMROJWlmE7O3pdtdp9Vf3fPIkzXtVd9GPKQeUjd656Y4yRrfr+bhfkfsNyADvE+U6Z/2upmLRFQDGZWqDV7t37/9ta7v/vjnzHpz7bvsvJjbP60wmNQPudbO3p1uv7HJhdfsku8h2GSZXZBnvBU8P29vT7W6viMflZp5siYyBjNRfK+JxtzfuGerP+kHc4+hG9zy9j+xx0+K2SrdLlnSjbnfXh2WMbHfvXLrn5pZPz8f7ROE5uyef//4rAEyDKQxe7d69J352/P775sivE2ue2xv3Uu5t9yZkbkUid/eCp9lwr9Fu8LxpVhi8yHmY14wweO6n0AHeRLa3pzvy4O77el1sbmqUI7gzSDdy7nhvlqnvHrmkGXnCuc7Z+/IB4N6aquDV7t37bvsv5j5wv7txAs1zF9Pcpblw6hMZs4p43B0WOSZ/8PQqn2u5Lwyeu+aZP3ju/DVy/dN7X4/7cSK/JTdmWsH8wdNh4b8bVPiJcp2z7pir2QAwbaYkeFW7d73b/ot5D84NX5r34Nxfnjr54s4d4UuRvMuxzjC8GV6utbhJCZ67u5xMnuC5GbjjDE9+lua5I3MVzuPO8MLZ2MSCF87w3MF5Znh5zjlylgwA02xKgrf5+ecjayfmf2le5ebN4fZQuG7pXqDdpzO8e3iTGLxEolYv5frWeYLn9sO9I5greLK7G4yxrwS67xs5ERxj8Nz66lvrZ3Q/rEwl5edc9/BUc1Oje3oED8C9NSXBmyyRf3vnPjfhLqBFxuzug6frmW5f9a01ve4R3HVF90x0cc8LXmcy6f6qXRHeSy73fd3z1O1jDJ77dnpA7yvST6Rhcz9R5Dm7X0UYYwCYZjM6eJgG3pImAMxWBM86ggfACIJnHcEDYATBAwCYQPAAACYQPACACQQPAGACwQMAmEDwAAAmEDwAgAkEDwBgAsEDAJhA8AAAJhA8AIAJBA8AYALBAwCYQPAAACYQPACACQQPAGACwQMAmEDwAAAmEDwAgAkEDwBgAsEDAJhA8AAAJhA8AIAJBA8AYEJhBG94aNATjpllmpsa5ZMmErXhqwCA8ZrpwUskasPa3X3wOpPJuz/IlCJ4ADC5ZnrwtHDudb+3pzscOXa9Pd2TUs0pRfAAYHLN6OBVxONy0b/LwnkIHgAYVBjBi7zuh8ubuv7Z3NSo65buESIXSHV3d3xnMqnb3UB6AzRLw0ODFfF4+BH0xNwDjsUdgxd+Om+AvtTb0x15NPfk9UsT7jfpDgvfBQAKxYwOXv7Lur6kG3WLWyl33zzBC7frtDI8lA6IHB/S9x179iITpcKTcQ/u/kPBo0eL/FD6ESK/KBGeDAAUhJkevMhLs8yl9LKuF3r9NXxJaucdUN/CLaV7ZNlFx8scKJzV6YA8kzw3QnnSqPIET89W52Re+8MB+tYyQA+u349ukV3c4MkA/Yzh+QBAQSiA4Am9/kZehd1rtFTHHex1KAyed3AlV39vfFhTrUWe4IXvnj97eYIX7u6dUjjAO1r4DXh7eYvDkVsAoLAUTPCElyK9CstNsvyLclqj8HLvjVTSj7EHL4xTLrJLnublOaZsJ3gAMC4FFjxv5U0v081NjXIR9y7HkQuJ4eU+LIRrcoPHDA8A7okZHbyKeNyrglxz3Qy4d9TcJuV6zDLyllt400s2Ru4+seBFpjePPMfM87SO1zP5NXzYNfx3Q657eAQPwKwx04OnV2pX5OTmjo8p6qvek5+5xueaEU4geHfzlKZHXg23e0/ohK8KPUP9UK5wQZjgAZg1ZnTwIq/LYVTCGU8YMG8v97CRGyPb4B18XMEbe+pE/uCF2fbe2m1eZzIZeYbeW7glC/MWbgGAwjLTgzcWEqSxrBOapXUcy3OkADArFXzwmHmEvFuYOpPj3wQALCvg4LlrelzKXeGKrghHAoAdsyF41M4TBo/pLwAUcPAAABg7ggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABNmevCGhwZFIlFbUlzUmUzKr53JZElxUXNTo/za29Md/loRj+vuFfE4R+NoHI2jeUeDKTM9eAAATAqCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABBWB4aDCRqA23R2puauxMJsPtw0ODFfF4uD2USNRGHgEoaAQPmCl6e7qHhwbV2AvnIXhAJIIHzBS9Pd1ukIaHBidWHYIHRCJ4BaOtteXSwEC4vSCsX1eRzaTXr6sIXxqXbCZdX18Xbh+juQ/c9+QT33ph29bt2yprqne/sH3rE//rH+Y+cF848p7wgucmSn+oiMd1CihjOpNJ+bW3p1u2SPB0u84U3eDpq81Nje7b6S4ED7PPjA7epYGBbCbtamttCYdhErW1trhfuGysr687e+a0/HxpYMDrbltrS0H8d/nLv/jzmurdsZXffnDuA7Jl/pfmlcVWVu3a+Zd/8Wfh+OkXBq8zmZQgheVzx+juMri5qVE7J4H09nWngL093TJSf7ibySUwk83o4AmZHITbMRXceumcMgyeW7iCCN4Xv/CFql07/2rxwvClJUv+6kd7quZ88QvhS9MsDF5zU6MXPDdLHh3sLWnmr6bslUjU6gSRJU3MVoUXvPXrKtwZxqWBAVkoO3vmtMxOZIusfeWZF7pTGV1qk4PozDLcy91RT+PsmdPelCjyBOTgehwdrLvrq5Gn0dbaotWR44ev6qHcxUMdKYuB4b7epwtP0guetz4ZGTzvXdz/iPqShFP2zXP+Ln0p8ivK47HHvhFb+W399W//5q//9m/+Wn9d9Z2n/v7rpeFe0ywMXmcyKXlzE+UtVMp8zl2f9IIXVtN9NEYmc17hCB5mpVkVPO+SKtfoyLtHkqLw+HIQPaBe5ZV7I02v1Lqlvr5Ofo48AX3VTYibsUsDA9KkyNNwR+qrekzJsOzunpLbOfkh3Nf7gGMJnrtvZPC879z9kvUl2ahfY+T55zps5FeUxwvbtj74wP+sZJYUFy348vwFX56vv857cO6W5zeFe00zL3iyGik/hyuZssVtW54ZnldNPazyZni5HnsBCtqsCp5edr1dNCS5tpw9c1pLoweJ7IG+nfKu7DIg1wm4ZfXy45Yj8jQ0eG759Jjh/M8Llb5FuK83wJ1oyuAweO5JRgbP+4b1lLwT8GZ43mCdC+q35M3wZHzkfynPnqpd7q+PL3v08WWPuluqdu0M95pmbvCkdpHPm+hgmYfpUyfePTwZn0jUhtWUR1q8d/feLhwAFDoTwdO6hHuJyNJ4byTCQnhb5L1ynYC+6s7AXHLRjzwNN3jeXuvXVUSWLExR5L65BugBI4On+QnfRV/11lHDU4oMXvitutsj/3OH/6U8u17c4f4aBm9P9f9XxHvC+zs896VwNVI7565MujM8PZq7Fqo/u+8lG93nP1nSxKxkN3h3nOFFXkbDQoxlhqfHl3K4F/3IK3vkabjBCwMTGQxvY6597zggV/BkahW5i5LvJ9cMz50mhucfmnDwXti6xV3SXPbYN5Y99g39dcGX52+v3BLuBWA2KbzguVc9mY5EXgEje6Nkx3DwHS+jZ8+cdu9deVu8e3iRJyAzJ21G5P2nyNPQJMjBw5lZGAwZqXMsnXp6+3rHCeuVK3hyqrkeC9Lx7pTXPQH5eXqC99hj3/jHVU/rr95DK9/9p3/0JnwAZp+CDJ4uu8nTDZFXwFy9CQ/ijbzjZVSfJNQLtG7R8XlOwA2kcP/cMPKzeMGTAnkLj7mCIWfiHjzc1zXe4Ml7hbvoW8hL7heiJxA+penu7h1Qt+f5ivKY88Uv/KimunTp18KXvv71pfv2/ui+GfBnCQCmVAEED7NY5L9Fpsif/emCmqrd3ymLzf/Sg7Jl3oNzn1q5oqa6aob84TmAKUXwcM+4fxwyPeR/LbZzx7Y91bte2LZ1pv2vxQBMKYKHaeUusea/mwgAk4vgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBMIHgAABMIHgDABIIHADCB4AEATCB4AAATCB4AwASCBwAwgeABAEwgeAAAEwgeAMAEggcAMIHgAQBM+H9Ew4MJ6ZfBmAAAAABJRU5ErkJggg==" width="592" height="374" class="img_ev3q"></li>
<li class="">WIN_D_CISL1_AdminTemplates-System-ConveniencePINSignIn-EXC_PROD
<img decoding="async" loading="lazy" alt="WIN_D_CISL1_AdminTemplates-System-ConveniencePINSignIn-EXC_PROD" src="data:image/png;base64,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" width="496" height="382" class="img_ev3q"></li>
</ul>]]></content:encoded>
            <category>Intune</category>
            <category>Settings Catalog</category>
        </item>
        <item>
            <title><![CDATA[Building an Intune Settings Catalog Report]]></title>
            <link>https://joeloveless.com/blog/building-an-intune-settings-catalog-report</link>
            <guid>https://joeloveless.com/blog/building-an-intune-settings-catalog-report</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Building an Intune Settings Catalog Report to compare policies, find unsupported SKUs, and find conflicting/duplicating and unique values.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/building-an-intune-settings-catalog-report-a4436c0833995e2c79ae36702ac532d5.png" width="1200" height="630" class="img_ev3q"></p>
<p>Happy April to everyone! Hard to believe that it is already spring-ish here in Minnesota. By spring in Minnesota, that means 70 degrees one day, 35 and sleeting the next. But the grass is starting to green up, which means more time outside for cleaning up the yard from winter. With 3 dogs that's a lot of fun. With 5 acres and wind, that's a lot of sticks and branches to pick up.</p>
<p>Last weekend we went to Omaha for the weekend, my daughter turned 15. Her and my wife went to see Six the Musical at the Orpheum Theatre. Omaha was fun. Not really what I expected it to be, but I mean that in a good way. My son and I ate some fried chicken and wandered around downtown for a bit while the musical was going on. Even just a 5 hour drive is exhausting these days.</p>
<p>We're well underway with more house projects. The upstairs flooring is finished, now we're working on remodeling the laundry room. The previous owners over engineered this DIY desk. It was a pain to take apart. I was finally able to use my Karate Kid 3 teachings of John Silver and kick some boards. Unlike Daniel LaRusso, I did not need to ice my foot after kicking said desk apart.</p>
<p>At work, I wanted a way to report on Intune Settings Catalog policies. I was looking to do the following:</p>
<ul>
<li class="">Report on 1 or multiple profiles<!-- -->
<ul>
<li class="">Report on the setting name + value (and any sub values)</li>
</ul>
</li>
<li class="">Find settings that are either duplicated/conflicting/unique.<!-- -->
<ul>
<li class="">I needed this for baseline comparison.</li>
</ul>
</li>
<li class="">Find what SKU is supported for these settings.<!-- -->
<ul>
<li class="">I need this for AVD Multi-Session fun.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-script">The Script<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#the-script" class="hash-link" aria-label="Direct link to The Script" title="Direct link to The Script" translate="no">​</a></h2>
<p>I've uploaded the script to my GitHub repo, direct link is here: <a href="https://github.com/Pacers31Colts18/Intune/blob/main/powershellscripts/Export-IntuneSettingsCatalogPolicyReport.ps1" target="_blank" rel="noopener noreferrer" class="">Export-IntuneSettingsCatalogPolicyReport.ps1</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="script-rundown">Script Rundown<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#script-rundown" class="hash-link" aria-label="Direct link to Script Rundown" title="Direct link to Script Rundown" translate="no">​</a></h3>
<p>Much of the script is a bit of combination of previous work done with the IntuneDocs, and other functions I've made from time to time to gather reporting data from the Graph API. I won't go through all of the script, but I'll give bits and pieces for context before explaining what all I was looking to do:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [CmdletBinding(DefaultParameterSetName = 'Interactive')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Param(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $false, ParameterSetName = 'ByName')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [string[]]$PolicyName,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $false, ParameterSetName = 'All')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [switch]$All,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [string]$OutputDir,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [ValidateSet('Markdown', 'Csv', 'All')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [string]$OutputFormat = 'Markdown'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    )</span><br></span></code></pre></div></div>
<p>First with the parameters.</p>
<ul>
<li class="">PolicyName<!-- -->
<ul>
<li class="">Search by one or many policies, or choose All if you want to report on everything. If you choose nothing, and OutGrid-View will display and you'll be able to select which policies. I typically use the OutGrid-View, mainly because I don't always remember the exact policy names.</li>
</ul>
</li>
<li class="">OutputDir<!-- -->
<ul>
<li class="">Optional, will default to the $pwd if not used.</li>
</ul>
</li>
<li class="">OutputFormat<!-- -->
<ul>
<li class="">Defaults to Markdown. I originally built this for a markdown report, but then thought having a csv report would be useful also. We typically do a lot of our manipulation based off CSV files.</li>
</ul>
</li>
</ul>
<p>After that, we then gather the Policies, the settings and then the SettingsDefinitions for each policy.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> #region Load all policies from Graph</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $uri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $response = Invoke-MgGraphRequest -Uri $uri -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $AllPolicies = $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    while ($response.'@odata.nextLink') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Invoke-MgGraphRequest -Uri $response.'@odata.nextLink' -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $AllPolicies += $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<p>Foreach policy, we're building a lookup table, and getting everything into the right format. Getting the sub values correct has always been tricky, this might not 100% gather all the down level data, but it should be enough for this report to get us "far enough".</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Select policies</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $Policies = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    switch ($PSCmdlet.ParameterSetName) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        'ByName' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($name in $PolicyName) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $p = $AllPolicies | Where-Object { $_.name -eq $name }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($p) { $Policies += $p }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                else { Write-Warning "Policy name '$name' not found." }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        'All' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $Policies = $AllPolicies</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        'Interactive' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $Policies = $AllPolicies |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Select-Object name, description, platforms, technologies, id |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Out-GridView -Title "Select one or more policies to include in the report" -PassThru</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($Policies.Count -eq 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Warning "No policies selected."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Collect settings for each policy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $PolicyDataList = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $SettingPolicyMap = @{}   # settingDefinitionId = List[policyName]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $SettingValueMap = @{}   # "settingDefId||value" = List[policyName]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($policy in $Policies) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Output "Processing: $($policy.name)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        # Get settings with definitions expanded</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $uri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($policy.id)')/settings?`$expand=settingDefinitions"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Invoke-MgGraphRequest -Uri $uri -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $rawSettings = $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        while ($response.'@odata.nextLink') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $response = Invoke-MgGraphRequest -Uri $response.'@odata.nextLink' -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $rawSettings += $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        # Build definition lookup: settingDefinitionId = definition object</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $DefLookup = @{}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($item in $rawSettings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($item.settingDefinitions) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                foreach ($def in $item.settingDefinitions) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    if (-not $DefLookup.ContainsKey($def.id)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $DefLookup[$def.id] = $def</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        # Flatten setting instances</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $PolicySettings = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($item in $rawSettings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $stack = [System.Collections.ArrayList]::new()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            [void]$stack.Add([PSCustomObject]@{ Instance = $item.settingInstance; Depth = 0 })</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            while ($stack.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $frame = $stack[$stack.Count - 1]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $stack.RemoveAt($stack.Count - 1)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $inst = $frame.Instance</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $depth = $frame.Depth</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if (-not $inst) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $defId = $inst.settingDefinitionId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $odataType = $inst.'@odata.type'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $def = if ($DefLookup.ContainsKey($defId)) { $DefLookup[$defId] } else { $null }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $displayName = if ($def) { $def.displayName } else { $defId }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $value = switch -Regex ($odataType) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    'choiceSettingInstance$' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $raw = $inst.choiceSettingValue.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        if ($def -and $def.options) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $opt = $def.options |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            Where-Object { $_.itemId -eq $raw } |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            Select-Object -First 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            if ($opt) { $opt.displayName } else { $raw }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        else { $raw }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    'simpleSettingInstance$' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        [string]$inst.simpleSettingValue.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    'simpleSettingCollectionInstance$' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        ($inst.simpleSettingCollectionValue | ForEach-Object { [string]$_.value }) -join '; '</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    'choiceSettingCollectionInstance$' {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $vals = foreach ($c in $inst.choiceSettingCollectionValue) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $cv = $c.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            if ($def -and $def.options) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                $opt = $def.options |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                Where-Object { $_.itemId -eq $cv } |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                Select-Object -First 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                if ($opt) { $opt.displayName } else { $cv }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            else { $cv }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $vals -join '; '</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    'groupSettingInstance$' { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    'groupSettingCollectionInstance$' { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    default { $odataType }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                # Info URL — first entry from infoUrls</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $infoUrl = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($def -and $def.infoUrls -and $def.infoUrls.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $infoUrl = "[Docs]($($def.infoUrls[0]))"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                # Platform from the definition applicability</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $defPlatform = if ($def -and $def.applicability -and $def.applicability.platform) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $def.applicability.platform</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                } else { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                # Windows SKUs — $null means not a Windows setting (SKUs not applicable).</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                # Empty array means Windows setting where all SKUs are supported.</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $isWindowsSetting = $defPlatform -match 'windows'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $supportedSkus = if (-not $isWindowsSetting) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $null</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                } elseif ($def.applicability.windowsSkus) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    @($def.applicability.windowsSkus)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                } else { @() }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $PolicySettings += [PSCustomObject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SettingDefinitionId = $defId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    DisplayName         = $displayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Value               = $value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Depth               = $depth</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    InfoUrl             = $infoUrl</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Platform            = $defPlatform</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    WindowsSkus         = $supportedSkus</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                # Push children in reverse so first child processes first</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $children = $null</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($odataType -match 'choiceSettingInstance$') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $children = @($inst.choiceSettingValue.children)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                elseif ($odataType -match 'groupSettingInstance$') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $children = @($inst.groupSettingValue.children)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                elseif ($odataType -match 'groupSettingCollectionInstance$') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $children = @($inst.groupSettingCollectionValue | ForEach-Object { $_.children })</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($children) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    for ($i = $children.Count - 1; $i -ge 0; $i--) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        [void]$stack.Add([PSCustomObject]@{ Instance = $children[$i]; Depth = $depth + 1 })</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        # Track settingDefinitionId = policies and settingDefId+value = policies</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($s in $PolicySettings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if (-not $SettingPolicyMap.ContainsKey($s.SettingDefinitionId)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $SettingPolicyMap[$s.SettingDefinitionId] = [System.Collections.Generic.List[string]]::new()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($SettingPolicyMap[$s.SettingDefinitionId] -notcontains $policy.name) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $SettingPolicyMap[$s.SettingDefinitionId].Add($policy.name)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $valueKey = "$($s.SettingDefinitionId)||$($s.Value)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if (-not $SettingValueMap.ContainsKey($valueKey)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $SettingValueMap[$valueKey] = [System.Collections.Generic.List[string]]::new()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($SettingValueMap[$valueKey] -notcontains $policy.name) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $SettingValueMap[$valueKey].Add($policy.name)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $PolicyDataList += [PSCustomObject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Name        = $policy.name</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Description = $policy.description</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Platform    = $policy.platforms</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Settings    = $PolicySettings</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # settingDefinitionId appears in multiple policies</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $DuplicateIds = $SettingPolicyMap.Keys | Where-Object { $SettingPolicyMap[$_].Count -gt 1 }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # settingDefinitionId appears in exactly 1 policy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $UniqueIds = $SettingPolicyMap.Keys | Where-Object { $SettingPolicyMap[$_].Count -eq 1 }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # same settingDefinitionId + same value in multiple policies</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $DuplicateValueKeys = $SettingValueMap.Keys | Where-Object { $SettingValueMap[$_].Count -gt 1 }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # settingDefinitionId in multiple policies with at least one differing value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $ConflictIds = $DuplicateIds | Where-Object {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $defId = $_</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        ($SettingValueMap.Keys | Where-Object { ($_ -split '\|\|', 2)[0] -eq $defId }).Count -gt 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<p>Here we are building the markdown report. A lot of this is just manipulating the output into the right markdown format and table format.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Build Markdown</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $md = [System.Text.StringBuilder]::new()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("# Intune Settings Catalog Policy Report")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("**Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm')")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Summary table</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("## Policies")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("| Policy Name | Platform | Top-Level Settings |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("| --- | --- | --- |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($pd in $PolicyDataList) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $topCount = ($pd.Settings | Where-Object { $_.Depth -eq 0 }).Count</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| $($pd.Name) | $($pd.Platform) | $topCount |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($DuplicateIds.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("&gt;  **Bold** = same setting in multiple policies with the same value.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("&gt; *Italic* = same setting in multiple policies with differing values.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Per-policy sections</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($pd in $PolicyDataList) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("---")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("### $($pd.Name)")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ($pd.Description) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $null = $md.AppendLine("*$($pd.Description)*")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("**Platform:** $($pd.Platform)")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| Setting | Sub-Setting | Value | Supported Platform | Supported SKUs | Unsupported SKUs |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| --- | --- | --- | --- | --- | --- |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $allSkus = @(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            'windowsHome', 'windowsProfessional', 'windowsEnterprise', 'windowsEducation', 'windowsMultiSession',</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            'windowsCloudN', 'windowsCPC', 'windows11SE', 'iotEnterprise', 'iotEnterpriseSEval',</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            'surfaceHub', 'hololens', 'hololensEnterprise', 'holographicForBusiness'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        )</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($s in $pd.Settings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $isDupe     = $DuplicateIds -contains $s.SettingDefinitionId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $valueKey   = "$($s.SettingDefinitionId)||$($s.Value)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $isConflict = $isDupe -and ($SettingValueMap[$valueKey].Count -lt $SettingPolicyMap[$s.SettingDefinitionId].Count)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            # Wrap display name in a doc link if one is available</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $linkedName = if ($s.InfoUrl) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $url = $s.InfoUrl -replace '^\[Docs\]\(|\)$', ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "[$($s.DisplayName)]($url)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            } else { $s.DisplayName }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $formatted = if ($isConflict) { "*$linkedName*" } elseif ($isDupe) { "**$linkedName**" } else { $linkedName }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($s.Depth -eq 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $settingCell    = $formatted</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $subSettingCell = ""</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $settingCell    = ""</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $subSettingCell = $formatted</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($null -eq $s.WindowsSkus) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $supportedCell   = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $unsupportedCell = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            } else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $supportedCell   = ($allSkus | Where-Object { $s.WindowsSkus.Count -eq 0 -or $s.WindowsSkus -contains $_ } | ForEach-Object { $_ -replace '^windows', '' }) -join '&lt;br&gt;'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $unsupportedCell = ($allSkus | Where-Object { $s.WindowsSkus.Count -gt 0 -and $s.WindowsSkus -notcontains $_ } | ForEach-Object { $_ -replace '^windows', '' }) -join '&lt;br&gt;'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $null = $md.AppendLine("| $settingCell | $subSettingCell | $($s.Value) | $($s.Platform) | $supportedCell | $unsupportedCell |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Conflicting Settings</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("---")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("## Values")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("### Conflicting Values")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($ConflictIds.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("The following settings are configured in multiple policies with different values.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| Setting | Policy | Value |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| --- | --- | --- |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($defId in ($ConflictIds | Sort-Object)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $displayName = $defId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($pd in $PolicyDataList) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $match = $pd.Settings | Where-Object { $_.SettingDefinitionId -eq $defId } | Select-Object -First 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($match) { $displayName = $match.DisplayName; break }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($policyName in ($SettingPolicyMap[$defId] | Sort-Object)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $pd  = $PolicyDataList | Where-Object { $_.Name -eq $policyName }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $val = ($pd.Settings | Where-Object { $_.SettingDefinitionId -eq $defId } | Select-Object -First 1).Value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $null = $md.AppendLine("| $displayName | $policyName | $val |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $displayName = ""   # only show setting name on first row</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("No conflicting settings found.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Duplicate Values</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("---")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("### Duplicate Values")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($DuplicateValueKeys.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("The following settings have identical values configured in multiple policies.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| Setting | Policy | Value |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| --- | --- | --- |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($key in ($DuplicateValueKeys | Sort-Object)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $defId, $val = $key -split '\|\|', 2</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $displayName = $defId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($pd in $PolicyDataList) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $match = $pd.Settings | Where-Object { $_.SettingDefinitionId -eq $defId } | Select-Object -First 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($match) { $displayName = $match.DisplayName; break }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($policyName in ($SettingValueMap[$key] | Sort-Object)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $null = $md.AppendLine("| $displayName | $policyName | $val |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $displayName = ""</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("No duplicate values found.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Unique Values</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("---")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("### Unique Values")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($UniqueIds.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("The following settings are configured in only one policy.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| Setting | Value | Policy |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("| --- | --- | --- |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($defId in ($UniqueIds | Sort-Object)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $policyName = $SettingPolicyMap[$defId][0]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $pd = $PolicyDataList | Where-Object { $_.Name -eq $policyName }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $match = $pd.Settings | Where-Object { $_.SettingDefinitionId -eq $defId } | Select-Object -First 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $displayName = if ($match) { $match.DisplayName } else { $defId }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $val = if ($match) { $match.Value } else { "" }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $null = $md.AppendLine("| $displayName | $val | $policyName |")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $null = $md.AppendLine("No unique values found.")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $null = $md.AppendLine("")</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Write output</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($OutputFormat -in 'Markdown', 'All') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $md.ToString() | Out-File -FilePath $OutputFilePath -Encoding UTF8</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if (Test-Path $OutputFilePath) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Output "Markdown file = $OutputFilePath."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Warning "No Markdown file created."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<p>And finally, the CSV format. For the CSV format, the only difference really is needing to add columns for the Unique, Duplicate, or Conflicting settings.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">if ($OutputFormat -in 'Csv', 'All') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $csvAllSkus = @(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            'windowsHome', 'windowsProfessional', 'windowsEnterprise', 'windowsEducation', 'windowsMultiSession',</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            'windowsCloudN', 'windowsCPC', 'windows11SE', 'iotEnterprise', 'iotEnterpriseSEval',</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            'surfaceHub', 'hololens', 'hololensEnterprise', 'holographicForBusiness'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        )</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $csvRows = foreach ($pd in $PolicyDataList) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($s in $pd.Settings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $supported   = if ($null -eq $s.WindowsSkus) { '' } else { ($csvAllSkus | Where-Object { $s.WindowsSkus.Count -eq 0 -or $s.WindowsSkus -contains $_ } | ForEach-Object { $_ -replace '^windows', '' }) -join '; ' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $unsupported = if ($null -eq $s.WindowsSkus) { '' } else { ($csvAllSkus | Where-Object { $s.WindowsSkus.Count -gt 0 -and $s.WindowsSkus -notcontains $_ } | ForEach-Object { $_ -replace '^windows', '' }) -join '; ' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $valueKey    = "$($s.SettingDefinitionId)||$($s.Value)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                [PSCustomObject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Policy             = $pd.Name</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Setting            = if ($s.Depth -eq 0) { $s.DisplayName } else { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SubSetting         = if ($s.Depth -gt 0) { $s.DisplayName } else { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Value              = $s.Value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    InfoUrl            = $s.InfoUrl -replace '^\[Docs\]\(|\)$', ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SupportedPlatform  = $s.Platform</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SupportedSKUs      = $supported</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    UnsupportedSKUs    = $unsupported</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Unique             = if ($UniqueIds -contains $s.SettingDefinitionId) { 'x' } else { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Duplicate          = if ($DuplicateValueKeys -contains $valueKey) { 'x' } else { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Conflicting        = if ($ConflictIds -contains $s.SettingDefinitionId) { 'x' } else { '' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $csvRows | Export-Csv -Path $CsvOutputFilePath -NoTypeInformation -Encoding UTF8</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if (Test-Path $CsvOutputFilePath) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Output "CSV file = $CsvOutputFilePath."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Warning "No CSV file created."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reasoning">Reasoning<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#reasoning" class="hash-link" aria-label="Direct link to Reasoning" title="Direct link to Reasoning" translate="no">​</a></h2>
<p>Why is all this needed? Well, I have multiple reasons.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reason-1-operating-system-sku">Reason #1: Operating System SKU<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#reason-1-operating-system-sku" class="hash-link" aria-label="Direct link to Reason #1: Operating System SKU" title="Direct link to Reason #1: Operating System SKU" translate="no">​</a></h3>
<p>Once a policy is created, there isn't really a great way to tell within Intune if a policy is actually applicable to the device you are applying it to.</p>
<p>When you create a new policy, it's somewhat easier, but not the most intuitive either. You have to ensure you are filtering before creating the policy, or choosing the setting:</p>
<p><img decoding="async" loading="lazy" alt="Settings Picker" src="https://joeloveless.com/assets/images/settings-picker-8730bb5d67fe3a6a784cbc1dd6cb2b83.png" width="493" height="560" class="img_ev3q"></p>
<p>After the profile is created, there is no way to filter from this view to the supported OS.</p>
<p><img decoding="async" loading="lazy" alt="Created Profile" src="https://joeloveless.com/assets/images/created-profile-1c2a05b562d8fffe261db76ae42837ab.png" width="887" height="580" class="img_ev3q"></p>
<p>At that point, to really know what is applying correctly is to do the following steps:</p>
<ol>
<li class="">Go to the device</li>
<li class="">Click Device Configuration</li>
<li class="">Open a policy</li>
<li class="">Look at all the settings, find ones that say Not Applicable.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Not Applicable" src="https://joeloveless.com/assets/images/not-applicable-748161f5012d4b815387790a90064f8e.png" width="509" height="241" class="img_ev3q"></p>
<p>Now do that for each profile you create. We've mainly ran into that with Azure Virtual Desktop Multi-Session hosts.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="policy-csp-documentation">Policy CSP Documentation<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#policy-csp-documentation" class="hash-link" aria-label="Direct link to Policy CSP Documentation" title="Direct link to Policy CSP Documentation" translate="no">​</a></h4>
<p>To make matters even worse, when you go to the CSP page in the Microsoft Learn documentation, there is 0 reference to that SKU (or all the SKUs)</p>
<p><a href="https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-storage#configstoragesenseglobalcadence" target="_blank" rel="noopener noreferrer" class="">Storage Sense CSP</a></p>
<p><img decoding="async" loading="lazy" alt="Storage Sense Learn" src="https://joeloveless.com/assets/images/storage-sense-b76cf11965bf3d4060b4edfcd128c2ce.png" width="877" height="362" class="img_ev3q"></p>
<p>If you go by the CSP documentation, Intune supports the following:</p>
<ul>
<li class="">Pro</li>
<li class="">Enterprise</li>
<li class="">Education</li>
<li class="">IoT Enterprise / IoT Enterprise LTSC</li>
</ul>
<p>After doing a dump from the Graph API, I found references to the following SKUs:</p>
<ul>
<li class="">WindowsHome</li>
<li class="">WindowsProfessional</li>
<li class="">WindowsEnterprise</li>
<li class="">WindowsEducation</li>
<li class="">WindowsMultiSession</li>
<li class="">WindowsCloudN</li>
<li class="">WindowsCPC</li>
<li class="">Windows11SE</li>
<li class="">iotEnterprise</li>
<li class="">iotEnterpriseSEval</li>
<li class="">surfaceHub</li>
<li class="">hololens</li>
<li class="">hololensenterprise</li>
<li class="">holographicForBusiness</li>
</ul>
<p>Maybe the Holo/Surface settings are referenced directly on policies that are supported, but for the most part, this seems to be pretty inaccurate. With the Learn documentation not referencing MultiSession, it would seem that none of the settings are supported (when in fact they are). Until you get into it, you don't really see what is applying and not applying.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reason-2-reporting-on-one-or-multiple-profiles">Reason #2: Reporting on One or Multiple Profiles<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#reason-2-reporting-on-one-or-multiple-profiles" class="hash-link" aria-label="Direct link to Reason #2: Reporting on One or Multiple Profiles" title="Direct link to Reason #2: Reporting on One or Multiple Profiles" translate="no">​</a></h3>
<p>Another reason for this, is wanting to compare multiple profiles. In the Group Policy days, we had PolicyAnalyzer, which allowed us to compare multiple GPOs and there settings, allowing for easy de-duping of policies when doing migrations. There really isn't a great way to do that in Intune. I believe I've seen some sites out there that allow you to connect your tenant to their site and compare policies. Personally, not too fond of that idea. I wanted something that I had more control over, and can just run on an ad-hoc basis.</p>
<p>Currently, we're running the enterprise flavor of the CIS benchmark, but have been talking of switching over to the Intune flavor. There is also the Windows Baseline that is in Intune that comes from Microsoft. Using this, I can download the .json files from CIS' Build Kit, import them into my tenant and then compare to the Microsoft baseline.</p>
<p><img decoding="async" loading="lazy" alt="OutGrid-View" src="https://joeloveless.com/assets/images/outgrid-view-5428e2bdf5a927dc421f95232ea4020e.png" width="905" height="839" class="img_ev3q"></p>
<p>Here I am selecting multiple profiles (and then the Microsoft benchmark).</p>
<p>I now have this beautiful report here in both CSV or Markdown format:</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/output/Export-IntuneSettingsCatalogPolicyReport-20260405-1042.md" target="_blank" rel="noopener noreferrer" class="">Markdown</a></p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/output/Export-IntuneSettingsCatalogPolicyReport-20260405-1042.csv" target="_blank" rel="noopener noreferrer" class="">CSV</a></p>
<p>Looking at the report, I can now see the conflicts between what Microsoft recommends vs. CIS recommendation.</p>
<p><img decoding="async" loading="lazy" alt="Conflicts" src="https://joeloveless.com/assets/images/conflicts-eefb9cc33167522f183d39016c8f7b4c.png" width="1445" height="942" class="img_ev3q"></p>
<p>If we wanted, we could compare ALL the policies in our tenant and easily find what is being set in multiple places. Intune will show you if there is a policy conflict (although not always accurate), but that's only if the setting is applied to the same group multiple times. What if there are two policies going out to two different groups? We'd want to consolidate that and have less policies.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-Up<a href="https://joeloveless.com/blog/building-an-intune-settings-catalog-report#wrap-up" class="hash-link" aria-label="Direct link to Wrap-Up" title="Direct link to Wrap-Up" translate="no">​</a></h2>
<p>Well that's it, I might have another post here before MMS in May, but we'll see. Yard work, hikes, camping, all that fun stuff is on the horizon. Hopefully this post (and script) is useful for others. Let me know if you run into any issues or see anything that could be of value for additions.</p>]]></content:encoded>
            <category>Intune</category>
            <category>Graph API</category>
            <category>PowerShell</category>
        </item>
        <item>
            <title><![CDATA[Introducing EndpointFeed.com]]></title>
            <link>https://joeloveless.com/blog/introducing-endpointfeed.com</link>
            <guid>https://joeloveless.com/blog/introducing-endpointfeed.com</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A new website built with Claude Code to aggregate endpoint community RSS feeds.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Introducing EndpointFeed.com" src="https://joeloveless.com/assets/images/introducing-endpointfeed.com-eb8e1d74f44895625e9f62ab3ab91ea8.png" width="1200" height="630" class="img_ev3q"></p>
<p>Happy March everyone! I don't have a ton of updates as far as life events goes. It's the middle of March, and where I am at, all the snow had melted away....and then we got dumped on overnight with a good amount of snow. Mother Nature has a fun way of torturing us in Minnesota, every time you think you are in the clear weatherwise, there are other plans in store. Last night the mrs. and I went out for a St. Patrick's Day event at a local bar we go to. A fun time was had, but the next day really hits me. I'm still on the hunt for something overseas, fingers still crossed! If anyone has any leads, reach out!</p>
<p>Other than that, hotel is officially booked for MMS. Radison Blu...too expensive, already booked. CountryInn, also already booked. I waited too long. I'm back at the Cambria for a short little walk or bus ride each morning. Also no continental breakfast....bummer. I'm really looking forward to going to MMS again, I think this is Year 6 for me, not all consecutive.</p>
<p>Recently, I've been exploring a little bit more in the AI space, mainly out of curiosity and not wanting to be left behind in technology. I've used CoPilot and ChatGPT here and there, nothing too extensive and didn't really find them to be all that valuable. They help yes, but  it can take a full day to get it to where you really want it to be. I haven't really explored the other models all that much, mainly just hearing about the latest on HackerNews, Reddit, or random YouTube videos and Podcasts. I have plenty of concerns with how they will work in a corporate environment, and if we are just heading backwards. I think back to the days of home grown applications, the developer leaving and nobody knowing how anything works. We've all been there as System Administrators. Now we are introducing applications with no developer ever being there, just fully AI coded applications? After years of cleaning up messes and trying to get everything standardized with vendor supported applications, here we are again.</p>
<p>/rant over</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="algorithm-frustration">Algorithm Frustration<a href="https://joeloveless.com/blog/introducing-endpointfeed.com#algorithm-frustration" class="hash-link" aria-label="Direct link to Algorithm Frustration" title="Direct link to Algorithm Frustration" translate="no">​</a></h2>
<p>Recently, I've became increasingly frustrated with the Internet and the constant barrage of the algorithms. The 90's-mid 2000's era of the Internet hold a special place in my heart. I miss the days of forums, random websites on Geocities/Angelfire, chat rooms, and Napster/Limewire/ShareBear/Kazaa, whatever other P2P music sites there were. While I am sure I look at that with rose colored glasses, and I am really just turning into an old man, I wish things could come back to that. Social media moderation is unachievable after you hit a certain user limit. I like the ActivityPub protocol, but don't love the Twitter style feed. Mastodon seems to have missed it's mark somewhat and there aren't the communities it needs (or I can't find them). BlueSky seems like Twitter but with just another layer on top, still being controlled by BlueSky itself.</p>
<p>Part of the reason I started this blog was because of the frustrations of leaving social media sites, and not having anything out there. I wanted a place of my own, whether people actually read it or not. I've always read other people's blogs, watched their YouTube videos, etc. At the same time, being able to keep up with it is a hard thing to achieve when there is so much out there.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="homelabbing-with-rss">HomeLabbing with RSS<a href="https://joeloveless.com/blog/introducing-endpointfeed.com#homelabbing-with-rss" class="hash-link" aria-label="Direct link to HomeLabbing with RSS" title="Direct link to HomeLabbing with RSS" translate="no">​</a></h2>
<p>Wanting to get out of rabbit holes of YouTube, X, Reddit, BlueSky, etc. I started going back to RSS feeds. While there are plenty of free apps out there that will gladly give you some features as long as you provide you their e-mail address and data, they give you just enough to make you want to pay for the subscription. I am also fatigued with so many subscriptions these days. Of course, I've blogged about my homelab, and went down the rabbit hole of setting up a <a href="https://freshrss.org/index.html" target="_blank" rel="noopener noreferrer" class="">FreshRSS</a> instance. I can't recommend it enough, pretty great free backend for RSS. I then tied it in with FocusReader, which is an Android application for reading the sites you aggregate.</p>
<p>That said, to read articles on the go while out and about, I'd need to connect to my TailScale instance to then connect back to my house. With Starlink being hit and miss at times, it was just frustrating enough to annoy me.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-not-a-public-rss">Why not a public RSS?<a href="https://joeloveless.com/blog/introducing-endpointfeed.com#why-not-a-public-rss" class="hash-link" aria-label="Direct link to Why not a public RSS?" title="Direct link to Why not a public RSS?" translate="no">​</a></h2>
<p>I then got an idea in my head, why do I need to have this just on my network, and just for my consumption. I looked around for a while, and couldn't really find a great setup that was easy and out of the box to achieve this. I wanted an RSS list on the backend, and a nice interface on the front end. I didn't care who could access it. If it is just myself that is accessing it, that's fine. If others get value out of it too, even better. There are plenty of free community tools for the endpoint management space, maybe someone will find value in mine?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="im-not-a-developer-and-am-still-learning-github-actions">I'm not a developer, and am still learning GitHub Actions<a href="https://joeloveless.com/blog/introducing-endpointfeed.com#im-not-a-developer-and-am-still-learning-github-actions" class="hash-link" aria-label="Direct link to I'm not a developer, and am still learning GitHub Actions" title="Direct link to I'm not a developer, and am still learning GitHub Actions" translate="no">​</a></h2>
<p>Having the idea in my brain, I decided to use Claude to try to achieve this.</p>
<p><img decoding="async" loading="lazy" alt="Claude Prompt" src="https://joeloveless.com/assets/images/claude-prompt-a54f5f069c18556649b23787e96af075.png" width="868" height="906" class="img_ev3q"></p>
<p>I went back and forth with Claude for about 25 different responses before I finally was satisfied with what I had. Most of that back and forth was trying to get the layout and theme to what I wanted it to be. While I could have done the CSS myself, having done this for a subreddit for my favorite NBA basketball team <strong><a href="https://old.reddit.com/r/pacers" target="_blank" rel="noopener noreferrer" class="">r/Pacers</a></strong>, and for my site.....it was a quicker win with Claude.</p>
<p>The gist of the backend for the website is this:</p>
<ul>
<li class="">/scripts/fetch-feeds.js<!-- -->
<ul>
<li class="">A node.js script that runs on a schedule (GitHub Action) that loops through hardcoded URLs in the script. These are all curated by me, and I'm adding to them overtime. I've asked fellow co-workers for help, gathering what RSS feeds they have. I'm not close enough to the Apple community to really know what's out there enough, so using their RSS feeds was helpful.</li>
<li class="">GitHub action runs every 4 hours.</li>
<li class="">What I had for the Microsoft Learn RSS that was working on my FreshRSS instance was choking here. Fellow co-workers RSS was working better.</li>
<li class="">Some feeds return an error. It seems to be an issue with the blogging platform Ghost. I use <a href="https://rssfinder.app/" target="_blank" rel="noopener noreferrer" class="">https://rssfinder.app</a> to find RSS feeds when I can't find a link on the site.</li>
<li class="">YouTube feeds get the thumbnail and description extracted, podcasts get the audio URLs and duration.</li>
<li class="">This is done in batch jobs of 4 in parallel, trying for 8 seconds before moving onto the next feed.</li>
</ul>
</li>
<li class="">Once the GitHub action finishes, the action commits to /docs/feeds.json</li>
<li class="">GitHub Pages then serves that to an index.html file.</li>
</ul>
<p>For the frontend:</p>
<ul>
<li class="">A vanilla JS and CSS index.html site.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-final-product">The final product<a href="https://joeloveless.com/blog/introducing-endpointfeed.com#the-final-product" class="hash-link" aria-label="Direct link to The final product" title="Direct link to The final product" translate="no">​</a></h2>
<p>With that, I now have <a href="https://endpointfeed.com/" target="_blank" rel="noopener noreferrer" class="">EndpointFeed.com</a></p>
<p>In one evening, I was able to get this stood up to show to my co-workers. From there, I added a New section, and have added websites here and there. If you see any sites that you think should be added, feel free to shoot me a message and I can get them added. Now I just need to duplicate this for all the other RSS categories I have on FreshRSS and stand up new sites.</p>]]></content:encoded>
            <category>Intune</category>
            <category>MECM</category>
            <category>Endpoint</category>
        </item>
        <item>
            <title><![CDATA[Exporting Intune Assignments for Groups in an Administrative Unit]]></title>
            <link>https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit</link>
            <guid>https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit</guid>
            <pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A new PowerShell function for exporting Intune assignments for groups in an administrative unit using Graph API with batching.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Exporting Intune Assignments for Groups in an Administrative Unit" src="https://joeloveless.com/assets/images/exporting-intune-assignments-for-groups-in-an-administrative-unit-143d17052a99cd0d71a0922b4b322745.png" width="1200" height="630" class="img_ev3q"></p>
<p>My motivation is high lately, and I am wanting to keep the momentum going. Life has been somewhat busy on the home front, 2nd semester is in full swing for the kids. My son is taking a 7th Grade Computer Science class using code.org, so I've been helping him the most with that. The interface seems to be Scratch based, which we've dabbled with in the past. He seems to be gaining interest in computers, using that and the Lego Studio app (which seems to be a really great introduction to CAD). My daughter has a pretty heavy load of science this semester with Physical Science and Astronomy classes. I'm currently decluttering the house, selling random items on Facebook Marketplace to gear up for a potential move. I feel like I've made a pretty good haul so far, but still have so many items I need to get rid of. It's amazing what you can gather, especially when you consider yourself to not have a ton of possessions anyways. Hopefully I have more at some point on a potential move, very excited about all the different possibilities. When not trying to sell off random items, I've been heavily playing retro games. I just beat Super Mario Bros for NES, thanks to being able to save games mainly. Not nearly as frustrating as on the original NES with only so many lives.</p>
<p><img decoding="async" loading="lazy" alt="Super Mario Bros." src="https://joeloveless.com/assets/images/mario-9be96c67cdb0ce00e7eb6900f80ab010.png" width="1280" height="720" class="img_ev3q"></p>
<p>My post this week is about gathering Intune assignments for groups in an Administrative Unit. I've seen many solutions out there for either exporting all policy assignments, or individual group assignments. In our org, we have two administrative units.</p>
<ol>
<li class="">One for the endpoint staff to create groups in that only we control.</li>
<li class="">Another to create groups in that gives Desktop Support/Service Desk the ability to add/remove membership.</li>
</ol>
<p>As we're trying to have proper hygiene in our environment, it's important to be able to cleanup groups. It's especially important to cleanup groups and make sure we're not deleting anything that is currently in use.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="github-scripts">GitHub Scripts<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#github-scripts" class="hash-link" aria-label="Direct link to GitHub Scripts" title="Direct link to GitHub Scripts" translate="no">​</a></h2>
<p>The scripts used in this blog post can be found here:</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/powershellscripts/Export-IntuneGroupAssignmentsForAdministrativeUnit.ps1" target="_blank" rel="noopener noreferrer" class="">Export-IntuneGroupAssignmentsforAdministrativeUnit.ps1</a></p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/powershellscripts/Invoke-MgGraphBatchRequest.ps1" target="_blank" rel="noopener noreferrer" class="">Invoke-MgGraphBatchRequest.ps1</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-an-administrative-unit">What is an Administrative Unit?<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#what-is-an-administrative-unit" class="hash-link" aria-label="Direct link to What is an Administrative Unit?" title="Direct link to What is an Administrative Unit?" translate="no">​</a></h2>
<p>Microsoft can definitely explain this better than me, here is the <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/administrative-units" target="_blank" rel="noopener noreferrer" class="">Microsoft Learn</a> documentation on it. In our scenario, it's a way to control and segment out groups for Intune use. That said, you can also segment out devices, users, or groups.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="required-permissions">Required Permissions<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#required-permissions" class="hash-link" aria-label="Direct link to Required Permissions" title="Direct link to Required Permissions" translate="no">​</a></h2>
<p>For this to work properly, you need proper Graph permissions. There are tons of ways to go about this, we run everything through delegated permissions both in my lab and at my org. Here is what I have setup with an App Registration:</p>
<p><img decoding="async" loading="lazy" alt="API Permissions" src="https://joeloveless.com/assets/images/api_permissions-f65dcdc59e6ac54662b060bb0de2529a.png" width="1031" height="430" class="img_ev3q"></p>
<p><em>Note: Read permissions are needed in this scenario, although the screenshot shows Read/Write, I use the app for other purposes.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="powershell-challenges">PowerShell Challenges<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#powershell-challenges" class="hash-link" aria-label="Direct link to PowerShell Challenges" title="Direct link to PowerShell Challenges" translate="no">​</a></h2>
<p>When you are talking at an enterprise level, we're talking thousands of groups. On top of that, we're looking at multiple different API calls to Microsoft Graph for all the different scenarios.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $resources = @(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Configuration Policy'; Uri = 'deviceManagement/configurationPolicies'; AssignUri = 'deviceManagement/configurationPolicies/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Device Compliance Policy'; Uri = 'deviceManagement/deviceCompliancePolicies'; AssignUri = 'deviceManagement/deviceCompliancePolicies/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Device Configuration'; Uri = 'deviceManagement/deviceConfigurations'; AssignUri = 'deviceManagement/deviceConfigurations/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Device Health Script'; Uri = 'deviceManagement/deviceHealthScripts'; AssignUri = 'deviceManagement/deviceHealthScripts/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Group Policy Configuration'; Uri = 'deviceManagement/groupPolicyConfigurations'; AssignUri = 'deviceManagement/groupPolicyConfigurations/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Windows Autopilot'; Uri = 'deviceManagement/windowsAutopilotDeploymentProfiles'; AssignUri = 'deviceManagement/windowsAutopilotDeploymentProfiles/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Device Enrollment Configuration'; Uri = 'deviceManagement/deviceEnrollmentConfigurations'; AssignUri = 'deviceManagement/deviceEnrollmentConfigurations/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Device Shell Scripts'; Uri = 'deviceManagement/deviceShellScripts'; AssignUri = 'deviceManagement/deviceShellScripts/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        @{ Name = 'Device Management Scripts'; Uri = 'deviceManagement/deviceManagementScripts'; AssignUri = 'deviceManagement/deviceManagementScripts/{id}/assignments' }</span><br></span></code></pre></div></div>
<p>In my use case for this, I'm only looking at what's in the deviceManagement section of the Graph API. Yes, this needs to be expanded to include application assignments, app configuration policies, etc. But this is a starting point to build upon things. As you can see, we have 9 different sections of Microsoft graph to search through. With a normal ForEach loop for each group, this is going to take a long time. I initially started out that way, and quickly found out (well, quickly in hours) that I would need to find a different way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="graph-api-batching">Graph API Batching<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#graph-api-batching" class="hash-link" aria-label="Direct link to Graph API Batching" title="Direct link to Graph API Batching" translate="no">​</a></h2>
<p>So then I went down the rabbit hole of Graph API batching. I don't consider myself an expert in this, and I am sure there are better ways of doing things. I <strong>think</strong> I got it going on best practices, but Microsoft's docs are a little confusing on how to accomplish this. It's also not natively built into the Graph commands, which is a little frustrating.I found other resources to be more helpful for starting out.</p>
<ul>
<li class=""><a href="https://learn.microsoft.com/en-us/graph/json-batching?tabs=http" target="_blank" rel="noopener noreferrer" class="">Microsoft Learn</a></li>
<li class=""><a href="https://www.jorgeasaur.us/supercharge-microsoft-graph-api-data-retrieval-with-powershell-batch-requests/" target="_blank" rel="noopener noreferrer" class="">Supercharge Microsoft Graph API Data Retrieval with PowerShell Batch Requests</a></li>
<li class=""><a href="https://manima.de/2023/09/microsoft-graph-json-batching-using-powershell/" target="_blank" rel="noopener noreferrer" class="">Microsoft Graph JSON batching using PowerShell</a>
<ul>
<li class="">This post contains a ton of great information in the Footnotes.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="script-walkthrough">Script Walkthrough<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#script-walkthrough" class="hash-link" aria-label="Direct link to Script Walkthrough" title="Direct link to Script Walkthrough" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="invoke-mggraphbatchrequest">Invoke-MgGraphBatchRequest<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#invoke-mggraphbatchrequest" class="hash-link" aria-label="Direct link to Invoke-MgGraphBatchRequest" title="Direct link to Invoke-MgGraphBatchRequest" translate="no">​</a></h3>
<p>The first thing I needed was a helper function that would help me loop through multiple requests.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">function Invoke-MgGraphBatchRequest {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">[CmdletBinding()]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">param (</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [Parameter(Mandatory)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [array]$Requests</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$batchUri = "https://graph.microsoft.com/beta/`$batch"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$body = @{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    requests = $Requests</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">} | ConvertTo-Json -Depth 10</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">(Invoke-MgGraphRequest -Method POST -Uri $batchUri -Body $body).responses</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="export-intunegroupassignmentsforadministrativeunit">Export-IntuneGroupAssignmentsforAdministrativeUnit<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#export-intunegroupassignmentsforadministrativeunit" class="hash-link" aria-label="Direct link to Export-IntuneGroupAssignmentsforAdministrativeUnit" title="Direct link to Export-IntuneGroupAssignmentsforAdministrativeUnit" translate="no">​</a></h3>
<p>With the helper function above, I am now ready for the main script walkthrough. As with a lot of what we write, much of this is repeatable code.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">[CmdletBinding()]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">param (</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [Parameter(Mandatory = $true)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [string]$AdministrativeUnit,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [ValidateSet('All', 'Assigned', 'Unassigned')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [string]$Scope = 'All'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Microsoft Graph Connection check</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">if (-not (Get-MgContext)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Write-Error "Authentication needed. Please connect to Graph."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Declarations</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$FunctionName = $MyInvocation.MyCommand.Name.ToString()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$date = Get-Date -Format yyyyMMdd-HHmm</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">if ($outputdir.Length -eq 0) { $outputdir = $pwd }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$OutputFilePath = "$OutputDir\$FunctionName-$date.csv"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$results = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$graphApiversion = "beta"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">#endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Resources</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$resources = @(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Configuration Policy'; Uri = 'deviceManagement/configurationPolicies'; AssignUri = 'deviceManagement/configurationPolicies/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Device Compliance Policy'; Uri = 'deviceManagement/deviceCompliancePolicies'; AssignUri = 'deviceManagement/deviceCompliancePolicies/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Device Configuration'; Uri = 'deviceManagement/deviceConfigurations'; AssignUri = 'deviceManagement/deviceConfigurations/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Device Health Script'; Uri = 'deviceManagement/deviceHealthScripts'; AssignUri = 'deviceManagement/deviceHealthScripts/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Group Policy Configuration'; Uri = 'deviceManagement/groupPolicyConfigurations'; AssignUri = 'deviceManagement/groupPolicyConfigurations/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Windows Autopilot'; Uri = 'deviceManagement/windowsAutopilotDeploymentProfiles'; AssignUri = 'deviceManagement/windowsAutopilotDeploymentProfiles/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Device Enrollment Configuration'; Uri = 'deviceManagement/deviceEnrollmentConfigurations'; AssignUri = 'deviceManagement/deviceEnrollmentConfigurations/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Device Shell Scripts'; Uri = 'deviceManagement/deviceShellScripts'; AssignUri = 'deviceManagement/deviceShellScripts/{id}/assignments' },</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    @{ Name = 'Device Management Scripts'; Uri = 'deviceManagement/deviceManagementScripts'; AssignUri = 'deviceManagement/deviceManagementScripts/{id}/assignments' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">)</span><br></span></code></pre></div></div>
<ul>
<li class="">
<p>The Parameters:</p>
<ul>
<li class="">$AdministrativeUnit<!-- -->
<ul>
<li class="">The AU we are going to search through.</li>
</ul>
</li>
<li class="">$Scope</li>
<li class="">We get three options. All/Assigned/Unassigned. This will default to All unless chosen otherwise.</li>
</ul>
</li>
<li class="">
<p>The Resources mapping:</p>
<ul>
<li class="">I used this as a way to combine the assignURI and regular URI along with a short name for the CSV export column.</li>
</ul>
</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-the-administrative-unit-details">Getting the Administrative Unit details<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#getting-the-administrative-unit-details" class="hash-link" aria-label="Direct link to Getting the Administrative Unit details" title="Direct link to Getting the Administrative Unit details" translate="no">​</a></h4>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Get AU</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $uri = "https://graph.microsoft.com/$graphApiVersion/directory/administrativeUnits?`$filter=displayName eq '$AdministrativeUnit'"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $au = (Invoke-MgGraphRequest -Method GET -Uri $uri).value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($au) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Output "Administrative Unit: $($au.displayName) found."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if (-not $au) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Error "Administrative Unit '$AdministrativeUnit' not found."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Get Groups in AU</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $groups = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $uri = "https://graph.microsoft.com/$graphApiVersion/directory/administrativeUnits/$($au.id)/members"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    do {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Invoke-MgGraphRequest -Method GET -Uri $Uri</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ($response.value -and $response.value.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $groups += $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        elseif ($response -and $response.PSObject.Properties.Name -ne '@odata.context') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $groups += $response</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $Uri = $response.'@odata.nextLink'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    } while ($Uri)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $groups = $groups | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($groups) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Output "Found $($groups.Count) groups in Administrative Unit."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if (-not $groups) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Warning "No groups found in Administrative Unit."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $groupMap = @{}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($g in $groups) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $groupMap[$g.id] = @{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            GroupId   = $g.id</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            GroupName = $g.displayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Assigned  = $false</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Items     = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<ul>
<li class="">From here, we're going to ensure the AU actually exists.</li>
<li class="">Next, we're going to get all the group members.</li>
<li class="">Then we're going to make a mapping for the groups, this allows us to store the groups, putting the Assigned to $false by default.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="processing-the-resources">Processing the Resources<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#processing-the-resources" class="hash-link" aria-label="Direct link to Processing the Resources" title="Direct link to Processing the Resources" translate="no">​</a></h4>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Process each resource</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">foreach ($resource in $resources) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Write-Output "Scanning $($resource.Name)..."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Get objects</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $objects = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource.Uri)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    do {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Invoke-MgGraphRequest -Method GET -Uri $Uri</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        # Normalize response</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ($response.value -and $response.value.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $objects += $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        elseif ($response -and $response.PSObject.Properties.Name -ne '@odata.context') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $objects += $response</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $Uri = $response.'@odata.nextLink'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    } while ($Uri)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if (-not $objects) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Normalize names</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($obj in $objects) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $name = $obj.displayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if (-not $name) { $name = $obj.name }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if (-not $name) { $name = "" }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $obj | Add-Member -NotePropertyName NormalizedName -NotePropertyValue $name -Force</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<p>Now that we have the groups stored, we can then process each resource from the $resources array from above. One of the joys of Microsoft Graph is the inconsistency in certain spots. Sometimes we get "name", sometimes we get "displayName". So we have to be aware that not everything is the same all the time. A fun challenge sometimes.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="batching">Batching<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#batching" class="hash-link" aria-label="Direct link to Batching" title="Direct link to Batching" translate="no">​</a></h4>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $batchSize = 20</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $chunks = for ($i = 0; $i -lt $objects.Count; $i += $batchSize) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        , $objects[$i..([Math]::Min($i + $batchSize - 1, $objects.Count - 1))]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($chunk in $chunks) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $requests = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $i = 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($obj in $chunk) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if (-not $obj) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $assignUrl = $resource.AssignUri.Replace("{id}", $obj.id)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $requests += @{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                id        = "$i"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                method    = 'GET'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                url       = "/$assignUrl"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                ObjectRef = $obj</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $i++</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ($requests.Count -eq 0) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $responses = Invoke-MgGraphBatchRequest -Requests $requests</span><br></span></code></pre></div></div>
<ul>
<li class="">Graph has a limit of 20 requests per batch. What we are doing here is splitting this up into chunks of 20, and then looping through and getting the assignments, rather than one by one. We're also replacing the id from the resource mapping with the actual <strong>$obj.id</strong></li>
<li class="">We're then sending all that to the Invoke-MgGraphBatchRequest to then process.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="processing-the-requests">Processing the Requests<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#processing-the-requests" class="hash-link" aria-label="Direct link to Processing the Requests" title="Direct link to Processing the Requests" translate="no">​</a></h4>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">foreach ($req in $requests) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $response = $responses | Where-Object { $_.id -eq $req.id }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if (-not $response) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $obj = $req.ObjectRef</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if (-not $obj) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($response.status -ne 200) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    continue</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $assignments = $response.body.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if (-not $assignments) { continue }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                foreach ($assignment in $assignments) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    #Include Assignments</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    if ($assignment.target.'@odata.type' -eq '#microsoft.graph.groupAssignmentTarget') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $gid = $assignment.target.groupId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        if ($gid -and $groupMap.ContainsKey($gid)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $groupMap[$gid].Assigned = $true</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $groupMap[$gid].AssignmentType = "Include"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $groupMap[$gid].Items += [pscustomobject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                PolicyType = $resource.Name</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                PolicyId   = $obj.id</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                PolicyName = $obj.NormalizedName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    #Exclude Assignments</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    if ($assignment.target.'@odata.type' -eq '#microsoft.graph.exclusionGroupAssignmentTarget') {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $gid = $assignment.target.groupId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        if ($gid -and $groupMap.ContainsKey($gid)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $groupMap[$gid].Assigned = $true</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $groupMap[$gid].AssignmentType = "Exclude"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $groupMap[$gid].Items += [pscustomobject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                PolicyType = $resource.Name</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                PolicyId   = $obj.id</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                                PolicyName = $obj.NormalizedName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<ul>
<li class="">Now we're taking the requests and processing them for the assignments. Initially, I didn't factor in the exclusion assignments and had to figure that in.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-and-exporting-the-results">Building and Exporting the Results<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#building-and-exporting-the-results" class="hash-link" aria-label="Direct link to Building and Exporting the Results" title="Direct link to Building and Exporting the Results" translate="no">​</a></h4>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Build results</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($g in $groupMap.Values) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ( $Scope -eq 'All' -or ($Scope -eq 'Assigned' -and $g.Assigned) -or ($Scope -eq 'Unassigned' -and -not $g.Assigned)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            #Assigned groups</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($g.Items.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                foreach ($item in $g.Items) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    $results += [pscustomobject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        GroupId        = $g.GroupId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        GroupName      = $g.GroupName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        Assigned       = $true</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        AssignmentType = $g.AssignmentType</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        PolicyType     = $item.PolicyType</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        PolicyId       = $item.PolicyId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        PolicyName     = $item.PolicyName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                #Unassigned groups</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $results += [pscustomobject]@{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    GroupId    = $g.GroupId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    GroupName  = $g.GroupName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    Assigned   = $false</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    AssignmentType = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    PolicyType = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    PolicyId   = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    PolicyName = ''</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Export results</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($results.Count -gt 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $results | Sort-Object GroupName | Export-Csv -Path $outputFilePath -NoTypeInformation</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Output "Output file = $outputFilePath"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Warning "No output file created."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span></code></pre></div></div>
<ul>
<li class="">I'm a sucker for a good CSV file. Some make pretty HTML reports, JSON files, whatever else. I prefer my data to be in a CSV, mainly for the ability to filter it all down.</li>
<li class="">We're building arrays for both the Assigned and Unassigned groups.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="csv-results">CSV Results<a href="https://joeloveless.com/blog/exporting-intune-assignments-for-groups-in-an-administrative-unit#csv-results" class="hash-link" aria-label="Direct link to CSV Results" title="Direct link to CSV Results" translate="no">​</a></h2>
<p>After running, we now have a CSV file:</p>
<p><img decoding="async" loading="lazy" alt="CSV File" src="https://joeloveless.com/assets/images/csv_results-c499f9159aa4e6be1ba278a154dd38c0.png" width="1436" height="179" class="img_ev3q"></p>
<ul>
<li class="">From here, we can take this CSV file, and build upon this with a function to remove the groups if not assigned.</li>
</ul>]]></content:encoded>
            <category>Intune</category>
            <category>Graph API</category>
            <category>Entra</category>
            <category>PowerShell</category>
        </item>
        <item>
            <title><![CDATA[Intune Documentation as Code]]></title>
            <link>https://joeloveless.com/blog/intune-documentation-as-code</link>
            <guid>https://joeloveless.com/blog/intune-documentation-as-code</guid>
            <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Configure and export Intune documentation automatically, converting JSON files to Markdown. All made possible with PowerShell scripts, and GitHub Actions.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Intune Documentation as Code" src="https://joeloveless.com/assets/images/intune-documentation-as-code-1cd79d49b31d08792933cb27c6a4b2bf.png" width="1200" height="630" class="img_ev3q"></p>
<p>Greetings everyone! It's almost February, winter is still in full swing, and I'm getting cranky from the cold weather. I'm looking forward to spring, and possible new adventures, I'm wanting to ride my bike more and do some camping. I've been selling off old junk on Facebook Marketplace, and trying to de-clutter what I have (not the random box of cables of course, you never know when you will need the random power cables or bluetooth adapters). In the meantime, I've been working on a solution to document our Intune policies in an automated fashion using GitHub Actions on a daily (or weekly, not sure yet?) schedule. With that, I want to write about it, mainly because I am a little pumped about it and think it's going to be a pretty nice solution. There is a lot to it, so let's dive into it.</p>
<p><a href="https://github.com/Pacers31Colts18/Intune.Docs" target="_blank" rel="noopener noreferrer" class="">Github Repository</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-overview">Code Overview<a href="https://joeloveless.com/blog/intune-documentation-as-code#code-overview" class="hash-link" aria-label="Direct link to Code Overview" title="Direct link to Code Overview" translate="no">​</a></h2>
<p>Since I mainly work on the configuration side of things, and not much with application packaging, or the other parts of Intune, this backup solution is focused mainly on policies. I think I've made this to be pretty expandable though, as long as you are aware of the differences in different sections of the Graph API for Intune. For this, within each section of the <a href="https://learn.microsoft.com/en-us/graph/api/resources/intune-device-mgt-conceptual?view=graph-rest-beta" target="_blank" rel="noopener noreferrer" class="">/deviceManagement</a> area of Graph API, I have made the following:</p>
<ul>
<li class="">A function that exports the JSON configuration for the policy.</li>
<li class="">A function that exports the JSON assignments for the policy.</li>
<li class="">A function that converts the JSON configuration to Markdown format to make it pretty.</li>
<li class="">A function that converts the JSON assignments to Markdown format to make it pretty.</li>
</ul>
<p>This takes place for these sections within the API:</p>
<ul>
<li class=""><strong>deviceManagement/configurationPolicies</strong>
<ul>
<li class="">Settings Catalog</li>
</ul>
</li>
<li class=""><strong>deviceManagement/deviceCompliancePolicies</strong></li>
<li class=""><strong>deviceManagement/deviceConfigurationPolicies</strong>
<ul>
<li class="">Templates</li>
</ul>
</li>
<li class=""><strong>deviceManagement/deviceEnrollmentConfigurations</strong></li>
<li class=""><strong>deviceManagement/deviceHealthScripts</strong>
<ul>
<li class="">Proactive Remediations</li>
</ul>
</li>
<li class=""><strong>deviceManagement/deviceManagementScripts</strong>
<ul>
<li class="">Platform Scripts</li>
</ul>
</li>
<li class=""><strong>deviceManagement/deviceShellScripts</strong>
<ul>
<li class="">Mac Shell Scripts</li>
</ul>
</li>
<li class=""><strong>deviceManagement/groupPolicyConfigurations</strong>
<ul>
<li class="">Custom ADMX Templates</li>
</ul>
</li>
<li class=""><strong>deviceManagemnt/windowsAutopilotDeploymentProfiles</strong></li>
</ul>
<p>Within each function for the exports, it's using:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Get-IntuneDeviceManagementPolicy</span><br></span></code></pre></div></div>
<p>This is a helper function that allows the code to be repeatable, calling those API sections.</p>
<p>From there, the two main functions that do the majority of the work for the GitHub Actions are:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Invoke-IntuneDocumentation</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Invoke-IntuneAssignmentDocumentation</span><br></span></code></pre></div></div>
<p>Both functions have two parameters, <strong>docType</strong> and <strong>outputPath</strong></p>
<ul>
<li class="">docType<!-- -->
<ul>
<li class="">This defaults to All, which processes all the Export commands in the function.</li>
<li class="">Can be switched to specific endpoints, such as deviceManagementScripts, etc.</li>
</ul>
</li>
<li class="">outputPath<!-- -->
<ul>
<li class="">Where to export the files to.</li>
</ul>
</li>
</ul>
<p>Some of this could have probably been de-duplicated, but there were enough differences in the different sections that after a bit of time, to me it made a little more sense to just have the same set of functions for each section of the API. Made it easier for myself to follow, and hopefully easier for others to follow too.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pre-requisites">Pre-Requisites<a href="https://joeloveless.com/blog/intune-documentation-as-code#pre-requisites" class="hash-link" aria-label="Direct link to Pre-Requisites" title="Direct link to Pre-Requisites" translate="no">​</a></h2>
<p>There are some moving pieces to this setup, so some things to be aware of:</p>
<ol>
<li class="">Need access to create an Azure App Registration</li>
<li class="">Need a GitHub Repository</li>
<li class="">Need an Intune tenant</li>
</ol>
<p>With that said, this is also something that can be ran manually without the App Registration or using GitHub Actions, but this post will walk through mainly the GitHub Actions scenario. I'll call out the spots for a manual configuration.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="azure-app-registration">Azure App Registration<a href="https://joeloveless.com/blog/intune-documentation-as-code#azure-app-registration" class="hash-link" aria-label="Direct link to Azure App Registration" title="Direct link to Azure App Registration" translate="no">​</a></h2>
<p>Within the <a href="https://portal.azure.com/" target="_blank" rel="noopener noreferrer" class="">Azure Portal</a>, go to App Registrations.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-permissions">API Permissions<a href="https://joeloveless.com/blog/intune-documentation-as-code#api-permissions" class="hash-link" aria-label="Direct link to API Permissions" title="Direct link to API Permissions" translate="no">​</a></h3>
<ol>
<li class="">Create a new App Registration, giving it a proper name.</li>
<li class="">Choose API permissions from the left hand side, and add the following permissions:<!-- -->
<ol>
<li class="">DeviceManagementConfiguration.Read.All</li>
<li class="">DeviceManagementScripts.Read.All</li>
<li class="">DeviceManagementServiceConfig.Read.All</li>
<li class="">GroupMember.Read.All</li>
</ol>
</li>
<li class="">All API permissions should be Application based permissions<!-- -->
<ul>
<li class=""><strong>Note: If running manually, you can add Delegated permissions</strong></li>
</ul>
</li>
<li class="">Don't forget to Grant admin consent</li>
</ol>
<p><img decoding="async" loading="lazy" alt="API Permissions" src="https://joeloveless.com/assets/images/azure_appreg_api-6744fa186917358ff1151acf4fc0f779.png" width="1050" height="381" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="federated-credentials">Federated Credentials<a href="https://joeloveless.com/blog/intune-documentation-as-code#federated-credentials" class="hash-link" aria-label="Direct link to Federated Credentials" title="Direct link to Federated Credentials" translate="no">​</a></h3>
<ol>
<li class="">Next go to Certificates &amp; Secrets and choose <strong>Federated Credentials</strong></li>
<li class="">Choose <strong>GitHub Actions deploying Azure Resources</strong> from the drop down menu.</li>
<li class="">Fill out the rest of the form, filling in the details of your GitHub Repository.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Federated Credentials" src="https://joeloveless.com/assets/images/azure_appreg_federated-6c3574c57ee8fdee2cbfbd478706b88c.png" width="926" height="899" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://joeloveless.com/blog/intune-documentation-as-code#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h3>
<ol>
<li class="">Once finished, go to the Overview page of your Azure App Registration and take note of your Tenant ID and Application ID, you'll need this later on.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Overview" src="https://joeloveless.com/assets/images/azure_appreg_overview-e641d54febbd99d78f5ccc6ae0c0e7ae.png" width="921" height="306" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="github-configuration">GitHub Configuration<a href="https://joeloveless.com/blog/intune-documentation-as-code#github-configuration" class="hash-link" aria-label="Direct link to GitHub Configuration" title="Direct link to GitHub Configuration" translate="no">​</a></h2>
<p>With the Azure portion configured, we can now configure GitHub. Start by cloning the repository <a href="https://github.com/Pacers31Colts18/Intune.Docs" target="_blank" rel="noopener noreferrer" class="">here</a>, however you see fit to do.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="environment-variables">Environment Variables<a href="https://joeloveless.com/blog/intune-documentation-as-code#environment-variables" class="hash-link" aria-label="Direct link to Environment Variables" title="Direct link to Environment Variables" translate="no">​</a></h3>
<p>In GitHub, go to Settings for your repository, and we're going to setup a couple of environment variables. Pay attention to the uppercase/lowercase, as this will matter. GraphApi should also match exactly to what you put in during the Azure App Registration process.</p>
<p><img decoding="async" loading="lazy" alt="GitHub Repo Secrets" src="https://joeloveless.com/assets/images/github_environments-91f018771668e38a9014c3060579c67a.png" width="1163" height="289" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="secrets">Secrets<a href="https://joeloveless.com/blog/intune-documentation-as-code#secrets" class="hash-link" aria-label="Direct link to Secrets" title="Direct link to Secrets" translate="no">​</a></h3>
<ul>
<li class="">Next under Settings, go to the Security section and choose <strong>Secrets and variables</strong> and then <strong>Actions</strong>.</li>
<li class="">Create two new secrets:<!-- -->
<ul>
<li class="">AZURE_CLIENT_ID</li>
<li class="">AZURE_TENANT_ID</li>
</ul>
</li>
<li class="">For each, add the values from your Azure App Registration.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Github Secrets" src="https://joeloveless.com/assets/images/github_repository_secrets-5d19b0f24cfacb8afaa9c2589e209634.png" width="895" height="686" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ruleset">Ruleset<a href="https://joeloveless.com/blog/intune-documentation-as-code#ruleset" class="hash-link" aria-label="Direct link to Ruleset" title="Direct link to Ruleset" translate="no">​</a></h3>
<ul>
<li class="">Now go to <strong>Rules</strong> and choose <strong>Rulesets</strong></li>
<li class="">Create a new ruleset, naming it whatever you like.</li>
<li class="">The settings should be as follows (some are automatically checked):<!-- -->
<ul>
<li class="">Enforcement Status: Active</li>
<li class="">Target Branch &gt; Include by Pattern &gt; <strong>main</strong> (or whatever the name of your branch is, might be master).</li>
<li class="">Restrict deletions</li>
<li class="">Require a pull request before merging.</li>
<li class="">Require status checks to pass.<!-- -->
<ul>
<li class="">Require branches to be up to date before merging.</li>
<li class="">Status checks that are required:<!-- -->
<ul>
<li class="">Status-Gate</li>
</ul>
</li>
</ul>
</li>
<li class="">Block force pushes.</li>
</ul>
</li>
<li class="">Save the changes.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Github Ruleset" src="https://joeloveless.com/assets/images/github_ruleset_1-89c06f26d082f5896abe783f9082eec2.png" width="850" height="1237" class="img_ev3q">
<img decoding="async" loading="lazy" alt="Github Ruleset" src="https://joeloveless.com/assets/images/github_ruleset_2-e67ce1c5530b738ce243ad807e762334.png" width="882" height="834" class="img_ev3q">
<img decoding="async" loading="lazy" alt="Status Check" src="https://joeloveless.com/assets/images/github_ruleset_statuscheck-b83bb52a12599b0bd33cc1b999fdbcb2.png" width="757" height="397" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="github-actions">GitHub Actions<a href="https://joeloveless.com/blog/intune-documentation-as-code#github-actions" class="hash-link" aria-label="Direct link to GitHub Actions" title="Direct link to GitHub Actions" translate="no">​</a></h3>
<p>Under the settings, click <strong>Actions</strong>, and go to General. Then go to Workflow permissions, and check the following:</p>
<ul>
<li class="">Read and Write Permissions</li>
<li class="">Allow GitHub Actions to create and approve pull requests</li>
</ul>
<p><img decoding="async" loading="lazy" alt="GitHub Workflow Permissions" src="https://joeloveless.com/assets/images/github_actions_workflowpermissions-68e7979f568e6c7d4ba5ca8d3e869466.png" width="1333" height="1088" class="img_ev3q"></p>
<p><em>Note: If you are working in an Enterprise tenant for GitHub, you might have to have these settings changed at the organizational or even Enterprise level, depending on your setup. For our environment, I was unable to change this until an admin changed the setting at the organization level</em></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="github-actions-files-overview">GitHub Actions Files Overview<a href="https://joeloveless.com/blog/intune-documentation-as-code#github-actions-files-overview" class="hash-link" aria-label="Direct link to GitHub Actions Files Overview" title="Direct link to GitHub Actions Files Overview" translate="no">​</a></h4>
<p>There are three GitHub Actions files, located at .github/workflows of the repo:</p>
<p><strong>GitHubAction-JL-IntuneDocs.yml</strong></p>
<ul>
<li class="">This is the main file. In my file, I have this set to run at 9:00 p.m. CST every night. Once this kicks off, the following will happen:<!-- -->
<ul>
<li class="">All files at this folder will be removed.</li>
</ul>
</li>
</ul>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">"$</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> env.REPO_DIR </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">/Docs"</span><br></span></code></pre></div></div>
<ul>
<li class="">Will login to Azure.</li>
</ul>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> DEV Azure Login</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> azure/login@v2</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">client-id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.AZURE_CLIENT_ID </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">tenant-id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.AZURE_TENANT_ID </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">allow-no-subscriptions</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:rgb(255, 88, 116)">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">enable-AzPSSession</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:rgb(255, 88, 116)">true</span><br></span></code></pre></div></div>
<ul>
<li class="">Will generate new markdown files:</li>
</ul>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Generate Markdown Files</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">shell</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> pwsh</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          $GraphTokenResponse = az account get-access-token --resource https://graph.microsoft.com</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          $GraphToken = ($GraphTokenResponse | ConvertFrom-Json).accessToken</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          $SecureToken = ConvertTo-SecureString $GraphToken -AsPlainText -Force</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          Install-Module Microsoft.Graph.Authentication -Force -AllowClobber</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          Install-Module Microsoft.Graph.Beta.DeviceManagement -Force -AllowClobber</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          Connect-MgGraph -AccessToken $SecureToken</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          Import-Module -Name "${{ env.REPO_DIR }}" -Force</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          Invoke-IntuneDocumentation -docType all -outputPath "${{ env.REPO_DIR }}/Docs"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          Invoke-IntuneAssignmentDocumentation -doctype All -outputPath "${{ env.REPO_DIR }}/Docs"</span><br></span></code></pre></div></div>
<ul>
<li class="">Configure Git</li>
<li class="">Set the branch name</li>
<li class="">Commit to the branch and do a push</li>
<li class="">Then create a pull request</li>
</ul>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic"># CONFIGURE GIT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Configure Git</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          git config --global user.name "Joe Loveless"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          git config --global user.email "joe@joeloveless.com"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic"># SET BRANCH NAME</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Set branch name</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> vars</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> echo "branch_name=JL</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">IntuneDocs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">$(date +'%Y</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">%m</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">%d')" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"> $GITHUB_OUTPUT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic"># COMMIT and PUSH</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Commit changes</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          cd "${{ env.REPO_DIR }}"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          git checkout -b ${{ steps.vars.outputs.branch_name }}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          git add .</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          git commit -m "Policies: $(date +'%Y-%m-%d')" || echo "No changes to commit"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          git push origin ${{ steps.vars.outputs.branch_name }}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic"># CREATE PR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Create Pull Request via REST API</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> pr</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          echo "Creating PR from branch: ${{ steps.vars.outputs.branch_name }}"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          RESPONSE=$(curl </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">w "%</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain">http_code</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">o response.json </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">X POST \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token key atrule">-H "Authorization</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Bearer $GITHUB_TOKEN" \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token key atrule">-H "Accept</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> application/vnd.github+json" \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            https</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">//api.github.com/repos/$</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> github.repository </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">/pulls \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">d @</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> &lt;&lt;EOF</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token key atrule">"title"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"Automated Intune Docs Update"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token key atrule">"head"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"${{ steps.vars.outputs.branch_name }}"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token key atrule">"base"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"main"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token key atrule">"body"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"Automated documentation update for $(date +'%Y-%m-%d')."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          EOF</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          )</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">echo "HTTP status</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $RESPONSE"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          echo "Response body</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          cat response.json</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          PR_NUMBER=$(jq </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">r '.number' response.json)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          echo "pr_number=$PR_NUMBER" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"> $GITHUB_OUTPUT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></span></code></pre></div></div>
<p>Once that is successful, a status check will run. This is basically ensuring that the first Action completed successfully, ensuring we don't have a messy merge process.</p>
<p><strong>GitHubAction-JL-StatusCheck.yml</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Find PR created by JL</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">IntuneDocs workflow</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> find_pr</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          echo "Searching for PR with branch prefix: JL-IntuneDocs-"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          PR=$(gh pr list </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">state open </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">json number</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain">headRefName \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">               </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">jq '.</span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token plain"> select(.headRefName </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token plain"> startswith("JL</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">IntuneDocs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">")) </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token plain"> .number')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">echo "PR</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $PR"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          echo "pr_number=$PR" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"> $GITHUB_OUTPUT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Get PR commit SHA</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> get_sha</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">if</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> steps.find_pr.outputs.pr_number </span><span class="token tag" style="color:rgb(255, 85, 114)">!=</span><span class="token plain"> '' </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          PR=${{ steps.find_pr.outputs.pr_number }}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          SHA=$(gh pr view "$PR" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">json headRefOid </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">jq '.headRefOid')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">echo "PR commit SHA</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $SHA"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          echo "sha=$SHA" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"> $GITHUB_OUTPUT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Set status check on PR commit</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">if</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> steps.get_sha.outputs.sha </span><span class="token tag" style="color:rgb(255, 85, 114)">!=</span><span class="token plain"> '' </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          SHA="${{ steps.get_sha.outputs.sha }}"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          echo "Setting status on PR commit: $SHA"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          gh api \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            repos/$</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> github.repository </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">/statuses/$SHA \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">f state=success \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">f context="Status</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">Gate" \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">f description="All required jobs completed successfully."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></span></code></pre></div></div>
<p><strong>GitHubAction-JL-AutoMerge.yml</strong></p>
<p>The last action, is to then merge the pull request into the main branch. After authentication, it's going to find the latest pull request, and then merge it into the main branch.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/checkout@v4</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Install GitHub CLI</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> sudo apt</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">get install </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">y gh</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Authenticate GitHub CLI</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> echo "$</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">" </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token plain"> gh auth login </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">with</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">token</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Find PR created by JL</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">IntuneDocs workflow</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> find_pr</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">|</span><span class="token scalar string" style="color:rgb(195, 232, 141)"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          PR=$(gh pr list --state open --json number,headRefName \</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">               --jq '.[] | select(.headRefName | startswith("JL-IntuneDocs-")) | .number')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token scalar string" style="color:rgb(195, 232, 141)">          echo "pr_number=$PR" &gt;&gt; $GITHUB_OUTPUT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Enable auto</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">merge (rebase)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">if</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> steps.find_pr.outputs.pr_number </span><span class="token tag" style="color:rgb(255, 85, 114)">!=</span><span class="token plain"> '' </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> gh pr merge $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> steps.find_pr.outputs.pr_number </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">auto </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">squash </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">delete</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">branch</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="markdown-output">Markdown Output<a href="https://joeloveless.com/blog/intune-documentation-as-code#markdown-output" class="hash-link" aria-label="Direct link to Markdown Output" title="Direct link to Markdown Output" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="policy-documents">Policy Documents<a href="https://joeloveless.com/blog/intune-documentation-as-code#policy-documents" class="hash-link" aria-label="Direct link to Policy Documents" title="Direct link to Policy Documents" translate="no">​</a></h3>
<p>My initial goal for this project was to convert the entire JSON file to Markdown, using PSObjects. In attempting to do that, starting with the Settings Catalog section, I found so many different variables, that it became a battle I didn't want to fight anymore.</p>
<p>For Settings Catalog alone, there are the following different setting types:</p>
<ul>
<li class="">optionValue</li>
<li class="">choiceSettingValue</li>
<li class="">simpleSettingValue</li>
<li class="">ConfigurationStringSettingValue</li>
<li class="">choiceSettingCollectionValue</li>
<li class="">groupSettingCollectionValue</li>
</ul>
<p>Within the old Templates area, I ran into differences with OMA-URI, Certificates, templates, etc.</p>
<p>Similar with the Group Policy area too, which was more annoying because that is the least used section in our org....</p>
<p>And I think I am probably missing some. Within those, I then ran into child items...and then more child items....and more child items (and probably even more). It got to the point where every time I went back to this project, I'd confuse myself more on where I was at with it.</p>
<p>At some point, I ran across <a href="https://github.com/sandytsang" target="_blank" rel="noopener noreferrer" class="">Sandy Zeng's GitHub</a> and found the idea of outputting the JSON for each setting instead. With that, I then came up with what I wanted to run with on this.</p>
<p>I'm taking out the items I find most valuable within each setting:</p>
<ul>
<li class="">Name</li>
<li class="">Description</li>
<li class="">Full URI</li>
<li class="">InfoURL</li>
</ul>
<p>For certain configurations (Scripts, XML, Certificates), the conversion functions will decode the files so you can get the actual script content displayed. Here you can see the example output from one of the default Remedation scripts from Microsoft. <a href="https://github.com/Pacers31Colts18/Intune.Docs/blob/main/Docs/DeviceHealthScripts/Update_stale_Group_Policies.md" target="_blank" rel="noopener noreferrer" class="">Update Stale Group Policies</a></p>
<p>The rest is the json file, split out for each setting. My <a href="https://github.com/Pacers31Colts18/Intune.Docs" target="_blank" rel="noopener noreferrer" class="">repo</a> has examples from my Intune test lab that you can check out.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="assignment-documents">Assignment Documents<a href="https://joeloveless.com/blog/intune-documentation-as-code#assignment-documents" class="hash-link" aria-label="Direct link to Assignment Documents" title="Direct link to Assignment Documents" translate="no">​</a></h3>
<p>When the <strong>Invoke-IntuneAssignmentDocumentation</strong> runs, a subfolder is created under /Docs/Assignments. The JSON for the Policy documents will create an assignments link, based off the same name of the policy. From there you can click that link in the markdown file, and go into the Assignments page.</p>
<p>The assignments page will give you each group assigned to the policy. I have another <a href="https://github.com/Pacers31Colts18/Intune.Docs/blob/main/Public/Helpers/Get-GroupClassification.ps1" target="_blank" rel="noopener noreferrer" class="">helper function</a> within this that takes the GroupID, and looks up the actual Entra group display name. If the group Id is 'adadadad-808e-44e2-905a-0b7873a8a531', then the output will be All Devices, if the group ID is 'acacacac-9df4-4c7d-9d50-4ef0226f57a9', the output will be All Users.</p>
<p>The assignments page also gives you the JSON output, similar to what is being done with the policies, just on an assignment basis and not a setting basis.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-thoughts">Final Thoughts<a href="https://joeloveless.com/blog/intune-documentation-as-code#final-thoughts" class="hash-link" aria-label="Direct link to Final Thoughts" title="Direct link to Final Thoughts" translate="no">​</a></h2>
<p>What I like about this configuration is the ability to have branches, so you can get a historical view of how your policies or assignments have changed over time. With GitHub, you have a nice repository you can search for keywords off of, something Intune is sorely lacking today. Publish this repository to an internal GitHub Pages setup, and then you have a nice documentation page you can give to your Service Desk or Desktop Support staff for easier troubleshooting.</p>
<p>Let me know what you think, would love to gather some feedback or potential issues that you see with this!</p>]]></content:encoded>
            <category>Intune</category>
            <category>GitHub</category>
            <category>Azure</category>
            <category>PowerShell</category>
        </item>
        <item>
            <title><![CDATA[Configuring Windows Autopilot in a Hyper-V Lab]]></title>
            <link>https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab</link>
            <guid>https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab</guid>
            <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A guide to configuring Windows Autopilot and setting up a VM in Hyper-V for Autopilot testing.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Configuring Windows Autopilot in a Hyper-V Lab" src="https://joeloveless.com/assets/images/configuring-windows-autopilot-in-a-hyperv-lab-b21817cbd66f1b87dcae4614850231eb.png" width="1200" height="630" class="img_ev3q"></p>
<p>Happy weekend everyone, I'm back with another post for the month. I'd like to say I've been busy and productive, but that's not really the case. I am trying to lay low and stay sane at the moment. To distract myself, I recently picked up a <a href="https://www.amazon.com/Handheld-3-2-inch-Trimui-Brick-Opensource-Protector/dp/B0DNQ5345P?th=1" target="_blank" rel="noopener noreferrer" class="">TrimUI Brick</a>....actually my mom got it for me for my birthday (yes my mom bought me a birthday present in my 40s). I've been burnt out recently on PS5 gaming, annoyed by the fact that there is not an end in sight to games anymore. There is always some expansion pack, open world, or micro transaction. As you can tell, I f'n love the 90's and early 2000's nostalgia from my childhood. It probably wasn't as great as I remember (hence my someone fragile mental state at times), but dammit, we had the best of both worlds. Life without the internet + life with the internet. Since I got the retro handheld, I've been playing a lot of Donkey Kong Country and NHL 94 to keep me distracted.</p>
<p><img decoding="async" loading="lazy" alt="Donkey Kong" src="https://joeloveless.com/assets/images/donkeykong-79a23cc852fb880083a9299bcc687d52.png" width="575" height="455" class="img_ev3q"></p>
<p>But enough about my personal updates, this blog post is more about getting back into what the original theme about this site was going to be, writing about endpoint management and capturing my thoughts on it. Last year, I setup a Hyper-V lab for ConfigMgr, Intune, and Active Directory. This post deviates a little bit from the ConfigMgr and AD side of things, and focuses more on the Intune side. I have a test tenant that has been sitting dormant for a while. I figured, why not start with the basics a little bit and configure Windows Autopilot in a Hyper-V scenario?</p>
<p>This post is not going to walk through setting up Hyper-V or Microsoft Intune, if you need help with that, <a href="https://joeloveless.com/blog/intunelab-2025" target="_blank" rel="noopener noreferrer" class="">check out my post from last year</a> on setting up an Intune lab.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="preparation-downloading-a-windows-11-iso">Preparation, downloading a Windows 11 ISO<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#preparation-downloading-a-windows-11-iso" class="hash-link" aria-label="Direct link to Preparation, downloading a Windows 11 ISO" title="Direct link to Preparation, downloading a Windows 11 ISO" translate="no">​</a></h2>
<p>The first thing we need to do is download a Windows 11 ISO. There are plenty of ways to do this, if you have access to <a href="https://my.visualstudio.com/downloads" target="_blank" rel="noopener noreferrer" class="">Visual Studio subscriptions</a>, you can get it there. For me, I am just going to download the ISO from Microsoft directly. The link is below:</p>
<p><a href="https://www.microsoft.com/en-us/software-download/windows11" target="_blank" rel="noopener noreferrer" class="">https://www.microsoft.com/en-us/software-download/windows11</a></p>
<p><img decoding="async" loading="lazy" alt="Download page" src="https://joeloveless.com/assets/images/download-71a8379aed1ca703cab5747ed04b5683.png" width="1116" height="346" class="img_ev3q"></p>
<p>Once downloaded, open the file and you'll want to choose ISO file during the setup assistant.</p>
<p><img decoding="async" loading="lazy" alt="Progress" src="https://joeloveless.com/assets/images/download2-8ed292f49cff897520ac8a57b325439a.png" width="694" height="547" class="img_ev3q"></p>
<p>This might take a minute....</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="autopilot-configuration">Autopilot Configuration<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#autopilot-configuration" class="hash-link" aria-label="Direct link to Autopilot Configuration" title="Direct link to Autopilot Configuration" translate="no">​</a></h2>
<p>To configure Autopilot, we basically have 3 things we need to do:</p>
<ol>
<li class="">Import the hardware hash (will do this later during the VM creation)</li>
<li class="">Create and assign an Autopilot profile.</li>
<li class="">Create a device group for the Autopilot devices</li>
</ol>
<p>Let's work backwards on this, makes total sense.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-the-device-group-for-autopilot-devices">Creating the device group for Autopilot devices<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#creating-the-device-group-for-autopilot-devices" class="hash-link" aria-label="Direct link to Creating the device group for Autopilot devices" title="Direct link to Creating the device group for Autopilot devices" translate="no">​</a></h3>
<p>Let's go over to the <a href="https://entra.microsoft.com/" target="_blank" rel="noopener noreferrer" class="">Entra portal</a>, and go to Groups.</p>
<p>We're going to create a new group:</p>
<p><img decoding="async" loading="lazy" alt="Entra ID Group" src="https://joeloveless.com/assets/images/entra_group-cdb067fe4bea99bd8c75ddba20830fda.png" width="738" height="561" class="img_ev3q"></p>
<p>We want the group to be a Dynamic Device group.</p>
<p><img decoding="async" loading="lazy" alt="Dynamic" src="https://joeloveless.com/assets/images/entra_dynamic-e44787792bc9247986537cb14241b64f.png" width="1653" height="587" class="img_ev3q"></p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">(device.devicePhysicalIDs -any (_ -contains "[ZTDId]"))</span><br></span></code></pre></div></div>
<p>This query retrieves any device that is <strong>registered</strong> with Windows Autopilot.</p>
<p><a href="https://learn.microsoft.com/en-us/autopilot/enrollment-autopilot" target="_blank" rel="noopener noreferrer" class="">Create device groups for Windows Autopilot</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-and-assign-an-autopilot-profile">Create and assign an Autopilot profile<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#create-and-assign-an-autopilot-profile" class="hash-link" aria-label="Direct link to Create and assign an Autopilot profile" title="Direct link to Create and assign an Autopilot profile" translate="no">​</a></h3>
<p>Now we're going to head over to <a href="https://intune.microsoft.com/" target="_blank" rel="noopener noreferrer" class="">Microsoft Intune</a>, and create the Autopilot profile.</p>
<p>From the Intune portal:</p>
<p>Devices &gt; Enrollment &gt; Deployment Profiles</p>
<p>Create a new profile and give it a proper name:</p>
<p><img decoding="async" loading="lazy" alt="Autopilot creation" src="https://joeloveless.com/assets/images/autopilot1-e1caeb92d95f9855d735f0ec2f08f2d8.png" width="800" height="443" class="img_ev3q"></p>
<p>In this case, we're going to choose a User-Driven profile and give it a naming convention.</p>
<p><img decoding="async" loading="lazy" alt="Autopilot User-Driven" src="https://joeloveless.com/assets/images/autopilot2-b0ddfff83c236bfa59d70e85d4f1aedc.png" width="807" height="773" class="img_ev3q"></p>
<p>Final step, we're going to assign the profile to the Entra group we just created.</p>
<p>Once finished, your profile should look like this:</p>
<p><img decoding="async" loading="lazy" alt="Autopilot completed" src="https://joeloveless.com/assets/images/autopilot3-78de4b992d1dc0616e2bc12997fb3670.png" width="814" height="893" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="importing-the-hardware-hash">Importing the hardware hash<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#importing-the-hardware-hash" class="hash-link" aria-label="Direct link to Importing the hardware hash" title="Direct link to Importing the hardware hash" translate="no">​</a></h3>
<p>Now that we have those steps done, we're at the point where we need to import the hardware hash. We can't do that step without creating a VM first, so let's open Hyper-V and get started on that.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-the-vm">Creating the VM<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#creating-the-vm" class="hash-link" aria-label="Direct link to Creating the VM" title="Direct link to Creating the VM" translate="no">​</a></h4>
<p>We can either do this through the GUI, or do it with PowerShell. I'm going to create it with PowerShell, and then I'll show you the configuration in Hyper-V</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">New-VM -Name "W11_25H2_Autopilot" -MemoryStartupBytes 4GB -BootDevice VHD -NewVHDPath "C:\VMs\W11_25H2_Autopilot\W11_25H2_Autopilot.vhdx" -Path "C:\VMs\W11_25H2_Autopilot\VMData" -NewVHDSizeBytes 80GB -Generation 2 -Switch "JoeLoveless.com" -</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Add-VMDvdDrive -Path "C:\VMs\W11.iso" -VMName "W11_25H2_AutoPilot"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Enable-VMTPM -VMName "W11_25H2_AutoPilot"</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Hyper-V VM" src="https://joeloveless.com/assets/images/hyperv-07c139508ea22f158fc0438f959105b2.png" width="712" height="676" class="img_ev3q"></p>
<ul>
<li class="">Before we power it up, remove the network adapter and the hard disk. We'll come back to these later, first we want to capture a checkpoint.</li>
<li class="">Go into settings for the VM and remove those, also add a 2nd processor and enable TPM.</li>
<li class="">Now we're ready to power up the VM.</li>
<li class="">At the first OOBE screen, create a checkpoint</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Checkpoint" src="https://joeloveless.com/assets/images/hyperv_checkpoint-078f014edd8cf0c14c384e08232fcc5f.png" width="1022" height="766" class="img_ev3q"></p>
<ul>
<li class="">Now, with the VM still up, add the network adapter back.</li>
<li class="">Continue on through the setup process, allowing the computer to restart when necessary.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Installation Process" src="https://joeloveless.com/assets/images/hyperv_install-c3e6897d74e5b89775c07d6c460e751e.png" width="1013" height="759" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Installation Process Continued" src="https://joeloveless.com/assets/images/hyperv_install2-c8331603c948537912dbc7b25f1771fa.png" width="1016" height="765" class="img_ev3q"></p>
<ul>
<li class="">Once at this screen, hit <strong>Shift+F10</strong> and open a command prompt.</li>
<li class="">Type powershell.exe</li>
</ul>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Set-ExecutionPolicy bypass</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Install-Script get-windowsautopilotinfo</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span></code></pre></div></div>
<ul>
<li class="">Accept all the prompts, allowing the script to install.</li>
</ul>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Get-WindowsAutoPilotInfo.ps1 -online</span><br></span></code></pre></div></div>
<ul>
<li class="">Some Microsoft Graph modules will install.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Sign-in" src="https://joeloveless.com/assets/images/hyperv_signin-6578be0fe84cf1f17e0565cdbc4c6e5a.png" width="946" height="512" class="img_ev3q"></p>
<ul>
<li class="">Sign in with your Microsoft account (choose work or school)</li>
<li class="">Accept all the prompts</li>
<li class="">After the window closes, you should see the device being imported through the powershell window.</li>
<li class="">Once finished, you should now see the serial number in the Entra group.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Autopilot Registration" src="https://joeloveless.com/assets/images/autopilot4-47c87266b19e8c0670e2d01a144addc0.png" width="1759" height="819" class="img_ev3q"></p>
<ul>
<li class="">Now that we are finished, let's revert to our checkpoint, and go through the full setup process.<!-- -->
<ul>
<li class="">Make sure you add the network adapter back!</li>
</ul>
</li>
<li class="">After a few minutes and clicking through, you should be at a sign-in screen.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Sign-in" src="https://joeloveless.com/assets/images/signin-ab2d545d116086f5f9db755ff2061c47.png" width="1022" height="766" class="img_ev3q"></p>
<p><strong>Make sure you sign in with an account licensed for Intune</strong></p>
<ul>
<li class="">Depending on your tenant setup, you might see some different options from here on out (MFA, WHFB, etc)</li>
<li class="">Eventually, you will find your way to the Windows logon screen</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Windows Sign-In" src="https://joeloveless.com/assets/images/windows-a5220005a90bc555281ee4a6333ae2ba.png" width="1306" height="509" class="img_ev3q"></p>
<p>Checking Microsoft Intune, we now have the VM loaded in properly.</p>
<p><img decoding="async" loading="lazy" alt="Microsoft Intune" src="https://joeloveless.com/assets/images/intune-f32c209c621f56871ba4f6afefe0d533.png" width="973" height="199" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-thoughts">Final Thoughts<a href="https://joeloveless.com/blog/configuring-windows-autopilot-in-a-hyperv-lab#final-thoughts" class="hash-link" aria-label="Direct link to Final Thoughts" title="Direct link to Final Thoughts" translate="no">​</a></h2>
<p>I'd like to know how others are testing Autopilot and Intune in their lab environments. Is anyone still using AD/ConfigMgr for testing, or going solely to Intune/Entra joined devices? Is there a better way to provision VMs?</p>]]></content:encoded>
            <category>Intune</category>
            <category>Autopilot</category>
        </item>
        <item>
            <title><![CDATA[Setting my goals for 2026]]></title>
            <link>https://joeloveless.com/blog/setting-my-goals-for-2026</link>
            <guid>https://joeloveless.com/blog/setting-my-goals-for-2026</guid>
            <pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[What are my goals for the year? How will I accomplish them?]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/setting-my-goals-for-2026-be4938721d5a9343029afd7e6f1ed4bc.png" width="1200" height="630" class="img_ev3q"></p>
<p>We're now 10 days in to 2026. It's already been a little hectic in the world, and in Minnesota. My goal for this space is to not only have technical content, but share some personal things to a degree also. At work, we have to fill out goals for the following year, three actionable items we want to accomplish, and details on how we want to accomplish them. This is apart of our yearly review process. Items like self-evaluation, and setting goals are difficult things for me to do, especially when I know there is a limited training budget, limited time, and limited ways to accomplish those things. But, I try to think about where I want to go with my career, and put real thought into that. At the end of the year, I do something similar at home, writing down both personal and professional goals I want to achieve. A lot of times that page of paper gets lost, I forget about it, I don't do it, or I do other things instead. There is always a balance of professional and personal goals, and I certainly do not want the professional goals to outweigh the personal goals.</p>
<p>I figured this website is a perfect place to write down those things, along with more detail on how I want to accomplish them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview-of-my-yearly-goals">Overview of my Yearly Goals<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#overview-of-my-yearly-goals" class="hash-link" aria-label="Direct link to Overview of my Yearly Goals" title="Direct link to Overview of my Yearly Goals" translate="no">​</a></h2>
<ul>
<li class="">Professional goals<!-- -->
<ul>
<li class="">Artificial Intelligence/Model Context Protocol</li>
<li class="">Python</li>
<li class="">KQL</li>
</ul>
</li>
<li class="">Personal goals<!-- -->
<ul>
<li class="">Eat healthier</li>
<li class="">Exercise</li>
<li class="">Read a minimum of 12 books</li>
<li class="">Visit state/national parks<!-- -->
<ul>
<li class="">6 state parks</li>
<li class="">2 national parks</li>
</ul>
</li>
<li class="">Learn photography</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="professional-goals">Professional Goals<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#professional-goals" class="hash-link" aria-label="Direct link to Professional Goals" title="Direct link to Professional Goals" translate="no">​</a></h3>
<p>Each year I try to think about what I am interested in, what's upcoming, and what could help my career. My goals for the year are to try to learn AI, Python, and KQL. While I have mostly been in the Endpoint space in my career, I do wonder what the world is like in other areas of technology. At home, I enjoy playing around with open-source technology, trying new things, and seeing what's out there. I've thought about going down the Cloud Computing route, or maybe even trying to switch over to Linux Administration. Either way, all the things I am hoping to learn professionally can only help me in my current role, and maybe open up new opportunities for me.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="aimcp">AI/MCP<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#aimcp" class="hash-link" aria-label="Direct link to AI/MCP" title="Direct link to AI/MCP" translate="no">​</a></h4>
<p>While I don't necessarily agree with everything about AI, I'd probably be a little silly to not try to learn all the capabilities, or even begin to learn. My plan at some point in the year is to go through Microsoft's MCP training, <a href="https://github.com/microsoft/mcp-for-beginners" target="_blank" rel="noopener noreferrer" class="">https://github.com/microsoft/mcp-for-beginners</a> and see where that leads me.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="python">Python<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#python" class="hash-link" aria-label="Direct link to Python" title="Direct link to Python" translate="no">​</a></h4>
<p>I've mostly been in the PowerShell world my entire professional career, not really deviating. I figured if I ever do want to head down the Linux/Cloud Computing path, it's probably best to start learning Python. I previously purchased <strong>Python Crash Course, 2nd Edition</strong>, but never opened it up. One of those goals that fell by the wayside.</p>
<p>I've purchased <a href="https://www.amazon.com/Python-Crash-Course-Eric-Matthes/dp/1718502702?crid=R5UAYQXWTEY5&amp;dib=eyJ2IjoiMSJ9.W7y-I8wX4MSihtffDetIB9fT0FgqpfWUAK4Bl5H9VEiajJXl-Q1IgQR4WOjjd5bZmXAsZtwA_R5K3YN9vk6-ZLhnuCV8TMXC-kFC3YIHbeqxmCzbqjbyXcf-5U1IRjU_102LMGcRWa_ZW824tCgKTaGratOLeK2X_UzKkELmEh8tbWjs4j0dmgpVyMSsto2mE8r7LigVbqXWYxOXxif7PqIDHtHQrahvLq7kewnI2MU.rMD4hJmOlB71tMcNssyowsqLF3kz4GcGwrbOiyIVYwY&amp;dib_tag=se&amp;keywords=python+crash+course+3rd+edition&amp;qid=1768071167&amp;sprefix=python+crash+course+3rd+editio%2Caps%2C208&amp;sr=8-1" target="_blank" rel="noopener noreferrer" class="">Python Crash Course, 3rd Edition</a> to help me learn Python. I'll also find some YouTube videos and maybe some other online material to help me along the way.</p>
<p>I find books/labs to be my best method for learning new things. Online videos, I tend to lose focus more easily, and have to keep going back to the content.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="kql">KQL<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#kql" class="hash-link" aria-label="Direct link to KQL" title="Direct link to KQL" translate="no">​</a></h4>
<p>I've messed around with KQL here and there, but nothing too extensive. A lot of copying and pasting of code from the community, and then tweaking to get things working how I want. I picked up <a href="https://www.amazon.com/Definitive-Guide-KQL-operations-defending/dp/0138293384?crid=2R6SN9PCDDMXC&amp;dib=eyJ2IjoiMSJ9.UVhofC8WD77sHc0Lz2S32csjDTSUjyD-9BzJhENzCZRyXXfEniZg7SveXbNvtsca.P_HgUta6Ak_xNS_NrEvhc0RcFdgGoRLbfKn9lq85K0g&amp;dib_tag=se&amp;keywords=the+definitive+guide+to+kql&amp;qid=1768071317&amp;sprefix=the+definitive+guide+to+kql%2Caps%2C216&amp;sr=8-1" target="_blank" rel="noopener noreferrer" class="">The Definitive Guide to KQL</a>, and plan on jumping into that more after I go through the Python content.</p>
<p>I've been playing more with Log Analytics Dashboards in Azure, and seeing how I can get data from Microsoft Intune into there and setting up different reports. Lately I've been building out a change tracking dashboard for our team.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="personal-goals">Personal Goals<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#personal-goals" class="hash-link" aria-label="Direct link to Personal Goals" title="Direct link to Personal Goals" translate="no">​</a></h3>
<p>The professional goals are a little bit more open ended for me. I try not to sink too much time on the weekends, after work into professional goals that require me to be away from my family. I put more into the personal goals, and these are all things I 100% want to achieve, or start working towards.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="eat-healthierexercise-routine">Eat Healthier/Exercise Routine<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#eat-healthierexercise-routine" class="hash-link" aria-label="Direct link to Eat Healthier/Exercise Routine" title="Direct link to Eat Healthier/Exercise Routine" translate="no">​</a></h4>
<p>Issue: I eat great in the summer, exercise in the summer. I live in Minnesota, where it's f'n cold a lot of the year.</p>
<p>My goal here is to try to eat healthier year round along with exercising regularly. I have back issues. Poor posture, shoulder pain, etc. Between my 7th and 8th grade year I grew a foot, which really messed with my back. No real injuries, just random issues here and there. I'm getting older, and need to take care of myself more.</p>
<p>I have been riding my bike a ton, but last year, I didn't get on it more than a handful of times. Living out in cornfield/gravel road country, it's hard to find the motivation to ride it, as the rides are straight, windy, and bumpy. I'm one of those people, when I do something, I like to do it all the way. It's why I don't gamble. My goal this year is to ride my bike more, but not that much more. I don't need to try for 100 miles, or probably even 50 miles. If I could do 10-20 miles 3 days a week, that would be fantastic for me. With that, I'd like to start lifting weights, or introduce a stretching routine. Stretching I have done in the past, but nothing consistent. Lifting weights is something I've never really done, as I don't see the joy in it. It might be one of those things (like cycling), where once I start doing that, I'll enjoy it more.</p>
<p>My other goal is to eat healthier. I love frozen pizza, candy, and cereal. I know it's not good for me. In the summertime, I eat a lot of veggies from my garden. Year round I eat a lot of fruit. I'd like to keep eating healthy year round, and prepare more meals.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="read-a-minimum-of-12-books">Read a minimum of 12 books<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#read-a-minimum-of-12-books" class="hash-link" aria-label="Direct link to Read a minimum of 12 books" title="Direct link to Read a minimum of 12 books" translate="no">​</a></h4>
<p>My goal last year was 15 books. I made it about 7 books in before I lost momentum, and then didn't read another book all year. I made it through those 7 books probably by March. Another one of those things of being 100% in on something, I was reading daily, but then got burnt out on a series I was reading. I read 5 of the books in the series, but didn't even bother finishing the last one (Red Rising Series, I loved it, need to finish the last one.). I cut my goal back by 3 this year, down to 12. So that would be one book a month. I should be able to achieve this.</p>
<p>My daughter reads about 12 books a month, so there is 0 reason I shouldn't be reading more.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="visit-statenational-parks">Visit State/National Parks<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#visit-statenational-parks" class="hash-link" aria-label="Direct link to Visit State/National Parks" title="Direct link to Visit State/National Parks" translate="no">​</a></h4>
<p>We didn't go on any national park trips last year, but I want to get back out to them this year. Rocky Mountains is my favorite park I've been to. I'd like to go back out there, and maybe visit another Colorado park. I love seeing all the wildlife. Last time in the Rockies, we saw moose, elk, and longhorn sheep. The moose we saw were standing on the trail we were walking on, very intimidating. No I did not try to hug one.</p>
<p>Every year I take each of my kids on a separate camping trip. I'm still working my way through the MNDNR Hiking Club, and hope to continue on with that. My goal is to visit 6 state parks (probably pretty low). The issue is, I've knocked off most of the parks in Southern MN within a 2 hour drive from me. So now I'll need to start traveling more.</p>
<p><img decoding="async" loading="lazy" alt="DNR Parks to Visit" src="https://joeloveless.com/assets/images/dnr-8552d2d1217367eead28a2938b2c3107.png" width="792" height="781" class="img_ev3q"></p>
<p>I think I can do the above on some camping trips with each of the kids. Georgia seems to love the north more than Jay, where Jay like to play along the Mississippi and try to do fishing.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="learn-photography">Learn Photography<a href="https://joeloveless.com/blog/setting-my-goals-for-2026#learn-photography" class="hash-link" aria-label="Direct link to Learn Photography" title="Direct link to Learn Photography" translate="no">​</a></h4>
<p>Georgia took a photography class in high school, which had me buying a used camera off Facebook Marketplace. She didn't really care for the class all that much, but learning photography has been an interest of mine since I was a teenager. One of those things I never bothered learning. I feel like as a millennial, I'm starting to detach from my phone more and more. I'd like to slow down, learn photography, go back to an MP3 player, and start retro gaming.</p>
<p>How will I learn photography? That part I haven't quite figured out yet, but it's on my to do list.</p>]]></content:encoded>
            <category>Other</category>
        </item>
        <item>
            <title><![CDATA[Expanding my Home Lab with Cloudflare, Tailscale, and more]]></title>
            <link>https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more</link>
            <guid>https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more</guid>
            <pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[How I use Cloudflare, Technitium, Caddy, and Tailscale to expand my home lab.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/expanding-my-homelab-with-cloudflare-tailscale-and-more-cd8f81d72cdf8338bce09d9a02ae9769.png" width="1200" height="630" class="img_ev3q"></p>
<p>2025 is almost to a close. We recently went back to Indiana to visit our family and had a good time. The weather was unseasonably warm, especially compared to Minnesota standards. We visited the Indianapolis Zoo, as the kids have been wanting to see the chimps since we last lived there and they did a summer camp at the zoo. We saw some super active orangutans, and other animals that are normally not active. I love visiting zoos on winter days. No crowds, some animals being more active than they normally are. Christmas was nice, my cousin came up to visit who I don't see all that often and I was able to catch up a bit with my dad and brother. Family gatherings are usually a little awkward for me, I'm more laid back and reserved while my family when all together are pretty loud and talk over each other (even if there is only 11 of us).</p>
<p>My goal for this blog going into the new year is to obviously try to write more consistently. I started out good in 2025, but tapered off at the end (as I do with most things.) This year I am hoping to write at least once a month, with posts having more content to them. I'm not exactly sure what I will write about, probably more things with endpoint management, but honestly I like to just write about whatever I feel like. Which sums up this post perfectly.</p>
<p>Before Christmas, I had been working on my home setup more and wanted to write a post before the end of the year. Recently I bought a new UGreen 4-bay NAS (my first NAS) and have been learning about Docker a lot. Before I had the NAS, I was running Proxmox off of a Intel NUC, using LXC Containers or VMs. I also had a Dell Optiplex in use, but that was more for Active Directory/ConfigMgr/Intune testing. With my new NAS, and expanded storage/memory and ideas, I decided to split up how I was doing things.</p>
<p>You can find more of my home lab setup <a href="https://github.com/Pacers31Colts18/HomeLab" target="_blank" rel="noopener noreferrer" class="">here</a> but I've migrated a few containers off of Proxmox and into Docker (using Portainer as my management tool for Docker). In this post, I plan to go into :</p>
<ul>
<li class="">How I've used an unused domain name on Cloudflare to get proper certificates.</li>
<li class="">Technitium to manage my DNS and ad blocking.</li>
<li class="">Caddy to reverse proxy my sites and retrieve the proper certificates.</li>
<li class="">Tailscale to access my network remotely</li>
<li class="">And probably some other things I'm forgetting</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-before-times">The Before Times<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#the-before-times" class="hash-link" aria-label="Direct link to The Before Times" title="Direct link to The Before Times" translate="no">​</a></h2>
<p>Okay, I've had my NAS since the fall. I've migrated the following from Proxmox to Docker, rebuilding services and trying to standardize. These were previously on a singular Proxmox container, running with an 8TB external hard drive plugged into the NUC and passing through to the container. Running off crappy internet, there was always some sort of issue or slow loading time. It got by good enough, but needed to be fixed.</p>
<ul>
<li class="">Plex</li>
<li class="">Prowlarr</li>
<li class="">Radarr</li>
<li class="">Sonarr</li>
<li class="">Lidarr</li>
<li class="">Bazarr</li>
</ul>
<p>I initially configured these all to run in host mode, with 192.x.x.x IP addresses. This was done mainly out of laziness, but once I discovered Portainer, I then went back and reconfigured these to all be in bridged networking (on separate networks). I was able to get them to communicate with each other by adding the following line in each compose file:</p>
<div class="language-yml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token key atrule">extra_hosts</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> ultron.local</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain">192.168.4.125</span><br></span></code></pre></div></div>
<p>So now that I have everything migrated over, I finally decided it was time to get certificates and domain names working correctly on this. Obviously, I'm the owner of joeloveless.com. I'm also the owner of some other variations of joeloveless and other random domain names my ADHD brain decides to randomly purchase. For this, I decided not to use joeloveless.com and instead use joeloveless.net for the setup. Why? I don't really have a great reason...separation of services, sure?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lets-play-with-cloudflare">Let's play with Cloudflare!<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#lets-play-with-cloudflare" class="hash-link" aria-label="Direct link to Let's play with Cloudflare!" title="Direct link to Let's play with Cloudflare!" translate="no">​</a></h2>
<p>Have I used Cloudflare before, nope. Is it a lot, you betcha. Compared to Square Space, there are about a million more options. In my initial design/messing around, I was looking at DDNS providers (DuckDNS, NoIP, etc). I registered joeloveless.net with NoIP, but couldn't get that working properly, I then discovered certificate challenging with Let's Encrypt. I used these sites as my starting points:</p>
<ul>
<li class=""><a href="https://techdecode.online/decode/build-your-homelab-nginx/" target="_blank" rel="noopener noreferrer" class="">https://techdecode.online/decode/build-your-homelab-nginx/</a></li>
<li class=""><a href="https://blog.jamesbrooks.net/posts/technitium-dns-server-with-tailscale/" target="_blank" rel="noopener noreferrer" class="">https://blog.jamesbrooks.net/posts/technitium-dns-server-with-tailscale/</a></li>
</ul>
<p>Once you have an account, the first thing you need to do is configure the NS records properly. Easier when you buy the domain through Cloudflare, as that work is already done for you. If bringing in from another registrar, there is the transfer process. I followed <a href="https://developers.cloudflare.com/registrar/get-started/transfer-domain-to-cloudflare/" target="_blank" rel="noopener noreferrer" class="">Cloudflare's docs</a> and was then ready for the next step.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-key">API Key<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#api-key" class="hash-link" aria-label="Direct link to API Key" title="Direct link to API Key" translate="no">​</a></h3>
<p>The next step is to generate an API key. Simple enough, make sure to copy it somewhere safe (not online) and actually do that. I can't tell you how many times I thought this was going to be a straightforward process, only to get stuck somewhere, not copy the API key somewhere I could reference it, starting over with a new try, and not having the API key. I think I generated about ten of them in total.</p>
<ol>
<li class="">From the Cloudflare portal &gt; Profile</li>
<li class="">Click API Tokens</li>
<li class="">Click Create Token</li>
<li class="">Choose Edit Zone DNS</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Token Creation" src="https://joeloveless.com/assets/images/cloudflare-03a1cf2d291fd60ddde880cadd838560.png" width="842" height="697" class="img_ev3q"></p>
<ul>
<li class="">When using the template, it will pre-fill Edit zone for you. I went ahead and added <strong>Read</strong> also. Probably not needed, as you have to have Read to be able to Edit, but whatever.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Token Creation Zones" src="https://joeloveless.com/assets/images/cloudflare2-c5e4fd601eb3f903eeded64afc6bc69c.png" width="1091" height="728" class="img_ev3q"></p>
<ul>
<li class="">If you only have one domain, you could probably just choose All Zones, but I'm not sure what the future looks like, so I wanted to configure this only for joeloveless.net</li>
</ul>
<p><strong>Again, copy the API key somewhere safe as you'll need it later</strong></p>
<p>For now, we are done in the Cloudflare portal, but will come back to this later.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-a-reverse-proxy---caddy-for-the-win">Setting up a Reverse Proxy - Caddy for the Win<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#setting-up-a-reverse-proxy---caddy-for-the-win" class="hash-link" aria-label="Direct link to Setting up a Reverse Proxy - Caddy for the Win" title="Direct link to Setting up a Reverse Proxy - Caddy for the Win" translate="no">​</a></h2>
<p>Three things I'm not the greatest at.</p>
<ol>
<li class="">Networking</li>
<li class="">Certificates</li>
<li class="">Linux</li>
</ol>
<p>For a starting off point, I referenced this blog post to get started, but ended up modifying some of the steps:</p>
<ul>
<li class=""><a href="https://samedwardes.com/blog/2023-11-19-homelab-tls-with-caddy-and-cloudflare/" target="_blank" rel="noopener noreferrer" class="">https://samedwardes.com/blog/2023-11-19-homelab-tls-with-caddy-and-cloudflare/</a></li>
<li class=""><a href="https://akashrajpurohit.com/blog/setup-caddy-with-automatic-ssl-certificates-with-cloudflare/" target="_blank" rel="noopener noreferrer" class="">https://akashrajpurohit.com/blog/setup-caddy-with-automatic-ssl-certificates-with-cloudflare/</a></li>
</ul>
<p>Well, this is a home lab, here I am trying to learn. This is also a good place to document so the next dumb dumb (or me) can maybe stumble there way through it.</p>
<p>My goal initially was to run this all through Docker. I had issues getting anything to work properly, using NGINX ProxyManager, Caddy, or any other solution. What I landed on was:</p>
<ul>
<li class=""><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=caddy" target="_blank" rel="noopener noreferrer" class="">Proxmox LXC for Caddy</a>
<ul>
<li class="">Reason for Caddy:<!-- -->
<ul>
<li class="">I was able to get it working while I struggled with the others.</li>
</ul>
</li>
<li class="">Reason for Proxmox:<!-- -->
<ul>
<li class="">Proxmox Helper Scripts are dumb dumb proof. I also know my way around Proxmox just a little bit better than Docker at this point. And by a little bit, it means I've had it setup for longer. I'm no expert by any means.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Once the base setup is complete, ensure you have the <a href="https://caddyserver.com/docs/modules/dns.providers.cloudflare" target="_blank" rel="noopener noreferrer" class="">Cloudflare DNS module</a> installed also.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">xcaddy build --with github.com/caddy-dns/cloudflare</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="port-forwardingip-reservation">Port Forwarding/IP Reservation<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#port-forwardingip-reservation" class="hash-link" aria-label="Direct link to Port Forwarding/IP Reservation" title="Direct link to Port Forwarding/IP Reservation" translate="no">​</a></h3>
<p>Once your Caddy LXC is up and running, we'll want to go into it through the Web UI.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-your-ip-address">Getting your IP Address<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#getting-your-ip-address" class="hash-link" aria-label="Direct link to Getting your IP Address" title="Direct link to Getting your IP Address" translate="no">​</a></h4>
<p>LXC containers are pretty bare bones. Turns out, ifconfig is not even an option.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">root@caddy01:/# ifconfig</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-bash: ifconfig: command not found</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">ip addr</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="IP Address" src="https://joeloveless.com/assets/images/caddy-a8f04dc49c11ae212933ae7078fe0068.png" width="1081" height="341" class="img_ev3q"></p>
<p><strong>An important step to do is to reserve the IP address for your Caddy container. You will also want to port forward 80/443. All of this is router dependent. I was able to do this through the Eero Android App, but your mileage may vary.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cloudflare-a-record">Cloudflare A Record<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#cloudflare-a-record" class="hash-link" aria-label="Direct link to Cloudflare A Record" title="Direct link to Cloudflare A Record" translate="no">​</a></h3>
<p>Back to the Cloudflare portal, we will want to add an A record for our <strong>internal</strong> IP address.</p>
<p><img decoding="async" loading="lazy" alt="Cloudflare A Record" src="https://joeloveless.com/assets/images/cloudflare3-731c4d760219b6d4f22da01b612de703.png" width="1383" height="451" class="img_ev3q"></p>
<p>More information on what this is doing exactly can be found on <a href="https://developers.cloudflare.com/dns/proxy-status/#proxied-records" target="_blank" rel="noopener noreferrer" class="">Cloudflare's documentation</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="back-to-caddy">Back to Caddy<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#back-to-caddy" class="hash-link" aria-label="Direct link to Back to Caddy" title="Direct link to Back to Caddy" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-caddy-as-a-service">Running Caddy as a Service<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#running-caddy-as-a-service" class="hash-link" aria-label="Direct link to Running Caddy as a Service" title="Direct link to Running Caddy as a Service" translate="no">​</a></h4>
<p>I followed this blog post to configure Caddy to run automatically or as a service.</p>
<ul>
<li class=""><a href="https://akashrajpurohit.com/blog/setup-caddy-with-automatic-ssl-certificates-with-cloudflare/" target="_blank" rel="noopener noreferrer" class="">https://akashrajpurohit.com/blog/setup-caddy-with-automatic-ssl-certificates-with-cloudflare/</a></li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="env-file">.env File<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#env-file" class="hash-link" aria-label="Direct link to .env File" title="Direct link to .env File" translate="no">​</a></h4>
<p>Now that we have all that configured, we can hop back into the Caddy LXC and start configuring it.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">cd /etc/caddy</span><br></span></code></pre></div></div>
<p><strong>/etc/caddy</strong> is where the config files for Caddy lives.</p>
<p>Let's start by making the .env file:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo nano .env</span><br></span></code></pre></div></div>
<p>This is where the Cloudflare API token comes into play:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">CF_API_TOKEN=REPLACE WITH YOUR API TOKEN</span><br></span></code></pre></div></div>
<p><strong>CTRL+X and CTRL+S will then save the file.</strong></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="caddyfile">Caddyfile<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#caddyfile" class="hash-link" aria-label="Direct link to Caddyfile" title="Direct link to Caddyfile" translate="no">​</a></h4>
<p>Now we will need to make the Caddyfile. Case sensitivity matters in Linux.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">cd /etc/caddy</span><br></span></code></pre></div></div>
<p>The <a href="https://caddyserver.com/docs/caddyfile" target="_blank" rel="noopener noreferrer" class="">Caddyfile</a> is where all the configuration for our sites lives.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo nano Caddyfile</span><br></span></code></pre></div></div>
<p>I struggled with this on the Docker side of things, mainly because Docker created a folder and was conflicting with my actual file.</p>
<p>Example configuration:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        email joe@joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Public HTTPS subdomains</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">radarr.joeloveless.net {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        reverse_proxy 192.168.4.208:7878</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        tls {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                dns cloudflare {env.CF_API_TOKEN}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">sonarr.joeloveless.net {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        reverse_proxy 192.168.4.125:8989</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        tls {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                dns cloudflare {env.CF_API_TOKEN}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">lidarr.joeloveless.net {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        reverse_proxy 192.168.4.219:8686</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        tls {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                dns cloudflare {env.CF_API_TOKEN}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>Once configured, we will want to reload the service:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">systemctl reload caddy.service</span><br></span></code></pre></div></div>
<p>Going to <strong>lidarr.joeloveless.net</strong> takes me to a black screen through Firefox.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> curl -v lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Could not resolve host: lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* shutting down connection #0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl: (6) Could not resolve host: lidarr.joeloveless.net</span><br></span></code></pre></div></div>
<p>We're almost there, we just need to configure Technitium properly.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-up-technitium">Next up, Technitium<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#next-up-technitium" class="hash-link" aria-label="Direct link to Next up, Technitium" title="Direct link to Next up, Technitium" translate="no">​</a></h2>
<p>To start, I live in the middle of nowhere Minnesota. My best and only internet provider is Starlink (booooo). I also have an Eero 6 router. Last year, I tried to configure Pi-hole again to block ads. What I found was my Pi-hole configuration (basically the default config) was balooning in size daily and recreating the hosts over and over again. In a limiited space Proxmox VM, this was no bueno, so I had to find alternatives.</p>
<p>This is where I found <a href="https://technitium.com/dns/" target="_blank" rel="noopener noreferrer" class="">Technitium</a>, and honestly, found it much better than Pi-hole. While the UI isn't as clean, the options provided are so much better than Pi-Hole. It's an Adblocker, DNS server, DHCP server, and I'm sure some other servers all rolled into one.</p>
<p>In a recent update, Technitium introduced a clustering option, and that is where the ideas started spinning. I already had Technitium on an LXC container using <a href="https://tteck.github.io/Proxmox/#technitium-dns-lxc" target="_blank" rel="noopener noreferrer" class="">Proxmox Helper Scripts</a>, but with clustering, I decided to go down the rabbit hole.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="docker-configuration">Docker Configuration<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#docker-configuration" class="hash-link" aria-label="Direct link to Docker Configuration" title="Direct link to Docker Configuration" translate="no">​</a></h3>
<p><a href="https://github.com/Pacers31Colts18/HomeLab/blob/main/Docker/Compose/technitium-docker_compose.yml" target="_blank" rel="noopener noreferrer" class="">Docker Compose</a></p>
<p>I'll be honest, I probably found the docker compose file somewhere, I'm just not sure where at this moment. I try to use the base Docker compose files when I can, and think this is what I used here. This is pretty straight forward, I try to use bridge mode for the networking whenever possible, but this is one that calls for the networking to be in host mode. Makes total sense to me and my Docker noobness.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="router-configuration">Router Configuration<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#router-configuration" class="hash-link" aria-label="Direct link to Router Configuration" title="Direct link to Router Configuration" translate="no">​</a></h3>
<p>You should now have two DNS servers. We'll want to copy the IP address from each, setup an IP reservation on your router, and set both of these as your primary and secondary DNS servers.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="optional-cluster-configuration">Optional: Cluster Configuration<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#optional-cluster-configuration" class="hash-link" aria-label="Direct link to Optional: Cluster Configuration" title="Direct link to Optional: Cluster Configuration" translate="no">​</a></h3>
<p>I now have two Technitium servers:</p>
<ul>
<li class="">dns01 - Proxmox LXC</li>
<li class="">dns02 - Docker Container</li>
</ul>
<p><em>Note: This part might be a tad out of order, as I already had a running Technitium server</em></p>
<p>I then logged into dns01 and followed <a href="https://blog.technitium.com/2025/11/understanding-clustering-and-how-to.html" target="_blank" rel="noopener noreferrer" class="">Technitium's guide to setting up clustering</a></p>
<p><img decoding="async" loading="lazy" alt="Clustering setup" src="https://joeloveless.com/assets/images/technitium_cluster-e250829f234652a0b94c91bcc43192b3.png" width="1262" height="414" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-zones">Setting up Zones<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#setting-up-zones" class="hash-link" aria-label="Direct link to Setting up Zones" title="Direct link to Setting up Zones" translate="no">​</a></h3>
<p>Back to the issue at hand, we're now trying to hit the sites that we have in our Caddyfile by going to <a href="https://lidarr.joeloveless.net/" target="_blank" rel="noopener noreferrer" class="">https://lidarr.joeloveless.net</a>, <a href="https://sonarr.joeloveless.net/" target="_blank" rel="noopener noreferrer" class="">https://sonarr.joeloveless.net</a>, etc.</p>
<p>In Technitium:</p>
<ol>
<li class="">Go to Zones &gt; Add Zone</li>
<li class="">Create a new zone as a <strong>Forwarder zone</strong></li>
</ol>
<p><img decoding="async" loading="lazy" alt="Zones" src="https://joeloveless.com/assets/images/technitium_zones-ba3210c929c269ce13e7e318aae84b53.png" width="1262" height="414" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-records">Adding Records<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#adding-records" class="hash-link" aria-label="Direct link to Adding Records" title="Direct link to Adding Records" translate="no">​</a></h4>
<p>Now we need to add a few records to our newly created zone:</p>
<p>First, create an @ record and a * record for Caddy:</p>
<p><img decoding="async" loading="lazy" alt="Technitium - Caddy Record" src="https://joeloveless.com/assets/images/technitium_caddy-401433dd89c68bf96204f46d169b0ad2.png" width="1258" height="891" class="img_ev3q"></p>
<p>Then, for each service, add an A record. The IP address should be that of the Caddy server, not of the service itself. This tripped me up initially.</p>
<p><img decoding="async" loading="lazy" alt="Technitium - Lidarr Record" src="https://joeloveless.com/assets/images/technitium_lidarr-6d35792ad8f71cf00885d23d6db21ba6.png" width="1258" height="571" class="img_ev3q"></p>
<p>We should now be able to go to a site and view the page correctly:</p>
<p><img decoding="async" loading="lazy" alt="Lidarr" src="https://joeloveless.com/assets/images/cert_lidarr-71eb7f5b40a93af5023a1fbda7a95566.png" width="759" height="612" class="img_ev3q"></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl -v lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Host lidarr.joeloveless.net:80 was resolved.</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* IPv6: (none)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* IPv4: 192.168.4.229</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">*   Trying 192.168.4.229:80...</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Connected to lidarr.joeloveless.net (192.168.4.229) port 80</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* using HTTP/1.x</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; GET / HTTP/1.1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; Host: lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; User-Agent: curl/8.15.0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; Accept: */*</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Request completely sent off</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; HTTP/1.1 308 Permanent Redirect</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Connection: close</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Location: https://lidarr.joeloveless.net/</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Server: Caddy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Date: Fri, 26 Dec 2025 15:16:59 GMT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Content-Length: 0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* shutting down connection #0</span><br></span></code></pre></div></div>
<p>We can also view the log files for Caddy by typing the following:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">journalctl -u caddy.service --no-pager | less +G</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-are-we-at">Where are we at?<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#where-are-we-at" class="hash-link" aria-label="Direct link to Where are we at?" title="Direct link to Where are we at?" translate="no">​</a></h2>
<p>A brief recap of what's been done so far.</p>
<ul>
<li class="">1 domain name on Cloudflare<!-- -->
<ul>
<li class="">joeloveless.net<!-- -->
<ul>
<li class="">API token for DNS zones configured</li>
<li class="">A record for internal IP address</li>
</ul>
</li>
</ul>
</li>
<li class="">2 DNS Servers:<!-- -->
<ul>
<li class="">dns01 - Proxmox</li>
<li class="">dns02 - Docker</li>
<li class="">Servers are clustered together</li>
<li class="">1 Forwarder zone (joeloveless.net)<!-- -->
<ul>
<li class="">@ and * records for 192.168.4.229 (caddy01)</li>
<li class="">A records for each service pointing to 192.168.4.229</li>
</ul>
</li>
</ul>
</li>
<li class="">1 Proxy Server:<!-- -->
<ul>
<li class="">caddy01 - Proxmox<!-- -->
<ul>
<li class="">Configured to run as a service</li>
<li class="">.env file storing the Cloudflare API token</li>
<li class="">Caddyfile containing the sites to be redirected</li>
</ul>
</li>
</ul>
</li>
<li class="">Services<!-- -->
<ul>
<li class="">Lidarr, Sonarr, Radarr, whatever other service to configure.</li>
<li class="">Currently resolving <strong>internally</strong> to <a href="https://service.joeloveless.net/" target="_blank" rel="noopener noreferrer" class="">https://service.joeloveless.net</a> with a HTTPS certificate.</li>
<li class="">Not resolving externally</li>
</ul>
</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Lidarr - External" src="https://joeloveless.com/assets/images/lidarr_offnetwork-c92ee8601396ed8769d02b0cf2313fe9.png" width="903" height="569" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="solving-the-remote-issue---enter-tailscale">Solving the remote issue - Enter Tailscale<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#solving-the-remote-issue---enter-tailscale" class="hash-link" aria-label="Direct link to Solving the remote issue - Enter Tailscale" title="Direct link to Solving the remote issue - Enter Tailscale" translate="no">​</a></h2>
<p>I'd like to be able to access my services remotely, without directly exposing ports to the Internet, setting up a full on VPN, or some other solution that I don't have too much time for. Enter <a href="https://tailscale.com/" target="_blank" rel="noopener noreferrer" class="">Tailscale</a>.</p>
<p>Tailscale allows you to install a client on devices/vms/whatever, and access those services from another Tailscale client. There are a multitude of options to configure, but I believe I landed on what works best for my use case.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="more-issues-installing-tailscale-on-dockerugreen-nasproxmox">More issues, installing Tailscale on Docker/UGreen NAS/Proxmox<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#more-issues-installing-tailscale-on-dockerugreen-nasproxmox" class="hash-link" aria-label="Direct link to More issues, installing Tailscale on Docker/UGreen NAS/Proxmox" title="Direct link to More issues, installing Tailscale on Docker/UGreen NAS/Proxmox" translate="no">​</a></h3>
<p>I ran into different issues trying to get all my clients talking correctly and accessible.</p>
<ul>
<li class="">Docker<!-- -->
<ul>
<li class="">All containers are being ran in bridged networking, getting them to communicate properly with the Tailscale docker container proved to be annoying.</li>
</ul>
</li>
<li class="">UGreen NAS<!-- -->
<ul>
<li class="">UGreen runs it's own Linux distro, I was running into errors trying to get it to install on the NAS. And that would only solve the problem for services running on the NAS directly had it worked.</li>
</ul>
</li>
<li class="">Proxmox<!-- -->
<ul>
<li class="">While I could have installed TailScale on all my Proxmox containers and VMs, I ran into different issues with different containers, and again, that would only solve the Proxmox side of things.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-a-new-container-to-control-it-all">Adding a new Container to control it all<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#adding-a-new-container-to-control-it-all" class="hash-link" aria-label="Direct link to Adding a new Container to control it all" title="Direct link to Adding a new Container to control it all" translate="no">​</a></h3>
<p>After thinking about it for a bit, I landed on what I hope is a good decision. I decided rather than trying to get the Tailscale client installed on everything individually, I would instead spin up a 3rd Technitium DNS server (Proxmox LXC) and then install Tailscale on that host.</p>
<p>My thought process behind this:</p>
<ul>
<li class="">Gives me a third server, should one of my other DNS servers have issues. I could just update the DNS servers on my Eero router.</li>
<li class="">Simple enough to setup. I don't need to worry about Docker configuration. I could just run the Proxmox Helper Script, then install the Tailscale client.</li>
<li class="">I can cluster with the dns01 and dns02, so all the configuration will be there should I need to take any of the other two down. It's aware of all the zones, etc, so I can use this as a DNS server on the Tailscale side.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuring-the-new-server---dns03">Configuring the new server - dns03<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#configuring-the-new-server---dns03" class="hash-link" aria-label="Direct link to Configuring the new server - dns03" title="Direct link to Configuring the new server - dns03" translate="no">​</a></h3>
<p>This process was really straight forward.</p>
<p><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=technitiumdns" target="_blank" rel="noopener noreferrer" class="">Proxmox Helper Script - Technitium</a></p>
<p>Once this is finished, you then will run the Tailscale setup.</p>
<p><a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=add-tailscale-lxc&amp;category=Proxmox+%26+Virtualization" target="_blank" rel="noopener noreferrer" class="">Proxmox Helper Script - Tailscale</a></p>
<p>From here, we are going to configure Tailscale to act as a <a href="https://tailscale.com/kb/1019/subnets" target="_blank" rel="noopener noreferrer" class="">subnet router</a>, which requires some extra configuration.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo sysctl -p /etc/sysctl.d/99-tailscale.conf</span><br></span></code></pre></div></div>
<p>Now we can start Tailscale and advertise the routes:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">tailscale up --advertise-routes=192.168.4.0/24 --advertise-exit-node</span><br></span></code></pre></div></div>
<p>You should then get a message on your console with a URL to authenticate to, go ahead and do that portion (and setup the Tailscale account if you have not already done so)</p>
<p>Back in the LXC container:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">tailscale set --accept-dns=false</span><br></span></code></pre></div></div>
<p>TLDR: We're telling the host not to use Tailscale's DNS servers.</p>
<p>Now we are ready to configure some settings in the Tailscale portal, but first, do the following:</p>
<ol>
<li class="">Capture the IP address and write it down</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">ip addr</span><br></span></code></pre></div></div>
<ol start="2">
<li class="">Setup an IP reservation on your router for your new host so this will not change.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailscale-portal-configuration">Tailscale Portal Configuration<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#tailscale-portal-configuration" class="hash-link" aria-label="Direct link to Tailscale Portal Configuration" title="Direct link to Tailscale Portal Configuration" translate="no">​</a></h3>
<p>In the Tailscale portal, you should now see your host.</p>
<ul>
<li class="">Click on the three dots</li>
</ul>
<p><img decoding="async" loading="lazy" alt="3 dots" src="https://joeloveless.com/assets/images/tailscale-da96aa6d8b5f0c7eeb1168227bd9b359.png" width="1301" height="337" class="img_ev3q"></p>
<ul>
<li class="">Ensure everything is checked correctly:</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Client settings" src="https://joeloveless.com/assets/images/tailscale2-e07aa60d1fe64fc1707dcee0f6f563f1.png" width="552" height="589" class="img_ev3q"></p>
<ul>
<li class="">Click Save, and then click on DNS in the top menu bar.</li>
<li class="">Scroll down to <strong>Nameservers</strong></li>
<li class="">Enter your <strong>Public IP Address</strong>
<ul>
<li class="">To find your public IP on Linux:</li>
</ul>
</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl -4 https://ipinfo.io/ip</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Tailscale Name Servers" src="https://joeloveless.com/assets/images/tailscale_nameservers-295bb87cc46020314b65d2714e28261f.png" width="835" height="814" class="img_ev3q"></p>
<ul>
<li class="">Click the three dots and toggle <strong>Use with exit node</strong></li>
<li class="">Remove any already configured Name Servers.</li>
<li class="">Under search domains, add your domain name (see above screenshot)</li>
<li class="">Disable MagicDNS.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-out-the-configuration">Testing out the configuration<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#testing-out-the-configuration" class="hash-link" aria-label="Direct link to Testing out the configuration" title="Direct link to Testing out the configuration" translate="no">​</a></h2>
<p>So with this configuration, we should be able to do the following:
[x] Resolve https://<strong>service</strong>.joeloveless.net internally.
[x] Resolve https://<strong>service</strong>.joeloveless.net if connected via Tailscale.
[x] Not be able to connect if on any other setup.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="internal">Internal<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#internal" class="hash-link" aria-label="Direct link to Internal" title="Direct link to Internal" translate="no">​</a></h3>
<p>My home wifi is cleverly named "LovelessWIFI"</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">ip addr</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Internal IP" src="https://joeloveless.com/assets/images/internal2-77c9ca1e131c14d99a4dce5968df35e6.png" width="922" height="225" class="img_ev3q"></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">resolvectl</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Internal DNS" src="https://joeloveless.com/assets/images/internal-4800b7988a3bde1b8badbc2c39d132e8.png" width="926" height="413" class="img_ev3q"></p>
<p>192.168.4.216 IP address, connected to dns01 (192.168.4.208) as the primary DNS server.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl -v lidarr.joeloveless.net</span><br></span></code></pre></div></div>
<p>Curl results are successful!</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Host lidarr.joeloveless.net:80 was resolved.</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">IPv6: (none)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">IPv4: 192.168.4.229</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Trying 192.168.4.229:80...</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Connected to lidarr.joeloveless.net (192.168.4.229) port 80</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">using HTTP/1.x</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">GET / HTTP/1.1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Host: lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">User-Agent: curl/8.15.0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Accept: */*</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Request completely sent off</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">HTTP/1.1 308 Permanent Redirect</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Connection: close</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Location: https://lidarr.joeloveless.net/</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Server: Caddy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Date: Fri, 26 Dec 2025 19:06:19 GMT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">Content-Length: 0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"> </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">shutting down connection #0</span><br></span></code></pre></div></div>
<p>I'm able to successfully connect to the service with valid certificates:</p>
<p><img decoding="async" loading="lazy" alt="Lidarr connection" src="https://joeloveless.com/assets/images/internal3-079ad5a40aaf6aaae2ea8cd0edc24ea5.png" width="1161" height="600" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="external">External<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#external" class="hash-link" aria-label="Direct link to External" title="Direct link to External" translate="no">​</a></h3>
<p>I've now disconnected from my home wifi, configured and connected to my mobile hotspot.</p>
<p><img decoding="async" loading="lazy" alt="External IP Address" src="https://joeloveless.com/assets/images/external-637dff2c8784cb563a68f5562ee9d05c.png" width="1148" height="304" class="img_ev3q">
<img decoding="async" loading="lazy" alt="External DNS" src="https://joeloveless.com/assets/images/external2-42f5adcac3bc0edeadf695c79fdd0e7b.png" width="1137" height="149" class="img_ev3q"></p>
<p>10.142.141.137 IP address connected to 10.142.141.105 DNS server</p>
<p>Just to make sure there isn't anything lingering, let's flush DNS:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo resolvectl flush-caches</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="External Try" src="https://joeloveless.com/assets/images/external3-6cd1273a7df4c9d6e7e1f078a024e552.png" width="916" height="394" class="img_ev3q"></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl -v lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Could not resolve host: lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* shutting down connection #0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl: (6) Could not resolve host: lidarr.joeloveless.net</span><br></span></code></pre></div></div>
<p>Failure = Success!</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailscale">Tailscale<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#tailscale" class="hash-link" aria-label="Direct link to Tailscale" title="Direct link to Tailscale" translate="no">​</a></h3>
<p>So we've been able to connect internally, but not externally. Now let's see if Tailscale does it's job.</p>
<p>From a client that has Tailscale installed, make sure you configure the client to connect to the exit node:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo tailscale set --exit-node=dns03</span><br></span></code></pre></div></div>
<p>Once configured, connect.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">tailscale up</span><br></span></code></pre></div></div>
<p>Let's make sure we're connected correctly:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">tailscale status</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Tailscale Status" src="https://joeloveless.com/assets/images/tailscale_status-0233707725d509dd3d3d77a7b3ec78c9.png" width="1066" height="185" class="img_ev3q"></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">curl -v lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Host lidarr.joeloveless.net:80 was resolved.</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* IPv6: (none)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* IPv4: 192.168.4.229</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">*   Trying 192.168.4.229:80...</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Connected to lidarr.joeloveless.net (192.168.4.229) port 80</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* using HTTP/1.x</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; GET / HTTP/1.1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; Host: lidarr.joeloveless.net</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; User-Agent: curl/8.15.0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; Accept: */*</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&gt; </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* Request completely sent off</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; HTTP/1.1 308 Permanent Redirect</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Connection: close</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Location: https://lidarr.joeloveless.net/</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Server: Caddy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Date: Fri, 26 Dec 2025 19:23:04 GMT</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; Content-Length: 0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">&lt; </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">* shutting down connection #0</span><br></span></code></pre></div></div>
<p>Curl is successful!</p>
<p><img decoding="async" loading="lazy" alt="Tailscale - Lidarr" src="https://joeloveless.com/assets/images/lidarr_tailscale-dc4e6473a05b12309a0229a81740492f.png" width="1628" height="765" class="img_ev3q"></p>
<p>Success! I can now see all my previously bought CDs and tapes from my younger days.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://joeloveless.com/blog/expanding-my-homelab-with-cloudflare-tailscale-and-more#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>This was a pretty fun process to figure out. It seems to work surprisingly well so far, I was able to connect back to my home Starlink network via Tailscale and listen to my music on the way to Indiana in a lovely 9 hour car ride through Iowa and Illinois with hardly any issues. I hope everyone has a wonderful New Year's and a great 2026. Until next time, take care.</p>]]></content:encoded>
            <category>Home Lab</category>
            <category>Networking</category>
        </item>
        <item>
            <title><![CDATA[Tracking Chores with Home Assistant]]></title>
            <link>https://joeloveless.com/blog/tracking-chores-with-home-assistant</link>
            <guid>https://joeloveless.com/blog/tracking-chores-with-home-assistant</guid>
            <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[How I use Home Assistant to keep track of chores.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/tracking-chores-with-home-assistant-4e9251778ab4a6a71a6805e23542b460.png" width="1200" height="630" class="img_ev3q"></p>
<p>I've been slammed. I keep trying to set a schedule and stay organized, but with everything at work and home life, I've just not had the time or motivation to really write all that much. The kids are back in school, and that has been eating up most of my time. Work has been really busy the past few months, and hopefully with the holiday season upon us, things will start to slow down a little bit.</p>
<p>Since the last post, I've purchased a new UGreen NAS, which I am deeply enjoying. I'm going down the Docker route currently, and setting up my Plex server on that. I've transferred everything over, and am just working out the kinks. I think I'll do a post on the setup here shortly. Windows 10 End of Life happened, and my trusty Lenovo laptop (2018ish?) isn't spec'd for Windows 11. I decided to try something new, and put Fedora on it. Typically when I go the Linux route, I go for a Ubuntu flavor (Mint or Ubuntu), but I decided to try Fedora this time. I've never really had any issues with Linux, but I'm not a heavy user either. This laptop is basically a dedicated web browser for Discord, PocketCasts, and Spotify. For labbing and other tasks, I have my Dell Optiplex I got off eBay earlier in the year, and do most of my browsing of blogs and such on my Samsung tablet.</p>
<p>I didn't want to go too long without posting though, this post is about how I am trying to get some organization going in my life. One of my constant struggles is staying organized. I'm usually a little all over the place, and have tried different methods. Currently, I use the following:</p>
<ul>
<li class="">Field Notes<!-- -->
<ul>
<li class="">Personal notebook to write down things to do each day.</li>
</ul>
</li>
<li class="">Microsoft ToDo<!-- -->
<ul>
<li class="">I use this for work to try to flag e-mails, setup daily reminders of what I should actually be working on.</li>
</ul>
</li>
<li class="">Azure DevOps<!-- -->
<ul>
<li class="">Our team uses this as a sprint planner to try to stay organized. It's a little overkill for our team, but I enjoy it and it does help keep me on track.</li>
</ul>
</li>
</ul>
<p>It has come to my attention that I need to do more around the house. Fair enough, I'd agree with that 100%. The problem is, I'm a very checklist oriented person. Rather than ask for a list, which adds one more thing to my wife's plate, I've decided to try to tackle this with <a href="https://home-assistant.io/" target="_blank" rel="noopener noreferrer" class="">Home Assistant</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="goals">Goals<a href="https://joeloveless.com/blog/tracking-chores-with-home-assistant#goals" class="hash-link" aria-label="Direct link to Goals" title="Direct link to Goals" translate="no">​</a></h2>
<p>With this configuration, I'm looking to achieve the following:</p>
<ul>
<li class="">I want a dashboard that contains both weekly and monthly tasks that I can toggle on (finished) and off (todo)</li>
<li class="">On Sunday, the weekly tasks should revert back to "off".</li>
<li class="">On the first of the month, the monthly tasks should revert back to "off".</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-helpers">Creating Helpers<a href="https://joeloveless.com/blog/tracking-chores-with-home-assistant#creating-helpers" class="hash-link" aria-label="Direct link to Creating Helpers" title="Direct link to Creating Helpers" translate="no">​</a></h2>
<ul>
<li class="">In Home Assistant, go to Settings &gt; Devices &amp; Services &gt; Automations</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Helpers" src="data:image/png;base64,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AKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZGGAIAAKZmq1OnTnANAADANAyn0+kLLgIAAJgFw2QAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUCEMAAMDUbBYLeQgAAJiXLTExMbgGAABgGvxqPQAAMDXGyAAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKkRhgAAgKn9f9xGNu9vD0USAAAAAElFTkSuQmCC" width="579" height="170" class="img_ev3q"></p>
<ul>
<li class="">Click on <strong>Create Helper</strong></li>
<li class="">Create a <strong>Toggle</strong> helper<!-- -->
<ul>
<li class="">I put a prefix of <strong>Chores</strong> on each one of mine.</li>
<li class="">Do this for each chore you want to add.</li>
</ul>
</li>
<li class="">Once finished naming, I then went in and edited each entityid to look like this:</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Entity" src="https://joeloveless.com/assets/images/entity-82210426f5ced7864bcc0b19101ae278.png" width="606" height="830" class="img_ev3q"></p>
<p>I added <em>monthly</em> or <em>weekly</em> to each chore that I added, I'll be using this later to help with organization. I also added tags to each one, to help me try to keep this in check.</p>
<p><img decoding="async" loading="lazy" alt="Chores" src="https://joeloveless.com/assets/images/chores_list-662d5c068dccadeec0454c7fdf78826e.png" width="1292" height="539" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-a-dashboard">Creating a Dashboard<a href="https://joeloveless.com/blog/tracking-chores-with-home-assistant#creating-a-dashboard" class="hash-link" aria-label="Direct link to Creating a Dashboard" title="Direct link to Creating a Dashboard" translate="no">​</a></h2>
<p>The next step is to create a dashboard, or if you want, add cards to your already created dashboard.</p>
<p>I've added my .yaml file on my <a href="https://github.com/Pacers31Colts18/HomeAssistant/blob/main/Dashboards/chores_dashboard.yaml" target="_blank" rel="noopener noreferrer" class="">GitHub</a></p>
<p>To get started, the dashboard is the following:</p>
<ul>
<li class="">Single pane dashboard</li>
<li class="">Horizontal stack card</li>
<li class="">Auto Entities HACS integration must be installed.<!-- -->
<ul>
<li class=""><a href="https://github.com/thomasloven/lovelace-auto-entities" target="_blank" rel="noopener noreferrer" class="">Repository</a></li>
</ul>
</li>
</ul>
<p>Because of how we named the entities with <strong>chores_weekly</strong> and <strong>chores_monthly</strong> we can now configure this in the yaml file:</p>
<p><img decoding="async" loading="lazy" alt="Chores YAML" src="https://joeloveless.com/assets/images/chores_yaml-e737a20bbadd67fd9ec30770eec282dc.png" width="481" height="616" class="img_ev3q"></p>
<p>We should now have a fully configured dashboard. When toggled to "on" (done), the toggle will go from ToDo to Finished.</p>
<p><img decoding="async" loading="lazy" alt="Chores Dashboard" src="https://joeloveless.com/assets/images/chores_dashboard-4b0118f0f20a308fc0d90a3c0f3cd5a4.png" width="1635" height="616" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resetting-the-dashboard-with-automations">Resetting the Dashboard with Automations<a href="https://joeloveless.com/blog/tracking-chores-with-home-assistant#resetting-the-dashboard-with-automations" class="hash-link" aria-label="Direct link to Resetting the Dashboard with Automations" title="Direct link to Resetting the Dashboard with Automations" translate="no">​</a></h2>
<p>The final thing I wanted to accomplish was to reset the chores either weekly or monthly. Why would I want to untoggle it myself? Craziness. Both of my automations are on my Github:</p>
<p><a href="https://github.com/Pacers31Colts18/HomeAssistant/blob/main/Automations/Chores%20-%20Reset%20Weekly%20Toggle.yaml" target="_blank" rel="noopener noreferrer" class="">Chores - Reset Weekly Toggle</a>
<a href="https://github.com/Pacers31Colts18/HomeAssistant/blob/main/Automations/Chores%20-%20Reset%20Monthly%20Toggle.yaml" target="_blank" rel="noopener noreferrer" class="">Chores - Reset Monthly Toggle</a></p>
<ul>
<li class="">Go back into Settings &gt; Automations &amp; Scenes</li>
<li class="">Click <strong>Create Automation</strong></li>
<li class="">Choose <strong>Create New Automation</strong></li>
<li class="">Click on the three dots in the top right corner and choose <strong>Edit in YAML</strong></li>
<li class="">Paste the code from my GitHub.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automation-notes">Automation Notes:<a href="https://joeloveless.com/blog/tracking-chores-with-home-assistant#automation-notes" class="hash-link" aria-label="Direct link to Automation Notes:" title="Direct link to Automation Notes:" translate="no">​</a></h3>
<ul>
<li class="">You'll want to change the entity_id list to match the chores you have added.</li>
<li class="">The weekly dashboard is easier, as you can choose the day you want the toggles to reset weekly.</li>
<li class="">The monthly dashboard, I chose to use a template. You could also create a calendar event if you have an integration for that to trigger off of.<!-- -->
<ul>
<li class="">If using the template route, I have mine resetting on the first of each month. Adjust to how you see fit.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://joeloveless.com/blog/tracking-chores-with-home-assistant#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>So that's it, now I have a dashboard where I can keep track of either my chores, or start tracking the chores for the kids also. We could also setup actionable reminders, but for now, I think this will do. The next step in the process is to actually stick with it and do the chores consistently. We'll see if that happens or not.</p>]]></content:encoded>
            <category>Home Assistant</category>
        </item>
        <item>
            <title><![CDATA[What Group Policy Settings aren't in Microsoft Intune?]]></title>
            <link>https://joeloveless.com/blog/what-settings-are-in-intune</link>
            <guid>https://joeloveless.com/blog/what-settings-are-in-intune</guid>
            <pubDate>Sat, 11 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Finding settings that are in Group Policy, but aren't in Intune.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/what-group-policy-settings-arent-in-microsoft-intune-3e3a1df4617c6cea4f04b850bcafb46b.png" width="1200" height="630" class="img_ev3q"></p>
<p>One of the challenges that we've found using Intune, is that there are policies that are in Group Policy (new settings even!), that then are not natively supported through the Settings Catalog in Microsoft Intune. Some of this seems to be random, and no clear reason to why this is the case. What is even more frustrating, is either the lack of clear documentation on what settings are actually available, and when they become available, or categories that have gone away from the ADMX backed method all together and write settings to different locations. In this post, I've written a couple of scripts to help admins with finding the settings that either exist or don't exist by exporting ADMX file data from a folder path, and then parsing that data through the Graph API.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-issues">The Issues<a href="https://joeloveless.com/blog/what-settings-are-in-intune#the-issues" class="hash-link" aria-label="Direct link to The Issues" title="Direct link to The Issues" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="issue-1-settings-in-intune-do-not-write-to-the-same-locations-as-group-policy">Issue #1: Settings in Intune do not write to the same locations as Group Policy<a href="https://joeloveless.com/blog/what-settings-are-in-intune#issue-1-settings-in-intune-do-not-write-to-the-same-locations-as-group-policy" class="hash-link" aria-label="Direct link to Issue #1: Settings in Intune do not write to the same locations as Group Policy" title="Direct link to Issue #1: Settings in Intune do not write to the same locations as Group Policy" translate="no">​</a></h3>
<p>This is not consistent of course. A lot of settings do write to the same location as the group policy settings, but not all. Nothing is more frustrating that inconsistency. I'm sure there are more that fall under this category. But from what I've seen, the following write to different spots in the registry:</p>
<ul>
<li class="">Bitlocker</li>
<li class="">Windows Firewall</li>
<li class="">Windows LAPS</li>
<li class="">Windows Hello for Business</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="issue-2-gpp-preferences">Issue #2: GPP Preferences<a href="https://joeloveless.com/blog/what-settings-are-in-intune#issue-2-gpp-preferences" class="hash-link" aria-label="Direct link to Issue #2: GPP Preferences" title="Direct link to Issue #2: GPP Preferences" translate="no">​</a></h3>
<p>I won't get into that too much here, that's already been asked for several times with no movement.</p>
<ul>
<li class="">Printers</li>
<li class="">Drive Mappings</li>
<li class="">Registry Settings</li>
<li class="">ODBC Settings</li>
<li class="">Files</li>
<li class="">Folders</li>
</ul>
<p>Doesn't seem like that's going to happen anytime soon.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="issue-3-random-settings-in-intune-arent-available">Issue #3: Random settings in Intune aren't available<a href="https://joeloveless.com/blog/what-settings-are-in-intune#issue-3-random-settings-in-intune-arent-available" class="hash-link" aria-label="Direct link to Issue #3: Random settings in Intune aren't available" title="Direct link to Issue #3: Random settings in Intune aren't available" translate="no">​</a></h3>
<p>Why this happens, I have no idea. I see settings from <a href="https://www.microsoft.com/en-us/download/details.aspx?id=55319&amp;msockid=11ab0ae17f91669f2c121f517e176760" target="_blank" rel="noopener noreferrer" class="">Microsoft's Security Compliance Toolkit</a> not available. I know there are settings from CIS' Windows 11 Enterprise Benchmark that aren't available. VSCode even recently introduced Group Policy ADMX templates, but did not introduce Intune Settings Catalog policies? It's a very frustrating scenario, and one that does not seem to have any rhyme or reason to it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="helping-find-whats-available-and-whats-missing">Helping find what's available and what's missing<a href="https://joeloveless.com/blog/what-settings-are-in-intune#helping-find-whats-available-and-whats-missing" class="hash-link" aria-label="Direct link to Helping find what's available and what's missing" title="Direct link to Helping find what's available and what's missing" translate="no">​</a></h2>
<p>I've written two functions recently that I wanted to share with the community, and hope that someone will find it valuable. Both scripts are available on my <a href="https://github.com/pacers31colts18" target="_blank" rel="noopener noreferrer" class="">GitHub</a> or you can find them directly below:</p>
<p><a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Export-GPOADMXSettings.ps1" target="_blank" rel="noopener noreferrer" class="">Export-GPOADMXSettings</a>
<a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneGPOSettings.ps1" target="_blank" rel="noopener noreferrer" class="">Export-IntuneGPOSettings</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="export-gpoadmxsettings">Export-GPOADMXSettings<a href="https://joeloveless.com/blog/what-settings-are-in-intune#export-gpoadmxsettings" class="hash-link" aria-label="Direct link to Export-GPOADMXSettings" title="Direct link to Export-GPOADMXSettings" translate="no">​</a></h2>
<p>While the Group Policy Analytics tool is available in Microsoft Intune, that is more for converting policies from Group Policy to Intune, or at least gathering the data on what is supported in already created group policies. What I am looking to do though, is export the settings from ADMX files themselves, and then find the corresponding Intune settings. Export-GPOADMXSettings helps do just that.</p>
<p>When running the script, you are presented with two paths to take.</p>
<ul>
<li class="">Option 1:<!-- -->
<ul>
<li class="">Enter a folder path that contains the ADMX files you want to analyze. This will then loop through all the ADMX files in the folder. Good for analyzing a few files, or a single file.</li>
</ul>
</li>
<li class="">Option 2:<!-- -->
<ul>
<li class="">Enter a domain name, this will then connect to the Sysvol and output the data.</li>
</ul>
</li>
</ul>
<p>If no parameter is specified, the function will default to loading a CSV file for a list of domains (our org has many). This will allow you to choose multiple domain names, with a <strong>Title</strong> header being the FQDN of the domain. From there, it's going to loop through each file and output the following data:</p>
<ul>
<li class="">Domain<!-- -->
<ul>
<li class="">Domain you connected to (if any)</li>
</ul>
</li>
<li class="">SettingName<!-- -->
<ul>
<li class="">Name of the group policy setting (not the displayName)</li>
</ul>
</li>
<li class="">Class<!-- -->
<ul>
<li class="">HKCU</li>
<li class="">HKLM</li>
<li class="">Both</li>
</ul>
</li>
<li class="">Key<!-- -->
<ul>
<li class="">Registry key path of the policy</li>
</ul>
</li>
<li class="">ValueName<!-- -->
<ul>
<li class="">Registry value name of the policy</li>
</ul>
</li>
<li class="">ValueType<!-- -->
<ul>
<li class="">I tried to categorize this how they are in Group Policy when selecting the options:<!-- -->
<ul>
<li class="">List</li>
<li class="">Enabled/Disabled List</li>
<li class="">Enabled List</li>
<li class="">Disabled List</li>
<li class="">Enabled/Disabled Value</li>
<li class="">Enabled Value</li>
<li class="">Disabled Value</li>
<li class="">TextBox</li>
<li class="">MultiText</li>
<li class="">Number</li>
<li class="">Dropdown</li>
<li class="">Checkbox</li>
<li class="">String</li>
</ul>
</li>
</ul>
</li>
<li class="">ChildKey<!-- -->
<ul>
<li class="">If exists, the child registry key path.</li>
</ul>
</li>
<li class="">ChildValue<!-- -->
<ul>
<li class="">If exists, teh child registry value.</li>
</ul>
</li>
<li class="">Category<!-- -->
<ul>
<li class="">The associated category of the policy</li>
</ul>
</li>
<li class="">SupportedOn<!-- -->
<ul>
<li class="">What OS, AppVersion, etc that the setting has support for.</li>
</ul>
</li>
<li class="">ADMXFile<!-- -->
<ul>
<li class="">The name of the ADMX file processed.</li>
</ul>
</li>
</ul>
<p>Not all of this data is necessary for the next portion, but good to have if you are still working in a hybrid environment on a day to day basis.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="analyzing-common-admx-files">Analyzing Common ADMX files<a href="https://joeloveless.com/blog/what-settings-are-in-intune#analyzing-common-admx-files" class="hash-link" aria-label="Direct link to Analyzing Common ADMX files" title="Direct link to Analyzing Common ADMX files" translate="no">​</a></h3>
<p>I've put example outputs also on my GitHub page, but the direct links are here:</p>
<p><a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Export-GPOADMXSettings/OneDrive_Export-GPOADMXSettings-20251010-1306.csv" target="_blank" rel="noopener noreferrer" class="">Microsoft OneDrive</a>
<a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Export-GPOADMXSettings/FSLogix_Export-GPOADMXSettings-20251010-1337.csv" target="_blank" rel="noopener noreferrer" class="">FSLogix</a>
<a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Export-GPOADMXSettings/Edge_Export-GPOADMXSettings-20251010-1144.csv" target="_blank" rel="noopener noreferrer" class="">Microsoft Edge</a>
<a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Export-GPOADMXSettings/Chrome_Export-GPOADMXSettings-20251010-0807.csv" target="_blank" rel="noopener noreferrer" class="">Google Chrome</a>
<a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Export-GPOADMXSettings/AVD_Export-GPOADMXSettings-20251011-1643.csv" target="_blank" rel="noopener noreferrer" class="">Azure Virtual Desktop</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="export-intunegposettings">Export-IntuneGPOSettings<a href="https://joeloveless.com/blog/what-settings-are-in-intune#export-intunegposettings" class="hash-link" aria-label="Direct link to Export-IntuneGPOSettings" title="Direct link to Export-IntuneGPOSettings" translate="no">​</a></h2>
<p><em>Note: Proper Graph API permissions are required for this. Feel free to checkout my <a href="https://joeloveless.com/2025/07/getting-started-with-microsoft-graph/" target="_blank" rel="noopener noreferrer" class="">Getting Started with Microsoft Graph</a> post if unfamiliar with Graph.</em></p>
<p>Now that we have the Group Policy data, we are going to run the Eport-IntuneGPOSettings function. The main API endpoint that we will be working with on this is this path: <strong><a href="https://graph.microsoft.com/beta/deviceManagement/configurationSettings" target="_blank" rel="noopener noreferrer" class="">https://graph.microsoft.com/beta/deviceManagement/configurationSettings</a></strong></p>
<p>What we are doing with this is loading the previous csv file, and looping through the SettingName, and looking for matches.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#Load CSV</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Try { </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $csv = Import-Csv -path $inputfilepath</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Error "An error occurred importing CSV file: $_"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Gather the settings</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">foreach ($gp in $csv){</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $uri = "https://graph.microsoft.com/beta/deviceManagement/configurationSettings?`$filter=Name eq '$($gp.settingName)'"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $Settings = (Invoke-MGGraphRequest -Method Get -Uri $uri).value | Where-Object { $_.applicability.platform -match 'windows' }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Error "Error gathering settings"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<p>I found that there were matching setting names in iOS, Android, and MacOS when initially running this, so I needed to narrow down the results a little bit more. The unforunate outcome that I found is that a setting name in Edge, can have the same setting name in Google Chrome. Rather than try to filter it down anymore, and have possible bad results, I've decided to leave this as is. Matching the Group Policy Category to the Intune Category was not an easy task.</p>
<p>Once finished, the following results will be returned:</p>
<ul>
<li class="">GroupPolicyName<!-- -->
<ul>
<li class="">SettingName from the last CSV file</li>
</ul>
</li>
<li class="">GroupPolicyKey<!-- -->
<ul>
<li class="">Registry key path from the last CSV file</li>
</ul>
</li>
<li class="">GroupPolicyValueName<!-- -->
<ul>
<li class="">Registry key value name from the last CSV file</li>
</ul>
</li>
<li class="">IntuneName<!-- -->
<ul>
<li class="">Name of the policy in Microsoft Intune</li>
</ul>
</li>
<li class="">IntuneKeywords<!-- -->
<ul>
<li class="">Any of the keywords from the configurationsettings api</li>
</ul>
</li>
<li class="">IntuneRootDefinitionID</li>
<li class="">IntuneDisplayName</li>
<li class="">IntuneHelpText</li>
<li class="">IntuneOffsetURI</li>
<li class="">IntuneMinimumSupportedVersion</li>
<li class="">IntuneWindowsSkus<!-- -->
<ul>
<li class="">Supported Windows Skus for the policy.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="so-what-is-missing">So what is missing?<a href="https://joeloveless.com/blog/what-settings-are-in-intune#so-what-is-missing" class="hash-link" aria-label="Direct link to So what is missing?" title="Direct link to So what is missing?" translate="no">​</a></h2>
<p>From the CSV files I gave above, let's take a look at what settings are available in Group Policy, available in Microsoft Intune, and what are missing in Microsoft Intune.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="azure-virtual-desktop">Azure Virtual Desktop<a href="https://joeloveless.com/blog/what-settings-are-in-intune#azure-virtual-desktop" class="hash-link" aria-label="Direct link to Azure Virtual Desktop" title="Direct link to Azure Virtual Desktop" translate="no">​</a></h3>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneGPOSettings/AVD_Export-IntuneGPOSettings-20251011-1722.csv" target="_blank" rel="noopener noreferrer" class="">AVD Results</a></p>
<ul>
<li class="">7 Group Policy settings</li>
<li class="">8 Microsoft Intune settings<!-- -->
<ul>
<li class="">AVD_SERVER_WATERMARKING is listed twice in Microsoft Intune<!-- -->
<ul>
<li class="">[Deprecated] Enable watermarking</li>
<li class="">Enable watermarking
So this is good, we have 100% support for AVD settings in the admx template file.
<a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneGPOSettings/Chrome_Export-IntuneGPOSettings-20251010-0808.csv" target="_blank" rel="noopener noreferrer" class="">Google Chrome</a></li>
</ul>
</li>
</ul>
</li>
<li class="">691 Group Policy Settings</li>
</ul>
<p>I ran into a lot of duplicates here with Microsoft Edge being in the mix, along with settings marked Deprecated. Instead, I'll filter by Blanks on the CSV file.</p>
<ul>
<li class="">110 missing settings!</li>
</ul>
<p>Not good! Within these missing settings, the ones that stick out to me the most are:</p>
<ul>
<li class="">AIModeSettings</li>
<li class="">DevToolsGenAiSettings</li>
<li class="">GeminiSettings</li>
<li class="">PasswordDismissCompromisedAlertEnabled</li>
<li class="">PasswordManagerPasskeysEnabled</li>
<li class="">PasswordSharingEnabled</li>
<li class="">PrivacySandboxAdMeasurementEnabled</li>
<li class="">PrivacySandboxAdTopicsEnabled</li>
<li class="">PrivacySandboxFingerprintingProtectionEnabled</li>
<li class="">PrivacySandboxIpProtectionEnabled</li>
<li class="">PrivacySandboxPromptEnabled</li>
<li class="">PrivacySandboxSiteEnabledAdsEnabled</li>
</ul>
<p>I would think/hope that there are orgs that would want a little bit more control over AI settings, password policies, and sandboxes in Intune that are purely cloud native?
<a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneGPOSettings/Edge_Export-IntuneGPOSettings-20251010-1144.csv" target="_blank" rel="noopener noreferrer" class="">Microsoft Edge</a></p>
<ul>
<li class="">835 Group Policy Settings</li>
</ul>
<p>Similar to Google Chrome, we're going to see a lot of duplicates with the naming convention being the same.</p>
<ul>
<li class="">
<p>20 missing settings. I do not understand how a Microsoft product doesn't have support in a Microsoft product. Below are the missing settings:</p>
</li>
<li class="">
<p>FileOrDirectoryPickerWithoutGestureAllowedForOrigins</p>
</li>
<li class="">
<p>LiveVideoTranslationEnabled</p>
</li>
<li class="">
<p>EdgeWalletCheckoutEnabled</p>
</li>
<li class="">
<p>EdgeWalletCheckoutEnabled_recommended</p>
</li>
<li class="">
<p>ShowTabPreviewEnabled</p>
</li>
<li class="">
<p>ShowTabPreviewEnabled_recommended</p>
</li>
<li class="">
<p>Pol_EdgePreviewEnrollmentTypeMicrosoftEdge</p>
</li>
<li class="">
<p>BlockTruncatedCookies</p>
</li>
<li class="">
<p>ChannelSearchKind</p>
</li>
<li class="">
<p>ReleaseChannels</p>
</li>
<li class="">
<p>AccessControlAllowMethodsInCORSPreflightSpecConformant</p>
</li>
<li class="">
<p>OriginKeyedProcessesEnabled_recommended</p>
</li>
<li class="">
<p>RelaunchFastIfOutdated</p>
</li>
<li class="">
<p>AccessControlAllowMethodsInCORSPreflightSpecConformant</p>
</li>
<li class="">
<p>BlockTruncatedCookies</p>
</li>
<li class="">
<p>WAMAuthBelowWin10RS3Enabled</p>
</li>
<li class="">
<p>WebRtcPostQuantumKeyAgreement</p>
</li>
<li class="">
<p>AllowBackForwardCacheForCacheControlNoStorePageEnabled</p>
</li>
<li class="">
<p>ExtensionsPerformanceDetectorEnabled_recommended</p>
</li>
<li class="">
<p>PrivateNetworkAccessRestrictionsEnabled
<a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneGPOSettings/FSLogix_Export-IntuneGPOSettings-20251010-1338.csv" target="_blank" rel="noopener noreferrer" class="">FSLogix</a></p>
</li>
<li class="">
<p>116 Group Policy settings</p>
</li>
<li class="">
<p>Only 1 missing setting, this one might make sense:</p>
<ul>
<li class="">ProfilesRoamGroupPolicyState
<a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneGPOSettings/OneDrive_Export-IntuneGPOSettings-20251010-1306.csv" target="_blank" rel="noopener noreferrer" class="">Microsoft OneDrive</a></li>
</ul>
</li>
<li class="">
<p>65 Group Policy Settings</p>
</li>
<li class="">
<p>6 missing Intune settings</p>
<ul>
<li class="">AddedFolderHardDeleteOnUnmount</li>
<li class="">DisableOfflineModeForExternalLibraries</li>
<li class="">DisableOfflineMode</li>
<li class="">AddedFolderUnmountOnPermissionsLoss</li>
<li class="">SharePointOnPremApplicationIdUri</li>
<li class="">SharePointOnPremOIDC</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-it-up">Wrapping It Up<a href="https://joeloveless.com/blog/what-settings-are-in-intune#wrapping-it-up" class="hash-link" aria-label="Direct link to Wrapping It Up" title="Direct link to Wrapping It Up" translate="no">​</a></h2>
<p>These are just some of the examples I ran into, with more being on the Windows setting side. Last time we refreshed our security baseline (pre-24H2), there were a lot more settings missing. I believe our count was around 180 settings not in the Settings Catalog. I'd be curious to know what the reasoning is behind settings not being available, especially settings that resolve CVE's.</p>]]></content:encoded>
            <category>Group Policy</category>
            <category>Intune</category>
            <category>PowerShell</category>
            <category>Graph API</category>
        </item>
        <item>
            <title><![CDATA[Exploring Microsoft Intune Filters]]></title>
            <link>https://joeloveless.com/blog/exploring-microsoft-intune-filters</link>
            <guid>https://joeloveless.com/blog/exploring-microsoft-intune-filters</guid>
            <pubDate>Sat, 04 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A walk through on the options and setup of filters in Microsoft Intune.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/exploring-microsoft-intune-filters-3526e4f7d4066084ac8cde41c98c6ca2.png" width="1200" height="630" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="intro">Intro<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#intro" class="hash-link" aria-label="Direct link to Intro" title="Direct link to Intro" translate="no">​</a></h2>
<p>It's been a couple weeks since I've made a post. A lot has been happening at our house. I went camping a few weekends ago with my daughter at Mille Lacs State Park. It was our first time checking out the Brainerd, MN area. We did a lot of hiking, and passed our 50 mile mark for the MN State Park Hiking Club. We visited Mille Lacs State Park, Crow Wing State Park, Father Hennepin State Park, and Lake Maria State Park before heading back home. It was a great weekend camping with her and checking out the area.</p>
<p><img decoding="async" loading="lazy" alt="MN Hiking Club" src="https://joeloveless.com/assets/images/hikingclub-c9180da0218442e195ee001aa0c7a397.jpg" width="4000" height="3000" class="img_ev3q"></p>
<p>I'm a sucker for stopping for a good road-side attraction. When the whole family is in the car, I don't get the chance to stop. But when by myself or the kids, yeah I'll stop at every place I can. I probably get that from my dad. He stopped at the Corn Palace in Mitchell, South Dakota once.
<img decoding="async" loading="lazy" alt="Paul Bunyan Land" src="https://joeloveless.com/assets/images/babe-478ba992c9dfbb2da1a5f42ed7feed24.jpg" width="4000" height="3000" class="img_ev3q"></p>
<p>I bought this hammock for myself on camping trips. I don't think I've used it for more than 5 minutes, the kids love to chill and read books in it though. It also protects from falling acorns.
<img decoding="async" loading="lazy" alt="Hammock" src="https://joeloveless.com/assets/images/hammock-7355d496c4470341a763fa63e2061402.jpg" width="4000" height="3000" class="img_ev3q"></p>
<p>Since then, the kids have been back in school. She is starting 9th grade, while my son is starting 7th grade. They've homeschooled the last few years, and now are doing online schooling through Minnetonka. Most likely, they'll transition back to in-person school after this year, depending on if we move or not. Where we live now is pretty damn rural, and not a lot to offer for them. With the RTO fun, we might be on the move (again). We'll see what the future holds, any move probably wouldn't be until the spring.</p>
<p>I wanted to write a bit about using filters with Microsoft Intune, and provide the filters we use.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-are-filters">What are filters?<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#what-are-filters" class="hash-link" aria-label="Direct link to What are filters?" title="Direct link to What are filters?" translate="no">​</a></h2>
<p><a href="https://techcommunity.microsoft.com/blog/intunecustomersuccess/intune-grouping-targeting-and-filtering-recommendations-for-best-performance/2983058/replies/3571242" target="_blank" rel="noopener noreferrer" class="">Scott Duffey</a> explains filters the best. My explanation is below.</p>
<ul>
<li class="">Entra has groups.<!-- -->
<ul>
<li class="">Dynamic or Static groups<!-- -->
<ul>
<li class="">Dynamic groups take a bit of time to process.<!-- -->
<ul>
<li class="">This can cause slowness on the processing of policy assignments on a device.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class="">Intune has "virtual groups". The recommendation is to use these whenever possible.<!-- -->
<ul>
<li class="">All Devices</li>
<li class="">All Users</li>
</ul>
</li>
<li class="">With filters, you can then narrow down the scope of your assignments.<!-- -->
<ul>
<li class="">This is done at the device check-in, and is faster than the processing of groups.</li>
</ul>
</li>
</ul>
<p>Recommendation: Use the built-in virtual groups + filters to be granular rather than relying on Dynamic or Static Groups.</p>
<p>Notes: There are limitations to filters, just like there are limitations to the groups. We have not been able to 100% use filters, nor 100% use groups. For the most part, our assignments look like this:</p>
<ul>
<li class="">Base policies will get assigned either All Devices or All Users (depending on the scope of the policy) with a filter assigned for the OS.</li>
<li class="">If a group needs to be excluded, we then exclude the group, and assign to an exception profile that will include the same filter.<!-- -->
<ul>
<li class="">Note: I'll be writing a post on our policy design at a later date. We've gone through many iterations of this and I believe we've landed on something that works best.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-filters">Creating Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#creating-filters" class="hash-link" aria-label="Direct link to Creating Filters" title="Direct link to Creating Filters" translate="no">​</a></h2>
<p>An admin can create filters by going to <a href="https://intune.microsoft.com/" target="_blank" rel="noopener noreferrer" class="">Microsoft Intune</a>, choosing Devices, and then going to Assignment Filters. Once there, you should see a create button with two options:</p>
<ol>
<li class="">Managed Devices</li>
<li class="">Managed Apps</li>
</ol>
<p>There are a variety of different rules that you can create, for the full list, see <a href="https://learn.microsoft.com/en-us/intune/intune-service/fundamentals/filters-device-properties" target="_blank" rel="noopener noreferrer" class="">Microsoft Learn</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-windows-filters">Common Windows Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#common-windows-filters" class="hash-link" aria-label="Direct link to Common Windows Filters" title="Direct link to Common Windows Filters" translate="no">​</a></h2>
<p>Now that you know what filters are, I want to provide you with the common Windows-based filters we use at our organization. Mostly, we use them for operating system criteria, AutoPilot group tags, and enrollment profiles. Obviously, there are other use cases.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="join-type-filters">Join Type Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#join-type-filters" class="hash-link" aria-label="Direct link to Join Type Filters" title="Direct link to Join Type Filters" translate="no">​</a></h3>
<table><thead><tr><th>Filter Name</th><th>Filter Rule</th><th>Description</th></tr></thead><tbody><tr><td>ALL_EntraJoin_PROD</td><td><code>(device.deviceTrustType -eq "Azure AD joined")</code></td><td>All Entra joined endpoints.</td></tr><tr><td>ALL_HybridJoin_PROD</td><td><code>(device.deviceTrustType -eq "Hybrid Azure AD joined")</code></td><td>All hybrid joined endpoints.</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="windows-11-filters">Windows 11 Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#windows-11-filters" class="hash-link" aria-label="Direct link to Windows 11 Filters" title="Direct link to Windows 11 Filters" translate="no">​</a></h3>
<table><thead><tr><th>Filter Name</th><th>Filter Rule</th><th>Description</th></tr></thead><tbody><tr><td>ALL_W11_PROD</td><td><code>((device.osVersion -startsWith "10.0.22") or (device.osVersion -startsWith "10.0.26"))</code></td><td>All Windows 11 endpoints.</td></tr><tr><td>ALL_W11_ENT_PROD</td><td><code>(device.osVersion -startsWith "10.0.22") or (device.osVersion -startsWith "10.0.26")) and (device.operatingSystemSKU -eq "Enterprise")</code></td><td>All Windows 11 Enterprise endpoints.</td></tr><tr><td>ALL_W11_PRO_PROD</td><td><code>((device.osVersion -startsWith "10.0.22") or (device.osVersion -startsWith "10.0.26")) and (device.operatingSystemSKU -eq "Professional")</code></td><td>All Windows 11 Professional endpoints.</td></tr><tr><td>ALL_W11_RDSH_PROD</td><td><code>((device.osVersion -startsWith "10.0.22") or (device.osVersion -startsWith "10.0.26")) and (device.operatingSystemSKU -eq "ServerRdsh")</code></td><td>All Windows 11 multi-session endpoints.</td></tr><tr><td>ALL_W11_LTSC_PROD</td><td><code>((device.osVersion -startsWith "10.0.22") or (device.osVersion -startsWith "10.0.26")) and (device.operatingSystemSKU -eq "EnterpriseS")</code></td><td>All Windows 11 LTSC endpoints.</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="virtual-machine-filters">Virtual Machine Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#virtual-machine-filters" class="hash-link" aria-label="Direct link to Virtual Machine Filters" title="Direct link to Virtual Machine Filters" translate="no">​</a></h3>
<table><thead><tr><th>Filter Name</th><th>Filter Rule</th><th>Description</th></tr></thead><tbody><tr><td>ALL_WIN_VirtualMachines_PROD</td><td><code>(device.model -eq "Virtual Machine") or (device.model -eq "VMWare")</code></td><td>All virtual machines</td></tr><tr><td>ALL_WIN_VMWare_PROD</td><td><code>(device.manufacturer -eq "VMWare, Inc.")</code></td><td>All VMWare Virtual Machines</td></tr><tr><td>ALL_WIN_Hyper-V_PROD</td><td><code>(device.manufacturer -eq "Microsoft Corporation") and (device.model -eq "Virtual Machine")</code></td><td>All Hyper-V Virtual machines</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="azure-virtual-desktop-filters">Azure Virtual Desktop Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#azure-virtual-desktop-filters" class="hash-link" aria-label="Direct link to Azure Virtual Desktop Filters" title="Direct link to Azure Virtual Desktop Filters" translate="no">​</a></h3>
<table><thead><tr><th>Filter Name</th><th>Filter Rule</th><th>Description</th></tr></thead><tbody><tr><td>ALL_AVD_PROD</td><td><code>((device.operatingSystemSKU -eq "ServerRdsh") or (device.operatingSystemSKU -eq "Enterprise")) and (device.manufacturer -eq "Microsoft Corporation") and (device.model -eq "Virtual Machine")</code></td><td>All AVD hosts.</td></tr><tr><td>ALL_AVD_Personal_PROD</td><td><code>(device.operatingSystemSKU -eq "Enterprise") and (device.manufacturer -eq "Microsoft Corporation") and (device.model -eq "Virtual Machine")</code></td><td>All Personal AVD hosts.</td></tr><tr><td>ALL_AVD_Pooled_PROD</td><td><code>(device.operatingSystemSKU -eq "ServerRdsh") and (device.manufacturer -eq "Microsoft Corporation") and (device.model -eq "Virtual Machine")</code></td><td>All pooled and remote app hosts.</td></tr></tbody></table>
<p><em>Note: If you have Hyper-V based virtual machines that are not AVD hosts, these filters will need to be tweaked. Intune does not have a great way of detecting these with filters other than OS. Naming conventions is probably the easiest way to detect, but that is not fool proof either.</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="autopilot-filters">AutoPilot Filters<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#autopilot-filters" class="hash-link" aria-label="Direct link to AutoPilot Filters" title="Direct link to AutoPilot Filters" translate="no">​</a></h3>
<table><thead><tr><th>Filter Name</th><th>Filter Rule</th><th>Description</th></tr></thead><tbody><tr><td>ALL_AutoPilotEnrollment_PROD</td><td><code>(device.enrollmentProfileName -eq "ProfileName")</code></td><td>Autopilot example filter</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://joeloveless.com/blog/exploring-microsoft-intune-filters#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>I'm curious if anyone has a better design for the AVD limitations that we've ran into for filters. We've also noticed that Microsoft really hasn't expanded much on filters past the initial set they released. Hopefully this is something that can be expanded on in the future. I'd love to see this tie into the Properties Catalog more, or to have more WMI based filtering similar to what Group Policy had.</p>]]></content:encoded>
            <category>Intune</category>
        </item>
        <item>
            <title><![CDATA[Making Dexcom better with Home Assistant Voice]]></title>
            <link>https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant</link>
            <guid>https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant</guid>
            <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Integrating Dexcom with Home Assistant Voice.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/making-dexcom-better-with-home-assistant-voice-c0f99ed9a1acf7f87a81e2e8fa7945c3.png" width="1200" height="630" class="img_ev3q"></p>
<p>Hi everyone. I recently ordered a couple of the somewhat recently released <a href="https://www.home-assistant.io/voice-pe/" target="_blank" rel="noopener noreferrer" class="">Home Assistant Voice Preview Edition's</a>, for a very specific need for my Type 1 Diabetic wife. She currently uses Dexcom G6, and while helpful, does have a laundry list of complaints with the product.</p>
<ul>
<li class="">Sensors disconnecting randomly. She does not know of the disconnect until she pulls her phone out to look at the app to check her glucose level. This can be quite problematic for a diabetic, and pretty dangerous.</li>
<li class="">Having to constantly pull her phone out to check. While quite an improvement over the technology of yesteryear, can be a distraction for people, especially if they have ADHD.</li>
</ul>
<p>Like any technologist, I got thinking of how I could help solve this. Selfishly, like any technologist, when you get the chance to play with new devices, you have to take advantage of that opportunity.</p>
<p>I'm a big fan of Home Assistant. Unfortunately, getting the Wife Approval Factor in place has been a challenge. Mainly because she doesn't care for technology all that much, and finds my tinkering to be a waste of money. I'll show her I said!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="researching-options">Researching Options<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#researching-options" class="hash-link" aria-label="Direct link to Researching Options" title="Direct link to Researching Options" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="digging-for-the-solution">Digging for the Solution<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#digging-for-the-solution" class="hash-link" aria-label="Direct link to Digging for the Solution" title="Direct link to Digging for the Solution" translate="no">​</a></h3>
<p>Before I started ordering these, I had to brainstorm a little bit, and think how I could accomplish what I <strong>think</strong> she wanted. I had possible ideas in mind.</p>
<p>At MMS 2025, <a href="https://mattzaske.com/blog/2025-07/configuringusing-awtrix-3-ulanzi-tc001-through-home-assistant" target="_blank" rel="noopener noreferrer" class="">Matt Zaske</a> had a demo on the Awtrix 3. This was the inital rabbit hole I started to go down, an always on display showing what was needed.</p>
<ul>
<li class="">Diabetic Specific Solutions<!-- -->
<ul>
<li class=""><a href="https://customtypeone.com/products/sugarpixel" target="_blank" rel="noopener noreferrer" class="">Sugar Pixels</a></li>
<li class=""><a href="https://glowcose.com/" target="_blank" rel="noopener noreferrer" class="">Glowcose</a></li>
</ul>
</li>
<li class="">DIY Solutions<!-- -->
<ul>
<li class="">Awtrix 3<!-- -->
<ul>
<li class="">Not a fan of the pixelated interface in my living room.</li>
</ul>
</li>
<li class="">ESP32 Displays<!-- -->
<ul>
<li class="">Doable, but do I want to and will I do the tinkering? My fear is this would be a Raspberry Pi that I order and then never look at.</li>
</ul>
</li>
<li class="">Tablets<!-- -->
<ul>
<li class="">Not sure I want to buy a tablet just for this purpose.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>I liked the idea of the Glowcose, but I didn't like that it was limited to a single use case. I could have gone the more DIY route, but factoring in time and cost, I'm not sure how much I'd be on board. There is also the wife acceptance factor, I didn't want a frankenstein of wires throughout the house.</p>
<p>While in the <a href="https://winadmins.io/" target="_blank" rel="noopener noreferrer" class="">WinAdmins</a> discord, someone was talking about the Home Assistant Voice that they recently ordered. From there, my curiosity (or ADHD) sparked my interest and I got going down the rabbit hole.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-i-chose-home-assistant-voice">Why I chose Home Assistant Voice<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#why-i-chose-home-assistant-voice" class="hash-link" aria-label="Direct link to Why I chose Home Assistant Voice" title="Direct link to Why I chose Home Assistant Voice" translate="no">​</a></h3>
<p>Home Assistant Voice gave me multiple options. I didn't want to have a device that only solved one use case, I wanted to solve (or tinker) multiple solutions.</p>
<ul>
<li class="">✅️Multiple use cases<!-- -->
<ul>
<li class="">This can be used for other automations, not just Dexcom. Win for me!</li>
</ul>
</li>
<li class="">✅️Ability to ask questions<!-- -->
<ul>
<li class="">"Hey Jarvis, what's my wife's glucose level?</li>
</ul>
</li>
<li class="">✅️Ability to glance over and get a sense of the status<!-- -->
<ul>
<li class="">Home Assistant Voice has a ring LED light you can program.</li>
</ul>
</li>
<li class="">✅️Local and not cloud based.<!-- -->
<ul>
<li class="">I've had enough issues with Google Home getting worse and worse over the years, I didn't want to invest in a cloud based product.</li>
</ul>
</li>
<li class="">✅️ BONUS: A programmable button that you can automate<!-- -->
<ul>
<li class="">4 button combos are available:<!-- -->
<ul>
<li class="">Long Press</li>
<li class="">Double Press</li>
<li class="">Triple Press</li>
<li class="">Easter Egg Press</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="unboxing-and-setup">Unboxing and Setup<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#unboxing-and-setup" class="hash-link" aria-label="Direct link to Unboxing and Setup" title="Direct link to Unboxing and Setup" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="unboxing-the-device">Unboxing the Device<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#unboxing-the-device" class="hash-link" aria-label="Direct link to Unboxing the Device" title="Direct link to Unboxing the Device" translate="no">​</a></h3>
<p>My only complaint so far, the device did not come with something to power the device. I'm not a fan of manufacturers doing this. For some reason</p>
<ul>
<li class="">I found 8 extra USB-C cables in 8 different locations in either my office, garage, or bedroom. Why are they in the garage? Not sure. Surprisingly, none were in my junk drawer in the kitchen.<!-- -->
<ul>
<li class="">For those curious:<!-- -->
<ul>
<li class="">5 USB-C to USB-C.</li>
<li class="">3 USB-A to USB-C.</li>
<li class="">A gazillion Micro USB cables.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>For some reason, I do not have 8 extra USB-C power bricks. For the time being, I took a brick off my Pixel dock to use.</p>
<p><img decoding="async" loading="lazy" alt="Unboxing" src="https://joeloveless.com/assets/images/unboxing-2cec5c4bdad2af7d5f3684e92a544777.png" width="650" height="488" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-the-device">Setting up the Device<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#setting-up-the-device" class="hash-link" aria-label="Direct link to Setting up the Device" title="Direct link to Setting up the Device" translate="no">​</a></h3>
<p>There are some requirements before setting up the device for this use case.</p>
<ul>
<li class="">Must already be running Home Assistant</li>
<li class="">Must have the Dexcom Integration added</li>
</ul>
<p>Once those parts are done, device setup was pretty straight forward. I followed the instructions given in the box, and in less than 5 minutes, I had my device setup. I found the process to be much smoother and straight forward than Google Home or Amazon Alexa. I didn't have a ton of screens to opt in and out of "features" that I don't want.</p>
<p>One last step to note, you then will need to expose the Dexcom entities to your Voice Assistants.</p>
<p><img decoding="async" loading="lazy" alt="Expose" src="https://joeloveless.com/assets/images/expose-e0c8731ce8e09cdb73967e601391bf77.png" width="500" height="432" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automations">Automations<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#automations" class="hash-link" aria-label="Direct link to Automations" title="Direct link to Automations" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-do-i-begin">Where do I begin?<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#where-do-i-begin" class="hash-link" aria-label="Direct link to Where do I begin?" title="Direct link to Where do I begin?" translate="no">​</a></h3>
<p>Back to the original use cases, I wanted to do the following:</p>
<ul>
<li class="">Give her the ability to glance over, get a sense of what her glucose level is.<!-- -->
<ul>
<li class="">LED Ring</li>
</ul>
</li>
<li class="">Be able to know if her glucose level is rapidly rising or falling.<!-- -->
<ul>
<li class="">LED Ring</li>
</ul>
</li>
<li class="">Be able to check her glucose level without pulling her phone out.<!-- -->
<ul>
<li class="">Button press</li>
<li class="">Talking to the device and getting a response</li>
</ul>
</li>
</ul>
<p><em>Before going further, you can find my automations on my <a href="https://github.com/Pacers31Colts18/HomeAssistant/tree/main/Automations" target="_blank" rel="noopener noreferrer" class="">GitHub</a>. I've omitted the username, replace the USERNAME with your username. You will also need to tweak to match the entity ID of your voice assistant.</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="led-ring-automation">LED Ring Automation<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#led-ring-automation" class="hash-link" aria-label="Direct link to LED Ring Automation" title="Direct link to LED Ring Automation" translate="no">​</a></h3>
<p>Thinking further through the use cases, I came up with the following:</p>
<ul>
<li class="">LED Light: Red<!-- -->
<ul>
<li class="">glucose level is below 80</li>
<li class="">glucose level is above 150</li>
</ul>
</li>
<li class="">LED Light: Blue<!-- -->
<ul>
<li class="">Status is Unavailable or Unknown. Dexcom, for whatever reason in the past 6 months or so has been awful about the sensor not communicating properly with the transmitter. There are plenty of Reddit threads on the issue, but Dexcom doesn't seem to be addressing the issue. I wanted to provide a light that would make her aware when the sensor was not communicating.</li>
</ul>
</li>
<li class="">LED Light: Green<!-- -->
<ul>
<li class="">I had trouble getting the light automation to go from the off state back to the on state. I didn't necessarily want a bright green light all the time. Instead what I did for this one was to set it to Green, but turn the brightness down to 0 percent. That way, if she does decide that she would like the light on, we can just increase the percentage.</li>
</ul>
</li>
<li class="">LED Light: Yellow<!-- -->
<ul>
<li class="">Dexcom provides trend statuses to tell the user how quickly their glucose level is either increasing or decreasing. With the light being yellow, means the trend is either:<!-- -->
<ul>
<li class="">Rising Quickly</li>
<li class="">Falling Quickly</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>This gives me 7 total branches in my automation.</p>
<ul>
<li class="">Option 1: glucose level is either above 150 AND below 80</li>
<li class="">Option 2: glucose level is below 80</li>
<li class="">Option 3: glucose level is above 150</li>
<li class="">Option 4: glucose level is rising quickly</li>
<li class="">Option 5: glucose level is falling quickly</li>
<li class="">Option 6: glucose level is unknown</li>
<li class="">Option 7: glucose level is unavailable</li>
</ul>
<p><em>Note: I do not know what the difference is between Unknown and Unavailable, Home Assistant gave me both of those options. My theory is Unavailable is when it stops communicating completely, Unknown is when it just can't read the value yet</em></p>
<p><img decoding="async" loading="lazy" alt="Blue" src="https://joeloveless.com/assets/images/blue-945619acc94656802c2f8804c7be56d6.png" width="500" height="667" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="button-press-automation">Button Press Automation<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#button-press-automation" class="hash-link" aria-label="Direct link to Button Press Automation" title="Direct link to Button Press Automation" translate="no">​</a></h3>
<p>The next automation to do was to configure the button press. This one was much simpler, as I'm just wanting the glucose level announced when doubling pressing the button.</p>
<p>I love how there are multiple button combinations you can do. My yaml file is just stripped down to the basics of this automation for simplicity.</p>
<p><em>Future enhancement idea: Let me Morse code to the other Home Assistant Voice through taps and have it translate to speech</em></p>
<!--$?--><template id="B:0"></template><!--/$-->
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="voice-announcement">Voice Announcement<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#voice-announcement" class="hash-link" aria-label="Direct link to Voice Announcement" title="Direct link to Voice Announcement" translate="no">​</a></h3>
<p>Of course, you can also talk to the device to get the current glucose level. I love the different options that are available on this thing!</p>
<!--$?--><template id="B:1"></template><!--/$-->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://joeloveless.com/blog/making-dexcom-better-wtih-homeassistant#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>A little bit different of a post, but hopefully helpful to others out there! Let me know if you have any questions or comments, I've added a shiny new GitHub commenting system.</p>]]></content:encoded>
            <category>Home Assistant</category>
        </item>
        <item>
            <title><![CDATA[Finding users that are enrolled in Windows Hello For Business]]></title>
            <link>https://joeloveless.com/blog/finding-enrollment-count-windows-hello</link>
            <guid>https://joeloveless.com/blog/finding-enrollment-count-windows-hello</guid>
            <pubDate>Sun, 31 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A guide to finding how many users are enrolled in Windows Hello for Business on a device.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/finding-users-that-are-enrolled-in-windows-hello-for-business-b404f4f2f52d86c89520a7a0c0bc2cf9.png" width="1200" height="630" class="img_ev3q"></p>
<p>It's been a minute since my last post. Since then, we've still been rolling out Windows Hello for Business at work as a project, along with many other tasks, projects, etc. Employee review season is upon us, so I need to start figuring out what all I did in the last year, and what I want to achieve in the upcoming year. It's one of those exercises I'm not the biggest fan of, mainly because I feel that I have to talk about myself more than I ever want to.</p>
<p>On the home front, we've been gearing up for the new school year and the end of the summer. I'm hoping to take my daughter camping next weekend, maybe some place up north in Minnesota. Anything is up north for us in Minnesota, as we live in one of the bordering counties of Iowa. What I really mean by up north is past the Twin Cities. I've also started yet another project, trying to finish the drywall mudding in my office/loft above the garage. The previous owners finished the mudding about 40% of the way. I'm now three years into living at this house, and just attempting to get it done. By the time I do get it done, we'll probably/hopefully be moving again.</p>
<p>From the blog side of things, I did a little thinking and reading and decided to do away with the ChatGPT generated images. I found them interesting, yes, but also felt that it sort of cheapens everything that I am trying to do. If I am willing to put in the work and write, why should I just use AI generated images? Seems silly to me. Unfortunately, I am not a graphic artist. I tried that route when I was about 19 and found out quickly that it wasn't for me (aka I sucked at it) at Ivy Tech Community College. So I am playing around with the idea of templated cover images instead. Let me know what you think!</p>
<p>One of the asks in this project, as we've been rolling out Windows Hello to internal pilot testers, was gathering how many users/devices have actually enrolled in Windows Hello for Business on their device.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-entra-way">The Entra Way<a href="https://joeloveless.com/blog/finding-enrollment-count-windows-hello#the-entra-way" class="hash-link" aria-label="Direct link to The Entra Way" title="Direct link to The Entra Way" translate="no">​</a></h2>
<p>Before I dive into the detection script I have, I want to point out that this data is available in Microsoft Entra natively under the Authentication Methods blade. If you work in an enterprise environment, you might not have the proper roles to access that data. From what I have seen also, this data is more "user-centric" rather than device based. I was looking for the data from the device side of things.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="intune-detection-scripts">Intune Detection Scripts<a href="https://joeloveless.com/blog/finding-enrollment-count-windows-hello#intune-detection-scripts" class="hash-link" aria-label="Direct link to Intune Detection Scripts" title="Direct link to Intune Detection Scripts" translate="no">​</a></h2>
<p>At the same time I was trying to piece everything together, Johanne's dropped a timely blog post that gave me the starting point of my script. Check it out <a href="https://johannesblog.com/2025/08/07/last-authentication-used/" target="_blank" rel="noopener noreferrer" class="">here</a>. His blog goes into finding the last authentication method used.</p>
<p>Using that script, I now know that D6886603-9D2F-4EB2-B667-1971041FA96B = 'WHFB PIN, NGC Credential Provider'.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{D6886603-9D2F-4EB2-B667-1971041FA96B}</span><br></span></code></pre></div></div>
<p>You can find the script <a href="https://github.com/Pacers31Colts18/Intune/blob/main/RemediationScripts/Detection-WindowsHelloForBusiness-EnrollmentCount.ps1" target="_blank" rel="noopener noreferrer" class="">here</a>. Now, create a new Detection Script in Microsoft Intune under Scripts and Remediations.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="detection-script-setup">Detection Script Setup<a href="https://joeloveless.com/blog/finding-enrollment-count-windows-hello#detection-script-setup" class="hash-link" aria-label="Direct link to Detection Script Setup" title="Direct link to Detection Script Setup" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="Detection Create Basics" src="https://joeloveless.com/assets/images/detection_create_basics-e8a81faf979fa535c353a7f91599ba78.png" width="826" height="433" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Detection Create Settings" src="https://joeloveless.com/assets/images/detection_create_settings-9cd87d53342019e3727193873c755c75.png" width="816" height="736" class="img_ev3q"></p>
<p>Once the data is gathered, under the Column settings, turn on the <strong>Pre-remediation detection output</strong> under Device Status, this will give you the output of how many users are enrolled.</p>
<p><img decoding="async" loading="lazy" alt="Detection Output" src="https://joeloveless.com/assets/images/detection_output-d0744148f3ecdf3d5f91f6c5d216eb5c.png" width="536" height="452" class="img_ev3q"></p>
<p>Clicking on review, will then give you the total number of users enrolled. Of course, I would recommend exporting the data instead, and then provide that data to whoever would need it.</p>]]></content:encoded>
            <category>Windows Hello</category>
            <category>Remediation Scripts</category>
            <category>Intune</category>
        </item>
        <item>
            <title><![CDATA[Gathering Data for a Windows Hello for Business Rollout]]></title>
            <link>https://joeloveless.com/blog/gathering-data-for-windows-hello</link>
            <guid>https://joeloveless.com/blog/gathering-data-for-windows-hello</guid>
            <pubDate>Sat, 09 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A guide to gathering data for a successful Windows Hello For Business rollout using Microsoft Intune detection scripts.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/gathering-data-for-a-windows-hello-for-business-rollout-d0f0bd12d0a131e37928cbf9868ce9d1.png" width="1200" height="630" class="img_ev3q"></p>
<p>Hello there! It's been a couple weeks since my last post. I've been slammed at work with various projects (Azure Virtual Desktop, Windows Hello for Business, etc, etc), studying for the AZ-104 certification (trying to meet my employee goal before the end of the year), various things around the house (still working on flooring, painting, gardening), and the usual business of summer. My son got his first stripe in jiujitsu. He's been doing this since about February/March, trying to improve his mental health and getting more active. While he certainly has his days where he doesn't want to go, he's been pretty great about going two times a week. When he leaves class, he's always smiling and happy. Seeing his improvement both with his overall attitude and how he's improving with jiujitsu has been awesome to see. I'm pretty proud of him for sticking with it, and getting his first stripe!</p>
<p>I am trying to be more consistent with this, but struggling to either find the time, or just having general writer's block. I need to get out of the mindset of not writing something because someone else has already written it, and just try to write something from my perspective. One of the projects we are working on at work is Windows Hello for Business. While there are plenty of guides out there on how to implement, we've gotten some random questions regarding shared workstations, different models and their supported biometrics, and how we can have a successful rollout. The goal of this post is to provide some helpful scripts to start the journey for Windows Hello.</p>
<p>The solutions I am posting below are designed for Intune Remediation Scripts (using a Detection method), this can probably be achieved in other ways (Configuration Items/Baselines), but we're starting to shift away from that and focus on Intune more and more.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-types-of-biometrics-are-on-our-devices">What types of biometrics are on our devices?<a href="https://joeloveless.com/blog/gathering-data-for-windows-hello#what-types-of-biometrics-are-on-our-devices" class="hash-link" aria-label="Direct link to What types of biometrics are on our devices?" title="Direct link to What types of biometrics are on our devices?" translate="no">​</a></h2>
<p>With any large organization, you probably have multiple makes and models spanning multiple years (hopefully four or less years) and are wondering how users will be able to login with Windows Hello for Business?</p>
<p>Windows Hello supports three types of biometric sign in (Source: <a href="https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/#biometric-sign-in" target="_blank" rel="noopener noreferrer" class="">https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/#biometric-sign-in</a>)</p>
<ul>
<li class="">Facial Recognition</li>
<li class="">Fingerprint</li>
<li class="">Iris recognition<!-- -->
<ul>
<li class="">Note: I'm yet to see a device that can do this. I didn't even know it was a thing until writing this!</li>
</ul>
</li>
</ul>
<p>As we are starting to write knowledge base articles for our end users, we want to provide them with the correct instructions. If they don't have Facial and Fingerprint, we are most likely going to only provide instructions for one or the other.</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/RemediationScripts/Detection-WindowsHelloForBusiness-BiometricDevices.ps1" target="_blank" rel="noopener noreferrer" class="">Intune Detection Script - Biometrics</a></p>
<p>I've linked the script to detect the different biometrics above. This should be configured as a Detection Script in Microsoft Intune, with the default configurations. Once configured and the script has ran for enough time, you can add the <strong>Pre-remediation detection output</strong> column, and should see results similiar to below:</p>
<p><img decoding="async" loading="lazy" alt="Biometric data" src="https://joeloveless.com/assets/images/biometrics-1c7b002ee756bee97ae139bf60fdb028.png" width="656" height="768" class="img_ev3q">
<img decoding="async" loading="lazy" alt="Biometric Data Review" src="data:image/png;base64,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" width="530" height="97" class="img_ev3q"></p>
<p>The output can also be exported to a CSV file for further review.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-many-user-profiles-are-on-our-devices">How many user profiles are on our devices?<a href="https://joeloveless.com/blog/gathering-data-for-windows-hello#how-many-user-profiles-are-on-our-devices" class="hash-link" aria-label="Direct link to How many user profiles are on our devices?" title="Direct link to How many user profiles are on our devices?" translate="no">​</a></h2>
<p>Another gotcha to look out for is how many user profiles are on your workstations. Windows Hello for Business supports a maximum of ten enrollments on a single device. This is Face or Fingerprint.</p>
<p>Source: <a href="https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/faq#how-many-users-can-enroll-for-windows-hello-for-business-on-a-single-windows-device" target="_blank" rel="noopener noreferrer" class="">https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/faq#how-many-users-can-enroll-for-windows-hello-for-business-on-a-single-windows-device</a></p>
<p>The initial group we are exploring rolling this out for has a large footprint of shared devices, with multiple people rotating in and out daily and even hourly, so we either need to exclude those devices, or look at other options (Yubikey, etc)</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/RemediationScripts/Detection-WindowsHelloForBusiness-ProfileCount.ps1" target="_blank" rel="noopener noreferrer" class="">Intune Detection Script - Profile Count</a></p>
<p>The detection script for this is also above, with the $excludedProfiles variable allowing you to exclude any profile you want. In my case, I'm excluding Administrator, Guest, Public, Default, and defaultuser0. I'm also looking for sign-ins for the last 90 days, hoping to capture accurate stats and not just devices with really stale profiles (that probably need to be remediated at some point)</p>
<p>If the script finds more than 10 profiles, this will return non-compliant. Less than 10 profiles, the script will return compliant.</p>
<p>This should also be ran as a Detection Script only in Microsoft Intune with the default configurations.</p>]]></content:encoded>
            <category>Windows Hello</category>
            <category>Remediation Scripts</category>
            <category>Intune</category>
        </item>
        <item>
            <title><![CDATA[Finding Group Policy settings by category]]></title>
            <link>https://joeloveless.com/blog/search-gpocategory</link>
            <guid>https://joeloveless.com/blog/search-gpocategory</guid>
            <pubDate>Sat, 19 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A PowerShell script to search for GPO settings by category.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/finding-group-policy-settings-by-category-e6c1f8ee5958cdfc89b8d4d4837bf588.png" width="1200" height="630" class="img_ev3q"></p>
<p>Greetings everyone. I wanted to share some of our functions that we've been using on our migration path from Group Policy to Microsoft Intune. This is a new one I just wrote this week, and think it will be very helpful. In our environment, we have multiple forests (27+). We've been migrating our standard workstation policies, but with 27 forests, we're discovering we still have a lot of cleanup to do.</p>
<p>While the push is for pure Entra join, many organizations aren't ready to commit to that (not for lack of trying to convince management otherwise), and will be in the hybrid scenario for quite a while. I'm always quite interested when I go to MMS and see the amount of hands that are either co-managed with Configuration Manager, or using Hybrid Join with Active Directory. Despite the marketing push from Microsoft, and all the new technology going into Azure/Entra/Intune, I don't think Active Directory or Configuration Manager are going anywhere anytime soon.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="search-gpocategory">Search-GPOCategory<a href="https://joeloveless.com/blog/search-gpocategory#search-gpocategory" class="hash-link" aria-label="Direct link to Search-GPOCategory" title="Direct link to Search-GPOCategory" translate="no">​</a></h2>
<p>I'm not going to go into a step by step breakdown of the script like I have done in previous posts. I'm not sure how much value there is in that, but will give a slight breakdown of it here. If you have any questions on it, please feel free to reach out to me and I can answer any questions on it. The function can be found on my <a href="https://github.com/Pacers31Colts18/GroupPolicy/blob/master/Search-GPOCategory.ps1" target="_blank" rel="noopener noreferrer" class="">GitHub</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://joeloveless.com/blog/search-gpocategory#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h3>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">[CmdletBinding()]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Param(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $False)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [array]$Domains,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $True, HelpMessage = "Category to search for")]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [array]$Category,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [ValidateSet('All', 'Computer', 'User')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [string]$PolicyScope = 'All'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    )</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Parameter Logic</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($Domains.Count -eq 0) { $Domains = (Import-CSV -path $global:DomainsFile | Out-GridView -PassThru).Title }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<ul>
<li class="">$Domains<!-- -->
<ul>
<li class="">As with most of our functions, we're asking for an array (or single) domain. If you do not list a domain, we'll attempt to load a CSV file containing our domains. If using a CSV title, the header should contain Title for the list of domains.</li>
</ul>
</li>
<li class="">$Category<!-- -->
<ul>
<li class="">This is the category (or multiple categories) that you're searching for. In my example, I will use "Microsoft Edge". This is looking for anything <em>like</em> Microsoft Edge. That means it will pull Windows Components\Microsoft Edge, Microsoft Edge\Downloads, Microsoft Edge, etc.</li>
</ul>
</li>
<li class="">$PolicyScope<!-- -->
<ul>
<li class="">This allows you to narrow it down a little bit more, so you're able to search the Computer or User side of the policy, or just search all of the policy.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-returned">Data Returned<a href="https://joeloveless.com/blog/search-gpocategory#data-returned" class="hash-link" aria-label="Direct link to Data Returned" title="Direct link to Data Returned" translate="no">​</a></h3>
<p>The following will be returned in the CSV file:</p>
<ul>
<li class="">DomainName<!-- -->
<ul>
<li class="">The domain the policy is on.</li>
</ul>
</li>
<li class="">PolicyName<!-- -->
<ul>
<li class="">The name of the GPO.</li>
</ul>
</li>
<li class="">PolicyStatus<!-- -->
<ul>
<li class=""><img decoding="async" loading="lazy" alt="Policy Status" src="data:image/png;base64,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" width="374" height="93" class="img_ev3q"></li>
</ul>
</li>
<li class="">Category<!-- -->
<ul>
<li class="">The category found. Example: Windows Components/Microsoft Edge</li>
</ul>
</li>
<li class="">Area<!-- -->
<ul>
<li class="">Computer or User</li>
</ul>
</li>
<li class="">Setting Name<!-- -->
<ul>
<li class="">The name of the settings found</li>
</ul>
</li>
<li class="">Setting State<!-- -->
<ul>
<li class="">Enabled/Disabled/whichever else. Will not return Not Configured.</li>
</ul>
</li>
<li class="">Enabled OU Links<!-- -->
<ul>
<li class="">All the OU links for the GPO, separated by a ";"</li>
</ul>
</li>
<li class="">Security Filtering<!-- -->
<ul>
<li class="">The security filtering applied to the policy.</li>
</ul>
</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Example Data" src="https://joeloveless.com/assets/images/example_data-48831e3ba3ad653a9ec2a529aa0a4df6.png" width="1892" height="422" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://joeloveless.com/blog/search-gpocategory#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>A little bit of history from the org I work in (and previous orgs up to Step 5). If you work in Education or Government, you're probably all too familiar with this process.</p>
<ol>
<li class=""><strong>Centralization</strong>
<ul>
<li class="">Higher ups decide to centralize IT services.</li>
<li class="">All local teams to start using central enterprise IT. Education and Government just love doing this.</li>
</ul>
</li>
<li class=""><strong>Policies managed by other teams</strong>
<ul>
<li class="">Here is this random structure of GPOs and OUs, have at it, not our problem anymore.</li>
</ul>
</li>
<li class=""><strong>OU Migration (Workstations)</strong>
<ul>
<li class="">We then migrate all the workstations to an enterprise managed OU.</li>
<li class="">While trying not to break things too much, some legacy policies will move over also.</li>
<li class="">Process can take 3 months to 3 years (yay politics!)</li>
</ul>
</li>
<li class=""><strong>OU Migration (Users)</strong>
<ul>
<li class="">Haven't even touched these yet, legacy policies and structure still hangs out there.</li>
<li class=""><strong>Why does User policies always fall on the Endpoint teams? A question I've been pondering</strong></li>
</ul>
</li>
<li class=""><strong>Let's use Intune!</strong>
<ul>
<li class="">We're well underway migrating our core policies that we have applied in all forests to Microsoft Intune.</li>
<li class="">We've reviewed policies, determining if they still make sense to move.</li>
<li class="">Inconsistencies from 1-4<!-- -->
<ul>
<li class="">User policies<!-- -->
<ul>
<li class="">Some previous teams did more configuration on the user side rather than workstation side. Can see duplicate settings on the user side vs. Intune policy, or settings doing the opposite of what we want</li>
</ul>
</li>
<li class="">Legacy policies<!-- -->
<ul>
<li class="">Duplication</li>
<li class="">Frankstein policies</li>
</ul>
</li>
<li class="">One off policies<!-- -->
<ul>
<li class="">Some department needed x setting and is filtered down a bit. Not a high priority, but would rather see all of that Category managed from a central spot and not a mixture.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-next-step">The Next Step<a href="https://joeloveless.com/blog/search-gpocategory#the-next-step" class="hash-link" aria-label="Direct link to The Next Step" title="Direct link to The Next Step" translate="no">​</a></h2>
<p>Now that we have our core policies moved into Intune, and being managed under the great central pane of glass, we're looking into stopping the inconsistencies, and really level setting the policies. My goal is to look at our core policies, and break these out by category. We then have some functions in our module that can help with this process. A previous one we get great use out of is Search-GPOString, this allows us to search GPO objects in all our forests by a query and get the GPO objects back, along with their status, security filtering, and linked OUs. This is great for finding single settings.</p>
<p>This week, I came up with the idea to search by category. By category, I mean I am looking specifically for this:</p>
<p><img decoding="async" loading="lazy" alt="GPO Category" src="data:image/png;base64,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" width="466" height="350" class="img_ev3q"></p>
<p>From there, we are then returning the settings associated with that category, along with details on the policy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-do-we-do-with-that-data">What do we do with that data?<a href="https://joeloveless.com/blog/search-gpocategory#what-do-we-do-with-that-data" class="hash-link" aria-label="Direct link to What do we do with that data?" title="Direct link to What do we do with that data?" translate="no">​</a></h2>
<p>From there, we can really breakdown our policy structure even more, and make more determinations.</p>
<ul>
<li class="">Do these policies make sense in 2025?</li>
<li class="">Are these policies better (more secure) than what we currently have in place?</li>
<li class="">Do we want to simplify and do away with these policies?<!-- -->
<ul>
<li class="">What will be the impact if we do that?</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="piloting">Piloting<a href="https://joeloveless.com/blog/search-gpocategory#piloting" class="hash-link" aria-label="Direct link to Piloting" title="Direct link to Piloting" translate="no">​</a></h2>
<p>No matter what decision that is made, we're then going to pilot this out. Daniel Ratliff had a great blog post <a href="https://potentengineer.com/2025/07/02/managing-endpoint-policies-for-the-enterprise.html" target="_blank" rel="noopener noreferrer" class="">Managing Endpoint Policies for the Enterprise</a>, that serves as a great reminder. For us, we like to use the randomized process, but as we develop pilot groups more, might revisit the idea. See <a href="https://joeloveless.com/2025/04/entra-grouprandomization/" target="_blank" rel="noopener noreferrer" class="">here</a> for details on our randomization process if interested.</p>
<p>While we all want to move a little quicker, and get done with migrating policies, we will never truly be done. There will always be some other policy to create, tweak, or get rid of. There is no finish line to this, so take your time and try to fly under the radar as much as possible when doing policy migrations.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://joeloveless.com/blog/search-gpocategory#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>I'll be publishing some more Group Policy related scripts to help people on their migration from GPO to Intune. I'm curious to know what other people are doing in their environments, whether doing a complete overhaul, migrating to new OUs through attrition (new builds), a mixture, or totally scrapping AD and going Entra only.</p>]]></content:encoded>
            <category>Group Policy</category>
            <category>PowerShell</category>
            <category>Hybrid</category>
            <category>Intune</category>
        </item>
        <item>
            <title><![CDATA[Getting Started with Microsoft Graph]]></title>
            <link>https://joeloveless.com/blog/getting-started-with-microsoft-graph</link>
            <guid>https://joeloveless.com/blog/getting-started-with-microsoft-graph</guid>
            <pubDate>Sun, 13 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A guide to Microsoft Graph]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/getting-started-with-microsoft-graph-11b1f8c1be9c02c7cb6872ea6fc1d0f0.png" width="1200" height="630" class="img_ev3q"></p>
<p>Good morning everyone! I've been struggling lately with things to write, and also struggling with making time to write. I feel like this summer we've been slammed at work with different projects, I've been slammed at home with different projects, and have generally just been a little tired. I'm attempting to launch a consulting company, so I've been working through trying to get that off the ground (design a website first), so that has been eating into my time. While weed eating our wooded trail yesterday, I was thinking of different posts to make. One that I've been wanting to write about is my thoughts on Microsoft Graph and all the pain points I've experienced myself as a general noobie to Graph. I've been using Microsoft Graph the last three years in different capacities. This would be either through the SDK, native API calls, or using Invoke-MgGraphRequest.</p>
<p>Last week, we had an incident pop-up where our Apple VPP apps had their assignments removed when we put in the new VPP cert. So, I wrote a Powershell script that called the Graph API to retrieve the application assignments after we went through and re-assigned everything. Unfortunately, I did not have this data beforehand. But, for this post, I'm going to use the script I developed as an example for configuration. The main script can be found here <a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneApplicationAssignments.ps1" target="_blank" rel="noopener noreferrer" class="">Export-IntuneApplicationAssignments</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-terms-and-painpoints">Common Terms and Painpoints<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#common-terms-and-painpoints" class="hash-link" aria-label="Direct link to Common Terms and Painpoints" title="Direct link to Common Terms and Painpoints" translate="no">​</a></h2>
<p>Before starting, I want to list out some common terms and some common pain points I've ran into. A lot of this will be dependent on what your environment looks like and what you are doing with Microsoft Graph.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-terms">Common Terms<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#common-terms" class="hash-link" aria-label="Direct link to Common Terms" title="Direct link to Common Terms" translate="no">​</a></h3>
<p>To use the Microsoft Graph API, you need access. Typically, Microsoft Graph is not going to be wide open for you to just connect to and run commands against. Thinking to the Active Directory world, you would have permissions delegated to your privileged account (hopefully) and then you would perform actions. Instead, in the Azure world, you will grant permissions to an App Registration. Microsoft has provided all the permissions to configure, allowing you to get very granular and use the principal of least privilege.</p>
<p><a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview" target="_blank" rel="noopener noreferrer" class="">Permissions Consent Overview</a></p>
<ul>
<li class="">Types of Access<!-- -->
<ul>
<li class="">Delegated<!-- -->
<ul>
<li class="">Connecting to a resource on behalf of the signed in user.</li>
<li class="">Used for running scripts on demand with no automation tied to it.</li>
<li class="">I use this most commonly in my day to day work over Application permissions.</li>
</ul>
</li>
<li class="">Application<!-- -->
<ul>
<li class="">Connecting to a resource as itself.</li>
<li class="">User is not needed to authenticate</li>
<li class="">I use this with automation (Azure Runbooks, Github Actions, etc)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Going back to the Active Directory scenario. With Delegated, I'd equate that to me running a script with my privileged account whenever necessary. Where if I then wanted any sort of automation (Scheduled Task maybe?), I'd probably have a Service Account with the permissions needed to then run the task.</p>
<ul>
<li class="">Pagination<!-- -->
<ul>
<li class="">Because you're now working in this huge environment, that has multiple customers, we need to be more efficient. On some of the data requests, Microsoft "pages" the data. Meaning, you will only get back chunks of results at a time. If wanting to return ALL the data (because who doesn't), you then have to use pagination to keep going through the results.</li>
</ul>
</li>
</ul>
<p><a href="https://learn.microsoft.com/en-us/graph/paging?tabs=http" target="_blank" rel="noopener noreferrer" class="">Paging Microsoft Graph data in your app</a>
<a href="https://practical365.com/pagination-graph-sdk/" target="_blank" rel="noopener noreferrer" class="">Practical Graph: All About Pagination and Fetching Data</a>
<a href="https://medium.com/@mozzeph/how-to-handle-microsoft-graph-paging-in-powershell-354663d4b32a" target="_blank" rel="noopener noreferrer" class="">How To Handle Microsoft Graph Paging in PowerShell</a></p>
<ul>
<li class="">Beta vs. v1.0<!-- -->
<ul>
<li class="">While Beta is just that, Beta. I've yet to really see anything go from Beta to 1.0, where much of the functionality needed is in Beta. For that reason, I tend to default to Beta, and only fall back to v1.0 if I absolutely have to.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started---app-registration">Getting Started - App Registration<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#getting-started---app-registration" class="hash-link" aria-label="Direct link to Getting Started - App Registration" title="Direct link to Getting Started - App Registration" translate="no">​</a></h2>
<ol>
<li class="">Open Azure (portal.azure.com), and go to App Registrations</li>
<li class="">Click <em>New Registration</em></li>
<li class="">Fill in the details, making note of the Redirect URI<!-- -->
<ul>
<li class="">This will prompt the user for authentication using the Microsoft authentication.</li>
</ul>
</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Delegated App Registration" src="https://joeloveless.com/assets/images/appregistration-delegated-f3a75874f20c7ba48098ec29489ab502.png" width="914" height="558" class="img_ev3q"></p>
<ol start="4">
<li class="">Once created, click on API Permissions.<!-- -->
<ul>
<li class="">By default, the application will have User.Read permissions</li>
</ul>
</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Delegated API Permissions" src="https://joeloveless.com/assets/images/appregistration-delegated-apipermissions-001de611312a520f7e2f981fed1ecda7.png" width="1053" height="228" class="img_ev3q"></p>
<ol start="5">
<li class="">Click Add a Permission</li>
<li class="">Click Microsoft Graph</li>
<li class="">Choose Permissions</li>
</ol>
<ul>
<li class="">Application</li>
<li class="">Delegated (I'm choosing Delegated in this walk through)</li>
</ul>
<ol start="8">
<li class="">Scroll down to DeviceManagementApps, then choose <strong>DeviceManagementApps.Read.All</strong>
<ul>
<li class="">We're trying to use the principle of least privilege here, so we're only choosing Read permissions.</li>
</ul>
</li>
<li class="">Now that the permission is added, we still aren't done. Somebody has to sign off on the permissions being added, that's why the Status is "Not granted"</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Grant Admin Consent" src="https://joeloveless.com/assets/images/appregistration-permission1-162f9a0b617a1b7842dc07fdace25b46.png" width="1031" height="192" class="img_ev3q"></p>
<ol>
<li class="">We'll need someone with the proper permissions to Grant Admin Consent.<!-- -->
<ul>
<li class=""><a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent?pivots=portal" target="_blank" rel="noopener noreferrer" class="">Grant tenant-wide admin consent to an application</a></li>
</ul>
</li>
<li class="">Thankfully, this is my Lab tenant, and I have Global Admin.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Easy Peasy" src="https://joeloveless.com/assets/images/easypeasy-41a471393636026e3d1e9470460caa5c.gif" width="320" height="180" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Granted Consent" src="https://joeloveless.com/assets/images/appregistration-permission2-678ef7afd55fcc90f1696dd7caf0d627.png" width="1032" height="208" class="img_ev3q"></p>
<ol start="3">
<li class="">One last step, go to Authentication, and add <a href="http://localhost/" target="_blank" rel="noopener noreferrer" class="">http://localhost</a> into the Mobile and Desktop applications Redirect URIs section</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Authentication" src="https://joeloveless.com/assets/images/authentication-7b04821596855a4c63e5d03a7d2c7370.png" width="955" height="318" class="img_ev3q"></p>
<p>We've now configured our Delegated App Registration that will allow us to connect, we can then restrict who can access the application.</p>
<ol>
<li class="">Back in the Azure portal, go to Enterprise Applications.</li>
<li class="">Find your newly created application and go to Users and Groups.</li>
<li class="">Add any user who should have permissions here.</li>
</ol>
<p>So with this configuration we have:</p>
<p>User (with permissions) ---------&gt; Connects to Application ---------&gt; Application has permissions ------------&gt; Application makes requests to Microsoft Graph</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="thats-fantastic-but-how-do-i-know-what-permissions-i-need">That's fantastic, but how do I know what permissions I need?<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#thats-fantastic-but-how-do-i-know-what-permissions-i-need" class="hash-link" aria-label="Direct link to That's fantastic, but how do I know what permissions I need?" title="Direct link to That's fantastic, but how do I know what permissions I need?" translate="no">​</a></h4>
<p>Knowing what permissions you need can be challenging. Fortunately, there are many tools provided to do such a job. I'm not going to go into great detail about how to use each one of them, those can be other posts.</p>
<p><a href="https://developer.microsoft.com/en-us/graph/graph-explorer" target="_blank" rel="noopener noreferrer" class="">Graph Explorer</a>
<a href="https://graphxray.merill.net/" target="_blank" rel="noopener noreferrer" class="">Graph X-Ray</a>
<a href="https://desktop.postman.com/?desktopVersion=10.6.0" target="_blank" rel="noopener noreferrer" class="">PostMan</a>
<a href="https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta" target="_blank" rel="noopener noreferrer" class="">Graph API Documentation</a></p>
<p>Above are either tools from Microsoft or third party tools to get started. Personally, I like to just look at the network calls being made in Microsoft Edge to find out what the calls are, and then I can typically find the right permissions based off that. The Graph API documentation is also a great starting place, you just have to know how to navigate and know what you are looking for.</p>
<p>So in this example scenario, we're wanting to get application assignments.</p>
<ol>
<li class="">Go to <a href="https://intune.microsoft.com/" target="_blank" rel="noopener noreferrer" class="">Microsoft Intune</a></li>
<li class="">Click Apps</li>
<li class="">Using Edge, hit F12 and open Developer Tools.</li>
<li class="">Make sure the Network tab is selected.</li>
<li class="">In this case (probably a bad example to be honest) I'm going to create a new app with the Network tab open</li>
<li class="">Make the application available to All Users</li>
</ol>
<p>You should then start seeing a bunch of calls being made</p>
<p><img decoding="async" loading="lazy" alt="Developer Tools" src="https://joeloveless.com/assets/images/developertools-name-e4527c2f1d4d69499d0d90e7c89eed44.png" width="196" height="1089" class="img_ev3q"></p>
<p>Most of this looks like garbage to me, anything with .js is worthless to me.</p>
<p><img decoding="async" loading="lazy" alt="Developer Tools - Request URL" src="https://joeloveless.com/assets/images/developertools-requesturl-9df0831549d4f1cb665c755e11fd1c4f.png" width="338" height="245" class="img_ev3q"></p>
<p>But, I then see this mobileapps. This is my starting point for configuring the permissions (and when I go to write my PowerShell script)</p>
<p><a href="https://graph.microsoft.com/beta/**deviceAppManagement**/mobileApps/" target="_blank" rel="noopener noreferrer" class="">https://graph.microsoft.com/beta/**deviceAppManagement**/mobileApps/</a></p>
<ul>
<li class="">DeviceAppManagement (Intune) = DeviceManagementApps (Azure)<!-- -->
<ul>
<li class="">Microsoft is never consistent with naming!</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="authentication">Authentication<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#authentication" class="hash-link" aria-label="Direct link to Authentication" title="Direct link to Authentication" translate="no">​</a></h2>
<p>Okay, now we have the App Registration configured, how do we connect to it?</p>
<p>We have two scenarios for connection, native Rest API connection(Get-MSALToken), or connecting with Microsoft Graph.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="invoke-restmethodget-msaltoken">Invoke-RestMethod/Get-MSALToken<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#invoke-restmethodget-msaltoken" class="hash-link" aria-label="Direct link to Invoke-RestMethod/Get-MSALToken" title="Direct link to Invoke-RestMethod/Get-MSALToken" translate="no">​</a></h3>
<p>I find this to be the most difficult to connect to, but let's give it a shot and see if I can explain it and figure it out myself.</p>
<ol>
<li class="">Install Get-MSALToken</li>
</ol>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Install-Module -Name MSAL.PS -Scope CurrentUser</span><br></span></code></pre></div></div>
<p>Now that we have the module installed, we need to configure our parameters.</p>
<p>On the Overview page of your App Registration, you'll find the details needed. You can use either your domain name or actual tenant ID for the below portion. I'm using my tenant domain name.</p>
<p><img decoding="async" loading="lazy" alt="Overview" src="https://joeloveless.com/assets/images/overview-dacbbc750e620e890c8e71732216e1da.png" width="839" height="292" class="img_ev3q"></p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">$token = Get-MsalToken -clientid "CLIENTIDHERE" -tenantid "joeloveless.com" -Interactive</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">$header = @{'Authorization' = $token.createauthorizationHeader();'ConsistencyLevel' = 'eventual'}</span><br></span></code></pre></div></div>
<p>In this example (Delegated), I am looking to connect interactively. If I wanted this in an automated scenario, I'd be looking at using Client Secrets, Federation, or Certificates to pass the authentication.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="connect-mggraph">Connect-MgGraph<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#connect-mggraph" class="hash-link" aria-label="Direct link to Connect-MgGraph" title="Direct link to Connect-MgGraph" translate="no">​</a></h3>
<p>The next option (and simpler IMO) is to connect using <strong>Connect-MGGraph</strong></p>
<p><a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/connect-mggraph?view=graph-powershell-1.0" target="_blank" rel="noopener noreferrer" class="">Connect-MgGraph</a></p>
<p>We'll need the Microsoft.Graph.Authentication module.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Install-Module -Name Microsoft.graph.authentication -Scope CurrentUser</span><br></span></code></pre></div></div>
<p>From here, we just need to run a one liner to then connect:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">connect-MgGraph -ClientId "ClientIDHere" -TenantId "joeloveless.com"</span><br></span></code></pre></div></div>
<p>Neither are overly difficult to configure, but Connect-MgGraph is just a tad simpler. Personally, I go with Connect-MgGraph, based off this paragraph from the PowerShell Gallery</p>
<p><em>The MSAL.PS PowerShell module wraps MSAL.NET functionality into PowerShell-friendly cmdlets and is not supported by Microsoft. Microsoft support does not extend beyond the underlying MSAL.NET library. For any inquiries regarding the PowerShell module itself, you may contact the author on GitHub or PowerShell Gallery.</em></p>
<p>Microsoft doesn't officially support MSAL, where they do officially support the Graph SDK.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="option-1-scripting-natively-with-invoke-restmethod">Option 1: Scripting natively with Invoke-RestMethod<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#option-1-scripting-natively-with-invoke-restmethod" class="hash-link" aria-label="Direct link to Option 1: Scripting natively with Invoke-RestMethod" title="Direct link to Option 1: Scripting natively with Invoke-RestMethod" translate="no">​</a></h2>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneApplicationAssignments-Invoke-RestMethod.ps1" target="_blank" rel="noopener noreferrer" class="">Full Script Here</a></p>
<p>In this scenario, we've already connected using the Invoke-RestMethod/Get-MSALToken method from above, connecting to our delegated application and are ready to start writing PowerShell scripts.</p>
<p>I've added the full script <a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneApplicationAssignments%20-%20Invoke-RestMethod" target="_blank" rel="noopener noreferrer" class="">here</a>, but I want to highlight the key differences in each script. The guts of the script are the same, just a little different for each one.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Check Graph connection</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($null -eq ($header)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Error "Authentication needed. Please call connect to Microsoft Graph."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<p>Here we are checking for the $header not to be null, as that is the variable we're using from above to store the authorization.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Get application details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $uri = "https://graph.microsoft.com/$graphApiVersion/deviceAppManagement/mobileApps?`$expand=assignments"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $resultCheck = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    do {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Invoke-RestMethod -Uri $Uri -Method Get -Headers $Header</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $resultCheck += $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $uri = $response.'@odata.nextLink'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    } while ($uri)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<p>The key difference here is the $response variable. We need an extra parameter, Headers to achieve the desired results. The above code is what I use for the <strong>pagination</strong> scenario from the Common Terms section. I'm looking for repeatable code, and this is what I have found to work perfectly, without over complicating things.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">"#microsoft.graph.groupAssignmentTarget" {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $uri = "https://graph.microsoft.com/$graphApiVersion/groups/$($assignment.target.groupid)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $group = Invoke-RestMethod -Uri $Uri -Method Get -Headers $Header</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            Write-Error "Unable to get group details: $_"</span><br></span></code></pre></div></div>
<p>Similar to above</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Filter Details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $filterId = $assignment.target.deviceAndAppManagementAssignmentFilterId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $filterType = $assignment.target.deviceAndAppManagementAssignmentFilterType</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                if ($filterId -and $filterId.Length -eq 36) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $uri = "https://graph.microsoft.com/$graphApiVersion/devicemanagement/assignmentFilters/$filterId"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $filterResponse = Invoke-RestMethod -Uri $Uri -Method Get -Headers $Header</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $filterName = $filterResponse.displayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        Write-Warning "Failed to get filter details for ID $filterId"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                #endregion</span><br></span></code></pre></div></div>
<p>Again, pointing out the difference with the Headers variable.</p>
<p>#Option 2: Using MgGraph</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneApplicationAssignments%20-%20MgGraph.ps1" target="_blank" rel="noopener noreferrer" class="">Full Script Here</a></p>
<p>In this scenario, we've connected to Graph using <strong>Connect-MgGraph</strong>, using my preferred way of connecting.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"># Check Graph connection</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($null -eq (Get-MgContext)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Error "Authentication needed. Please call connect to Microsoft Graph."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<p>This is using Get-MgContext to do the connection check, running this will give you more information on how you are connected, what scopes you have, etc.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Get application details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $uri = "https://graph.microsoft.com/$graphApiVersion/deviceAppManagement/mobileApps?`$expand=assignments"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $resultCheck = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    do {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Invoke-MgGraphRequest -Method GET -Uri $uri</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $resultCheck += $response.value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $uri = $response.'@odata.nextLink'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    } while ($uri)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<p>One less parameter to worry about</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">"#microsoft.graph.groupAssignmentTarget" {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $uri = "https://graph.microsoft.com/$graphApiVersion/groups/$($assignment.target.groupid)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $group = Invoke-MgGraphRequest -Uri $uri -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            Write-Error "Unable to get group details: $_"</span><br></span></code></pre></div></div>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">if ($filterId -and $filterId.Length -eq 36) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $uri = "https://graph.microsoft.com/$graphApiVersion/devicemanagement/assignmentFilters/$filterId"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $filterResponse = Invoke-MgGraphRequest -Uri $uri -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $filterName = $filterResponse.displayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        Write-Warning "Failed to get filter details for ID $filterId"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="option-3-using-graph-powershell-cmdlets">Option 3: Using Graph PowerShell Cmdlets<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#option-3-using-graph-powershell-cmdlets" class="hash-link" aria-label="Direct link to Option 3: Using Graph PowerShell Cmdlets" title="Direct link to Option 3: Using Graph PowerShell Cmdlets" translate="no">​</a></h2>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneApplicationAssignments-GraphSDK.ps1" target="_blank" rel="noopener noreferrer" class="">Full Script Here</a></p>
<p>We had two options to connect to Graph, but now we have three options to run commands against Graph? What the hell?</p>
<p><img decoding="async" loading="lazy" alt="Huh?" src="https://joeloveless.com/assets/images/wedding-crashers-2044d882061b5564cf65484efab3d697.gif" width="460" height="199" class="img_ev3q"></p>
<p>Microsoft attempted to make this easier for us. In doing so, what they really did was create confusion. Many of the cmdlets are half baked, have really poor documentation, sometimes work, most of the time don't work, and are just down right confusing. I've struggled finding the right commands to use, and when I do find the right commands to use, nothing really seems to work just the way I expect it to. With the first two options, the code is very repeatable, where with this option, it takes a minute to find the right commands, then you have to hop on one foot and hope they work correctly.</p>
<p><a href="https://learn.microsoft.com/en-us/powershell/microsoftgraph/get-started?view=graph-powershell-1.0" target="_blank" rel="noopener noreferrer" class="">Get started with the Microsoft Graph PowerShell SDK</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="find-mggraphcommand">Find-MgGraphCommand<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#find-mggraphcommand" class="hash-link" aria-label="Direct link to Find-MgGraphCommand" title="Direct link to Find-MgGraphCommand" translate="no">​</a></h3>
<p>Where with the first two options, I could go to the API documentation and just find the URLs, I have to do a little bit more digging to find the right commands needed.</p>
<ol>
<li class="">Navigate to the Graph API documentation, in this case, we're looking for <a href="https://learn.microsoft.com/en-us/graph/api/resources/intune-app-conceptual?view=graph-rest-beta" target="_blank" rel="noopener noreferrer" class="">Corporate Management &gt; App Management</a>, which in turn points us to the URL of</li>
</ol>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">/deviceAppManagement/mobileApps</span><br></span></code></pre></div></div>
<ol start="2">
<li class="">Now that we have the URL, we can run Find-MgGraphCommand</li>
</ol>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Find-MgGraphCommand -Uri '/deviceAppManagement/mobileapps'</span><br></span></code></pre></div></div>
<p>This will then return us the available cmdlets to use.</p>
<p><img decoding="async" loading="lazy" alt="Find-MgGraphCommand Results" src="https://joeloveless.com/assets/images/find-mggraph-dfe4edf98745bc9c780733016d0c007b.png" width="2062" height="204" class="img_ev3q"></p>
<ol start="3">
<li class="">Going down even further, we can run the following:</li>
</ol>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Find-MgGraphCommand -Command Get-MgbetaDeviceAppManagementMobileApp</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Find-MgGraphCommand -Command" src="https://joeloveless.com/assets/images/find-mggraph2-fd5f0106f1a7cd378e023d6844486a1d.png" width="2070" height="137" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-script">Building the script<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#building-the-script" class="hash-link" aria-label="Direct link to Building the script" title="Direct link to Building the script" translate="no">​</a></h4>
<p>First thing I noticed, I have another module to install.</p>
<p>So to build this script, I'll need the following:</p>
<ul>
<li class="">Microsoft.Graph.Authentication</li>
<li class="">Microsoft.Graph.Beta.Devices.CorporateManagement</li>
</ul>
<p>This is just for this example script. If I had a script that was peicing together multiple parts of Intune, I'd have a whole lot of dependencies and hoping there aren't issues at any point. Where with the previous two, I'm relying on exactly one module to install.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Get application details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $resultCheck = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    do {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $response = Get-MgBetaDeviceAppManagementMobileApp -All -ExpandProperty Assignments </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $resultCheck += $response</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $uri = $response.'@odata.nextLink'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    } while ($uri)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<p>This is a little bit less code, but did I really save any time by having to research this and peice it all together?</p>
<p>Now I'm down to the Groups portion of the script.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">"#microsoft.graph.groupAssignmentTarget" {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $uri = "https://graph.microsoft.com/$graphApiVersion/groups/$($assignment.target.groupid)"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            $group = Invoke-MgGraphRequest -Uri $uri -Method GET</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                            Write-Error "Unable to get group details: $_"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        }</span><br></span></code></pre></div></div>
<p>Previously I had this in Option 2. Now I need to find the commands again.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Find-MgGraphCommand -Uri '/groups'</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Find-MgGraph Groups" src="https://joeloveless.com/assets/images/findmggraph-groups-21a6abb49e9d8a6eef3f58af25cead4d.png" width="1264" height="204" class="img_ev3q"></p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">Find-MgGraphCommand -Command get-mggroup</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Find-MgGraph Groups Command" src="data:image/png;base64,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" width="1244" height="97" class="img_ev3q"></p>
<p>Now I have another module to install, the Groups module. If you're following along, now I need three modules:</p>
<ul>
<li class="">Microsoft.Graph.Authentication</li>
<li class="">Microsoft.Graph.Beta.Devices.CorporateManagement</li>
<li class="">Microsoft.Graph.Beta.Groups</li>
</ul>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">if ($filterId -and $filterId.Length -eq 36) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $filterresponse = Get-MgBetaDeviceManagementAssignmentFilter -DeviceAndAppManagementAssignmentFilterId $filterId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        $filterName = $filterResponse.displayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                        Write-Warning "Failed to get filter details for ID $filterId"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span></code></pre></div></div>
<p>This one didn't take another module to be installed.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="comparing-the-data">Comparing the Data<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#comparing-the-data" class="hash-link" aria-label="Direct link to Comparing the Data" title="Direct link to Comparing the Data" translate="no">​</a></h3>
<p>Now that I have the script built, lets compare the results:</p>
<p>Option 1 and 2 CSV Results:</p>
<p><img decoding="async" loading="lazy" alt="CSV Results - Rest Method and Graph" src="https://joeloveless.com/assets/images/results1-ed818d6be92013b35129413fd85a0b53.png" width="1539" height="178" class="img_ev3q"></p>
<p>Cool! Look at all that data it returned. This is my test lab, so I'm expecting some blank results as I don't have a VPP account actually setup.</p>
<p><img decoding="async" loading="lazy" alt="CSV Results - Cmdlets" src="https://joeloveless.com/assets/images/results2-b37c8a674b561602f4bcba0011a179e8.png" width="1305" height="167" class="img_ev3q"></p>
<p>Where's Platform? This is just an example of the inconsistency that I talked about earlier. For whatever reason, Platform results aren't returned in this example. This was a totally random script I built last week for work and one I thought would be a good use case to show. I literally had no idea that the data would be inconsistent until I built this while writing the article. What are the odds?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://joeloveless.com/blog/getting-started-with-microsoft-graph#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p><strong>Hopefully</strong> this was a good explainer on the different options for getting started with Microsoft Graph. Going forward, I hope this explained the differences, and why I prefer using Invoke-MgGraphRequest over the others.</p>
<p>In my next post, I want to look at using some automation and create an Application App Registration vs. Delegated, and see how we can pump this data elsewhere (PowerBI) and take a look at that. Have a great day</p>]]></content:encoded>
            <category>PowerShell</category>
            <category>Graph API</category>
        </item>
        <item>
            <title><![CDATA[Tech tools I use at home and at work]]></title>
            <link>https://joeloveless.com/blog/tech-tools-I-use</link>
            <guid>https://joeloveless.com/blog/tech-tools-I-use</guid>
            <pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[An overview of the random tech I use at home and at work.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/tech-tools-i-use-at-home-and-work-d28dbbea0f1422df9033adc823a606bf.png" width="1200" height="630" class="img_ev3q"></p>
<p>I don't have a whole lot to write about this week, summer is in full swing, which means I'm pretty swamped with the kids and doing different activities. Right now I'm studying (again) for the AZ-104 test, blogging, and being slammed at work. My motivation hasn't fully been there since the RTO announcement, but the tech motivation is always there. Just a lot of anxiousness on what the future holds, which the HR department seems to change minute by minute while the agency has promoted telework as the future for the past five years.</p>
<p>But, I wanted to post something a little bit different. This post is a rundown of the tech I use both at home and at work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="home-tech">Home Tech<a href="https://joeloveless.com/blog/tech-tools-I-use#home-tech" class="hash-link" aria-label="Direct link to Home Tech" title="Direct link to Home Tech" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hardware">Hardware<a href="https://joeloveless.com/blog/tech-tools-I-use#hardware" class="hash-link" aria-label="Direct link to Hardware" title="Direct link to Hardware" translate="no">​</a></h3>
<ul>
<li class="">Intel Nuc (8th generation?)<!-- -->
<ul>
<li class="">I use this running ProxMox to run my Home Assistant and Plex VMs. Probably a little overkill for the use case. My Plex setup is as basic as you can get, with an 8 TB external HD plugged into it.</li>
</ul>
</li>
<li class="">Dell Optiplex 7070 SFF<!-- -->
<ul>
<li class="">I bought this used off eBay. I was supposed to get a Mini Tower, but the seller sent me a SFF. My dream of beefing it up with multiple hard drives died there. Instead, PayPal refunded me and I have a free SFF PC. I use this for my home lab stuff, blogging, etc.</li>
</ul>
</li>
<li class="">Lenovo Thinkpad Yoga 370<!-- -->
<ul>
<li class="">I recently discovered how much I love the tablet mode, never used that before. I've been using this for note taking for AZ-104 using OneNote. Makes me want to buy an actual tablet. Mainly I have this on my desk with Discord open during the day, and maybe random podcasts playing.</li>
</ul>
</li>
<li class="">Google Pixel 6<!-- -->
<ul>
<li class="">My phone, I recently put GrapheneOS on this instead of stock Android. I don't have any complaints about it, I don't see random news articles anymore, and I've basically de-googled at this point which is nice. The ability to still download apps from the Play Store is there, with everything being sandboxed.</li>
</ul>
</li>
<li class="">Eero Mesh<!-- -->
<ul>
<li class="">I use the Eero Mesh for my wifi in my office, basement, and main floors. Would I buy it again? Probably not, I'm not a fan of how locked down some of it is. Fiber is supposed to come to my house in the fall, so I might look at upgrading then to something else. Maybe I'll go the Ubiquiti route.</li>
</ul>
</li>
<li class="">Starlink<!-- -->
<ul>
<li class="">No options really, either Starlink or some crappy DSL provider. I'm looking forward to the fiber option and can't wait to cancel. It does well for the most part, heavy rain interferes with it (not really snow though). But it's $120 bucks a month, and Elmo seems like a douche.</li>
</ul>
</li>
<li class="">PS5<!-- -->
<ul>
<li class="">I have a PS5 for gaming, mainly for my kids, but I'm a big kid at heart. Since getting it last year, I've played the God of War games, and now am hooked on Call of Duty. I'll probably get a new game here soon. Gamertag: Pacers31Colts18.</li>
</ul>
</li>
<li class="">Zwift Hub<!-- -->
<ul>
<li class="">I bought this for indoor cycling in the winter. Turns out indoor cycling is super f'n boring. I don't use it all that much.</li>
</ul>
</li>
<li class="">Google Chromecast TV<!-- -->
<ul>
<li class="">I use Google TVs for streaming purposes. I wish the storage was a little bit bigger, but I also don't run a lot of apps so it isn't too bad. No complaints.</li>
</ul>
</li>
<li class="">Google Hubs<!-- -->
<ul>
<li class="">I have two of these, honestly, I barely use them anymore. I find them to get dumber and dumber as the days go on and have basically given up trying.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="software">Software<a href="https://joeloveless.com/blog/tech-tools-I-use#software" class="hash-link" aria-label="Direct link to Software" title="Direct link to Software" translate="no">​</a></h3>
<ul>
<li class=""><a href="https://plex.tv/" target="_blank" rel="noopener noreferrer" class="">Plex</a>
<ul>
<li class="">I use Plex to stream my home media. My kids (14 and 12) have a specific niche of Star Wars, Marvel, Avatar, Monsterverse, Jurassic Park, any anything else that involves giant monsters or cool superheroes. That is mainly what I watch too.</li>
</ul>
</li>
<li class=""><a href="https://sonarr.tv/" target="_blank" rel="noopener noreferrer" class="">Sonarr</a>
<ul>
<li class="">Sonarr for grabbing TV shows I already own.</li>
</ul>
</li>
<li class=""><a href="https://radarr.video/" target="_blank" rel="noopener noreferrer" class="">Radarr</a>
<ul>
<li class="">Radarr for grabbing movies I already own.</li>
</ul>
</li>
<li class=""><a href="https://www.bazarr.media/" target="_blank" rel="noopener noreferrer" class="">Bazarr</a>
<ul>
<li class="">Bazarr for grabbing subtitles for said movies and tv shows I already own.</li>
</ul>
</li>
<li class=""><a href="https://nzbget.net/" target="_blank" rel="noopener noreferrer" class="">NZBGet</a>
<ul>
<li class="">Sonarr/Radarr tell NZBGet what to grab.</li>
</ul>
</li>
<li class=""><a href="https://prowlarr.com/" target="_blank" rel="noopener noreferrer" class="">Prowlarr</a>
<ul>
<li class="">Prowlarr sync's everything together in one spot so I don't have to.</li>
</ul>
</li>
<li class=""><a href="https://www.home-assistant.io/" target="_blank" rel="noopener noreferrer" class="">Home Assistant</a>
<ul>
<li class="">Home Assistant is the home automation platform. My setup is super basic. I have some smart bulbs, smart switches, the hubs, and a propane tank sensor. I've always dreamed of having more automation, but I don't use it all that much. I also use it for watching HD space on my external HD for Plex.</li>
</ul>
</li>
<li class="">Hyper-V<!-- -->
<ul>
<li class="">I use Hyper-V to run my home lab, off the Dell Optiplex 7070. This is connected to my Azure tenant that I use for testing stuff out.</li>
</ul>
</li>
<li class="">OneNote<!-- -->
<ul>
<li class="">OneNote for random note taking</li>
</ul>
</li>
<li class=""><a href="https://bitwarden.com/" target="_blank" rel="noopener noreferrer" class="">BitWarden</a>
<ul>
<li class="">BitWarden is my password vault. I recommend it, $10 bucks a year. Does have random autofill issues on Android it seems.</li>
</ul>
</li>
<li class=""><a href="https://github.com/InfinityLoop1308/PipePipe/releases" target="_blank" rel="noopener noreferrer" class="">PipePipe</a>
<ul>
<li class="">Open source alternative to Youtube. Fork of NewPipe. I use this over YouTube in my attempt to DeGoogle as much as possible.</li>
</ul>
</li>
<li class="">VSCode<!-- -->
<ul>
<li class="">I use VSCode to either write scripts, or write blog posts in Markdown format.</li>
</ul>
</li>
<li class="">GitHub<!-- -->
<ul>
<li class="">My site is tied to GitHub pages with branching and source control.</li>
</ul>
</li>
<li class="">Squarespace<!-- -->
<ul>
<li class="">I purchased my domain name on Google Domains initially, then that got sold off to Squarespace. Seems simple enough.</li>
</ul>
</li>
<li class="">Hugo<!-- -->
<ul>
<li class="">The platform I use for blogging. I mainly followed the guide from <a href="https://mikefrobbins.com/2023/10/26/building-and-deploying-a-blog-with-hugo-and-github-pages/" target="_blank" rel="noopener noreferrer" class="">Mike Robbins</a> to get started.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="work-tech">Work Tech<a href="https://joeloveless.com/blog/tech-tools-I-use#work-tech" class="hash-link" aria-label="Direct link to Work Tech" title="Direct link to Work Tech" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hardware-1">Hardware<a href="https://joeloveless.com/blog/tech-tools-I-use#hardware-1" class="hash-link" aria-label="Direct link to Hardware" title="Direct link to Hardware" translate="no">​</a></h3>
<ul>
<li class="">Dell Latitude 5440<!-- -->
<ul>
<li class="">Run of the mill Dell Latitude laptop running a 13th gen i5 with 32 GB ram.</li>
</ul>
</li>
<li class="">Some Herman Miller chair<!-- -->
<ul>
<li class="">I have back problems, decent enough chair, I'm also not going to go out and buy one, so work let me use this at home.</li>
</ul>
</li>
<li class="">Custom made standing desk<!-- -->
<ul>
<li class="">I had some leftover butcher block, and ordered a standing desk kit off Amazon and put the butcher block onto it. My goal was to automate it into Home Assistant, but I never got there. One day.</li>
</ul>
</li>
<li class="">3 monitors<!-- -->
<ul>
<li class="">1 32 inch monitor</li>
<li class="">2 27 inch monitors<!-- -->
<ul>
<li class="">1 vertical</li>
<li class="">1 horizontal</li>
</ul>
</li>
</ul>
</li>
<li class="">Blue Yeti Microphone<!-- -->
<ul>
<li class="">Given to me by Fabian Rodriguez at TCSMUG.</li>
</ul>
</li>
<li class="">MMS Playing Cards<!-- -->
<ul>
<li class="">When in meetings I need something to keep me occupied while talking. So I shuffle a deck of playing cards I got at MMS 2024. Other random toys I have are a koosh ball and a fidget spinner.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="software-1">Software<a href="https://joeloveless.com/blog/tech-tools-I-use#software-1" class="hash-link" aria-label="Direct link to Software" title="Direct link to Software" translate="no">​</a></h3>
<ul>
<li class="">Microsoft Office<!-- -->
<ul>
<li class="">Typical Microsoft shop, running Office and Teams.</li>
</ul>
</li>
<li class="">Azure Virtual Desktop<!-- -->
<ul>
<li class="">AVD multi-session host for nearly all my administrative work.</li>
</ul>
</li>
<li class="">ADUC/GPMC (Active Directory and Group Policy)<!-- -->
<ul>
<li class="">ADUC and GPMC inside the AVD host to be able to manage all the domains we manage.</li>
</ul>
</li>
<li class="">Configuration Manager<!-- -->
<ul>
<li class="">Where and how I really got going in tech, still love it to this day and will die on the ConfigMgr hill.</li>
</ul>
</li>
<li class="">VSCode<!-- -->
<ul>
<li class="">I use VSCode extensively for writing PowerShell scripts.</li>
</ul>
</li>
<li class="">GitHub Enterprise<!-- -->
<ul>
<li class="">Where our team stores all our repos.</li>
</ul>
</li>
<li class="">VSCode Extensions<!-- -->
<ul>
<li class="">I run a pretty vanilla setup, we migrated from Horizon Virtual Desktops to AVD, and I'm trying not to run as many extensions this time around.<!-- -->
<ul>
<li class="">PowerShell<!-- -->
<ul>
<li class="">All my coding is done in PowerShell.</li>
</ul>
</li>
<li class="">Prettier - Code Formatter<!-- -->
<ul>
<li class="">I like how this will format the file for me.</li>
</ul>
</li>
<li class="">GitHub Actions<!-- -->
<ul>
<li class="">For managing everything we have tied to GitHub Actions.</li>
</ul>
</li>
<li class="">GitHub Pull Requests<!-- -->
<ul>
<li class="">So much easier than knowing the proper Git syntax and workflow. 10/10.</li>
</ul>
</li>
<li class="">Azure Automation<!-- -->
<ul>
<li class="">Used for managing the runbooks in Azure, with proper source control to GitHub tied to it.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>]]></content:encoded>
            <category>Other</category>
        </item>
        <item>
            <title><![CDATA[Examining CIS Microsoft Windows 11 Enterprise Benchmark in a Hybrid Environment]]></title>
            <link>https://joeloveless.com/blog/cis-benchmarks-enterprise-win11</link>
            <guid>https://joeloveless.com/blog/cis-benchmarks-enterprise-win11</guid>
            <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Review of CIS Microsoft Windows 11 Enterprise Benchmark in a hybrid environment]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/examining-cis-microsoft-windows11-enterprise-benchmark-in-a-hybrid-environment-541992cfff97a8caa73b22373d58e3a0.png" width="1200" height="630" class="img_ev3q"></p>
<p>It's been a few weeks now since I've made a post. The nice weather has kept me busy with yard work, taking my son camping, working on my garden, and trying to keep the weeds at bay.</p>
<p>Last weekend I took my son camping at Beaver Creek Valley State Park in Minnesota and had a blast with him. Beaver Creek is a fairly small campground, with an odd one road layout. For me, that's awesome. I don't want to be around too many people, and the park itself was amazing. The Hiking Club Trail was 6.2 miles long, and split up into two legs. The first leg, was really hilly, and I ended up slipping twice and landing on my butt on the trek down. The second leg, was fairly flat, but in this awesome valley with really great scenery. We did the first leg on Friday afternoon after arriving to our site, and then did the second leg in the morning. From there we did some fishing in Brownsville along the Mississippi River. I'm now 40 years old, and am yet to catch a fish that doesn't involve deep sea fishing, or trout fishing. Granted, I'm not a fisherman, and I don't do it all that often, but you would think I'd be able to catch one once? Nope, not me. Jay has though, which really doesn't make me feel any better, but good for him. Saturday afternoon we did some more hiking at Great River Bluffs State Park, which was around 2 miles on the hike. We're now over 40 miles on the hiking club trails, well on our way to getting our 50 mile badge.</p>
<p><img decoding="async" loading="lazy" alt="Beaver Creek" src="https://joeloveless.com/assets/images/beavercreek-606d49f7d58191595a82e7443dfef249.jpg" width="1708" height="1286" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Great River Bluffs" src="https://joeloveless.com/assets/images/greatriverbluffs-02676666cb19449fe7889ffe26b5e456.jpg" width="1708" height="1286" class="img_ev3q"></p>
<p>My garden is also coming along. Last year, I didn't plant anything. My first year here, it was trying to get it all ready, but weeds took over the area and won the battle. Weeds took over the area even more last year. My plan this year was to go back to raised beds. A trip to Menards put that all in shock when I found the price of lumber for a 1x8x8 was around 15 dollars for the crappiest piece of pine you could buy. I thought about giving up again, but then realized the barn we have had a bunch of old lumber still in it. I've now built five raised beds using that lumber. To date, I've planted:</p>
<ul>
<li class="">Bed 1:<!-- -->
<ul>
<li class="">Green Beans</li>
</ul>
</li>
<li class="">Bed 2:<!-- -->
<ul>
<li class="">Tomatoes</li>
<li class="">Marigolds</li>
</ul>
</li>
<li class="">Bed 3<!-- -->
<ul>
<li class="">Peppers</li>
<li class="">Lettuce</li>
</ul>
</li>
<li class="">Bed 4<!-- -->
<ul>
<li class="">Corn</li>
</ul>
</li>
<li class="">Bed 5<!-- -->
<ul>
<li class="">Need more compost, might plant pumpkins.</li>
</ul>
</li>
</ul>
<p>Finally, my Pacers are in the NBA Finals. It's been 25 years since the last trip, which put me in I think my freshman year of high school. My brother was going into his senior year. We have good memories of sitting in the parking lot across the street from the Fieldhouse in Indianapolis and watching the Finals then. His kids are now going into their senior year of high school, which makes us feel really f'n old.</p>
<p>Since I've been out of the groove a little bit, I've had a little bit of a writers block about what to write about. My last post was about using the CIS Benchmarks with AVD Multi-Session hosts. I wanted to expand on the topic some more, and touch on configuring the CIS Microsoft Windows 11 Enterprise Benchmark using Microsoft Intune, what settings aren't supported, what settings are supported, how to configure, and any gotcha's that we have experienced.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-cis">What is CIS?<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#what-is-cis" class="hash-link" aria-label="Direct link to What is CIS?" title="Direct link to What is CIS?" translate="no">​</a></h2>
<p>Simply put, CIS Benchmarks are a baseline of settings that will give you a secure environment. They are a <strong>guidance, and do not need to be configured 100% (despite what your security team says)</strong>. Striving for 100% should be the goal, but honestly, isn't achievable in any organization I've been in. For more information on CIS, you can go to <a href="https://cissecurity.org/" target="_blank" rel="noopener noreferrer" class="">https://cissecurity.org</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-the-difference-between-the-enterprise-and-the-intune-benchmark">What's the difference between the Enterprise and the Intune benchmark?<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#whats-the-difference-between-the-enterprise-and-the-intune-benchmark" class="hash-link" aria-label="Direct link to What's the difference between the Enterprise and the Intune benchmark?" title="Direct link to What's the difference between the Enterprise and the Intune benchmark?" translate="no">​</a></h2>
<p>From CIS:</p>
<ul>
<li class="">Intune Benchmark:<!-- -->
<ul>
<li class="">The Microsoft Intune Windows Benchmarks are written for MDM-joined systems using Microsoft Intune device configuration profiles only. This benchmark is not intended for use on standalone or workgroup systems, systems joined to and receive policies from Active Directory, or systems created, maintained, or used in other Cloud offerings. This benchmark covers supported endpoint states for Entra Hybrid Joined and Entra Joined systems that receive policies from Microsoft Intune using Configuration Profiles only.</li>
</ul>
</li>
<li class="">Enterprise Benchmark:<!-- -->
<ul>
<li class="">The Microsoft Windows Benchmarks are written for Active Directory domain-joined systems using Active Directory’s Group Policy Manager only. This benchmark is not intended for use on standalone or workgroup systems, systems joined to a cloud offering, such as Entra ID, or systems created, maintained, or used in the Cloud. This benchmark covers supported endpoint states for Active Directory and Entra Hybrid joined systems that receive policies from Active Directory Group Policy Manager only.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="but-what-if-our-org-is-hybrid-and-has-regulations-to-adhere-to">But what if our org is hybrid and has regulations to adhere to?<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#but-what-if-our-org-is-hybrid-and-has-regulations-to-adhere-to" class="hash-link" aria-label="Direct link to But what if our org is hybrid and has regulations to adhere to?" title="Direct link to But what if our org is hybrid and has regulations to adhere to?" translate="no">​</a></h3>
<p>Too bad? Microsoft is pushing us to be modern managed, using Intune, and starting that journey by hybrid-joining your existing devices. Cool. Great. But what if I want to move away from Group Policy and use Intune configuration? You can do that. As you see, both benchmarks cover hybrid-join scenarios. The difference being is the amount of settings that are in each benchmark.</p>
<ul>
<li class="">If you have regulations to adhere to, you're probably going to be stuck with the Enterprise benchmark.<!-- -->
<ul>
<li class="">The decision should be made, use GPO even if you're hybrid joined?<!-- -->
<ul>
<li class="">That seems like a bad idea to me, as you want to avoid the conflicting configurations. Pick one or the other. You've already enrolled these in Intune, I would keep using Intune.</li>
</ul>
</li>
</ul>
</li>
<li class="">If you don't have regulations to adhere to? Lucky you!<!-- -->
<ul>
<li class="">Use the Intune benchmark.<!-- -->
<ul>
<li class="">CIS does the hard work for you, the .json files are already there.</li>
<li class="">WARNINGS:<!-- -->
<ul>
<li class="">Don't just import the settings and apply to All Devices.</li>
<li class="">REVIEW THE SETTINGS, GET AN UNDERSTANDING OF THE POLICIES, AND GRADUALLY ROLL OUT THE POLICIES.</li>
<li class="">Push back on your security team if they say you need to implement all of these 100%. They're checking boxes, you have to deal with the support.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="downloading-the-benchmark">Downloading the Benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#downloading-the-benchmark" class="hash-link" aria-label="Direct link to Downloading the Benchmark" title="Direct link to Downloading the Benchmark" translate="no">​</a></h2>
<p>To download the kit to implement the benchmark, you need to have an account with CIS. That can be achieved by going to <a href="https://workbench.cisecurity.org/" target="_blank" rel="noopener noreferrer" class="">https://workbench.cisecurity.org/</a>. I don't know the full ins and outs of the cost, as my organizations have always had the means one way or another.</p>
<p>To start, we're actually going to download the CIS Microsoft Intune for Windows 11 Benchmark v4.0.0. Why this over the enterprise flavor? We're going to use this as the starting point for our configuration, to see what works, and what doesn't work right out of the gate. The Enterprise baseline is group policy based, so that only has group policy templates. The Intune baseline has the proper .json files for importing into Intune.</p>
<ul>
<li class="">Once registered and signed-in, click on Benchmarks</li>
<li class="">Search for <strong>CIS Microsoft Intune for Windows 11 Benchmark</strong></li>
<li class="">Click <strong>Files</strong> in the top right corner</li>
<li class="">Click <strong>CIS Microsoft Intune for Windows 11 Benchmark v4.0.0 - Build Kit</strong></li>
<li class="">We're then going to download the .zip file</li>
<li class="">Unzip the file</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="importing-the-benchmark">Importing the Benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#importing-the-benchmark" class="hash-link" aria-label="Direct link to Importing the Benchmark" title="Direct link to Importing the Benchmark" translate="no">​</a></h2>
<p>Now that we have the benchmark downloaded and unzipped, we can then import into Intune.</p>
<p><img decoding="async" loading="lazy" alt="Importing in Intune" src="https://joeloveless.com/assets/images/intune_import-2add3c560ffa2484f927fa50eae9f720.png" width="455" height="273" class="img_ev3q"></p>
<p>The following is the .json files that are available to import, which makes this so much easier.</p>
<p><img decoding="async" loading="lazy" alt="CIS Json Files" src="https://joeloveless.com/assets/images/cis_json-a2779bd088f677931ba0dce7fa734d92.png" width="473" height="382" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="importing-and-configuring-the-baseline">Importing and Configuring the Baseline<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#importing-and-configuring-the-baseline" class="hash-link" aria-label="Direct link to Importing and Configuring the Baseline" title="Direct link to Importing and Configuring the Baseline" translate="no">​</a></h2>
<p>Cool, now that we have them imported, we just select All Devices and away we go right? No. Don't do this. I can't stress that enough.</p>
<ul>
<li class="">Build a test VM or test device if you have one handy.</li>
</ul>
<p>I won't walk you through that, as that's different for each org. Too many options to choose from. As Microsoft would say, do what fits your org.</p>
<p>I learned upon first import, the Windows LAPS configuration is missing. Looking at the folder structure under the L1 folder, I see these Settings Catalog profiles, and then a script to disable Services.</p>
<p><img decoding="async" loading="lazy" alt="Imported Profiles" src="https://joeloveless.com/assets/images/imported_profiles-15628e8e0462d5411c4284b5051e762c.png" width="759" height="448" class="img_ev3q">
<img decoding="async" loading="lazy" alt="Platform Scripts" src="data:image/png;base64,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" width="608" height="276" class="img_ev3q"></p>
<p>After reviewing the readme file, I noticed that the LAPS profile has to be configured manually. Importing profiles with the endpoint security blade is a tricky item in Intune, and really a pain.</p>
<p>From the ReadMe file:</p>
<p>For 100% benchmark compliance, perform the following steps:</p>
<ol>
<li class="">Import desired profile from ".\Settings Catalog" folder into Intune ex: “CIS (L1)
Defender - Windows 11 Intune 3.0.0.json”.</li>
<li class="">Run scripts in ".\System Service Scripts" folders either locally, via Intune script
manager or your preferred method.</li>
<li class="">Configure LAPS settings in the "Endpoint security &gt; Account protection" section
in Intune admin center as outlined in the benchmark.</li>
</ol>
<p>What I haven't done at this point is assign the baseline to my group and test VM. What I want to do first is scan the system with a fresh build, and see what the configuration looks like out of the gate. Now, this will be different in every organization, as you might have some GPOs applying, settings applying during the build process, etc that will change the score slightly. Ideally, you'll have a pretty crappy score. If not, that means you have configurations in place already. As much as possible, you'll want to transition those policies out of wherever they are, and into Intune so you can manage the policies from a central location.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cis-cat-assessor">CIS-CAT Assessor<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#cis-cat-assessor" class="hash-link" aria-label="Direct link to CIS-CAT Assessor" title="Direct link to CIS-CAT Assessor" translate="no">​</a></h2>
<p>To review what is being applied, CIS also has a tool called CIS-CAT Assessor. This can be downloaded at this site, <a href="https://workbench.cisecurity.org/download/cis-cat/assessor" target="_blank" rel="noopener noreferrer" class="">https://workbench.cisecurity.org/download/cis-cat/assessor</a>. You will also need a license file, which will go in the license folder of the extracted zip file. Download all of this on your freshly built VM, as we're only going to run this locally and not over a network.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fresh-build">Fresh Build<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#fresh-build" class="hash-link" aria-label="Direct link to Fresh Build" title="Direct link to Fresh Build" translate="no">​</a></h2>
<p>When I run the CIS-CAT Assessor, I am going to choose both the CIS Microsoft Intune for Windows 11 Benchmark and the CIS Microsoft Windows 11 Enterprise Benchmark for assessment. Typically, I choose output to an HTML file also. This is just easier for my eyes to read, to each their own.</p>
<p><img decoding="async" loading="lazy" alt="CIS-CAT Assessor" src="https://joeloveless.com/assets/images/cis_assessor-8e175578be70c34bf1b3814d82c9c8f7.png" width="1201" height="778" class="img_ev3q"></p>
<p>Out of the gate, with no policies applied, I achieved the following results:</p>
<ul>
<li class="">CIS Microsoft Intune for Windows 11 Benchmark<!-- -->
<ul>
<li class="">337 settings</li>
<li class="">Pass: 57 settings</li>
<li class="">Fail: 273 settings</li>
<li class="">Score: 17%</li>
</ul>
</li>
<li class="">CIS Microsoft Windows 11 Enterprise Benchmark<!-- -->
<ul>
<li class="">406 settings</li>
<li class="">Pass: 86 settings</li>
<li class="">Fail: 318 settings</li>
<li class="">Score: 21%</li>
</ul>
</li>
</ul>
<p>What this tells me is that some of the settings defined in the benchmarks are default settings that Microsoft sets. I've added CSV files of the scan results, so you'll be able to see what settings are already configured with a freshly imaged device. A debate could be had on whether you need to configure these or not, as it would take someone with administrative rights to change the policies anyways. Files can be found here: <a href="https://github.com/Pacers31Colts18/Intune/tree/main/CIS%20v4.0" target="_blank" rel="noopener noreferrer" class="">https://github.com/Pacers31Colts18/Intune/tree/main/CIS%20v4.0</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="applying-imported-cis-microsoft-intune-for-windows-11-benchmark">Applying Imported CIS Microsoft Intune for Windows 11 Benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#applying-imported-cis-microsoft-intune-for-windows-11-benchmark" class="hash-link" aria-label="Direct link to Applying Imported CIS Microsoft Intune for Windows 11 Benchmark" title="Direct link to Applying Imported CIS Microsoft Intune for Windows 11 Benchmark" translate="no">​</a></h2>
<p>Now that we have the initial baseline established, we are ready to assign the policies we imported earlier into Intune. Typically when I am piloting, I will create a group, exclude from the previous policies, and apply to the new policies. Because this is in my lab, I don't really have any policies. If you do have policies, you'll want to have this configured before you run the scores on the fresh build, or you will have mixed results of policies already applying.</p>
<p>After some time, the policies should be applying, you can tell if the policy has applied, simply by rebooting the device. You should see this on the logon screen.</p>
<p><img decoding="async" loading="lazy" alt="Initial Policy" src="https://joeloveless.com/assets/images/cis_initialpolicy-dd75b371e262d14833a7959fbd2a1ddb.png" width="1358" height="817" class="img_ev3q"></p>
<p>Remember how I said don't just apply this without any thought? This is what happens.</p>
<p>Attempting to run the CIS-CAT assessor again, I now get this error message.</p>
<p><img decoding="async" loading="lazy" alt="CIS-CAT" src="data:image/png;base64,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" width="366" height="142" class="img_ev3q"></p>
<p>And another error! The policies are doing their job</p>
<p><img decoding="async" loading="lazy" alt="CIS-CAT" src="https://joeloveless.com/assets/images/cis_assessor3-507d8a49c9637534dd4fff93f69b052d.png" width="677" height="200" class="img_ev3q"></p>
<p>Instead of using a standard user account, I am going to login as my Administrator account now to run the CIS-CAT Assessor.</p>
<p>I'm now ready to run the CIS-CAT Assessor again, and have achieved the following results:</p>
<ul>
<li class="">CIS Microsoft Intune for Windows 11 Benchmark<!-- -->
<ul>
<li class="">337 settings</li>
<li class="">Pass: 320 settings</li>
<li class="">Fail: 12 settings</li>
<li class="">Error: 5 settings</li>
<li class="">Score: 95%</li>
</ul>
</li>
<li class="">CIS Microsoft Windows 11 Enterprise Benchmark<!-- -->
<ul>
<li class="">406 settings</li>
<li class="">Pass: 257 settings</li>
<li class="">Fail: 147 settings</li>
<li class="">Error: 0 settings</li>
<li class="">Score: 64%</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-didnt-apply-correctly">What didn't apply correctly?<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#what-didnt-apply-correctly" class="hash-link" aria-label="Direct link to What didn't apply correctly?" title="Direct link to What didn't apply correctly?" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="cis-microsoft-intune-for-windows-11-benchmark">CIS Microsoft Intune for Windows 11 Benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#cis-microsoft-intune-for-windows-11-benchmark" class="hash-link" aria-label="Direct link to CIS Microsoft Intune for Windows 11 Benchmark" title="Direct link to CIS Microsoft Intune for Windows 11 Benchmark" translate="no">​</a></h4>
<ul>
<li class="">Errors:<!-- -->
<ul>
<li class="">(L1) Ensure 'Turn off toast notifications on the lock screen (User)' is set to 'Enabled'</li>
<li class="">(L1) Ensure 'Do not preserve zone information in file attachments (User)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Notify antivirus programs when opening attachments (User)' is set to 'Enabled'</li>
<li class="">(L1) Ensure 'Prevent users from sharing files within their profile. (User)' is set to 'Enabled'</li>
<li class="">(L1) Ensure 'MSI Always install with elevated privileges (User)' is set to 'Disabled'</li>
</ul>
</li>
<li class="">Fails:<!-- -->
<ul>
<li class="">(L1) Ensure 'Allow Spotlight Collection (User)' is set to '0'</li>
<li class="">(L1) Ensure 'Remote Procedure Call (RPC) Locator (RpcLocator)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'SSDP Discovery (SSDPSRV)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'UPnP Device Host (upnphost)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Windows Media Player Network Sharing Service (WMPNetworkSvc)' is set to 'Disabled' or 'Not Installed'</li>
<li class="">(L1) Ensure 'Windows Mobile Hotspot Service (icssvc)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Xbox Accessory Management Service (XboxGipSvc)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Xbox Live Auth Manager (XblAuthManager)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Xbox Live Game Save (XblGameSave)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Xbox Live Networking Service (XboxNetApiSvc)' is set to 'Disabled'</li>
<li class="">(L1) Ensure 'Generate Security Audits' is set to 'LOCAL SERVICE, NETWORK SERVICE'</li>
<li class="">(L1) Ensure 'Impersonate Client' is set to 'Administrators, LOCAL SERVICE, NETWORK SERVICE, SERVICE'</li>
</ul>
</li>
</ul>
<p>I see themes here:</p>
<ol>
<li class="">
<p>User settings not applying properly. I've ran into this in the past. The CIS-CAT Assessor scans all user profiles, even if they are not sync'd to Entra. For example, if I had logged in with my local administrator account (which I had), CIS-CAT will then scan that path in the registry and report back a failure. For that, I'm not going to worry about these settings.</p>
</li>
<li class="">
<p>System Services. CIS has this configured as a Platform Script, rather than a remediation script with Write-Host being used. Logging is not being written locally on the device. Reading the comments on the script, I can see that it most likely will need to be modified:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">The purpose of this script is to configure a system using the recommendations </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">provided in the Benchmark, section(s), and profile level listed above to a </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">hardened state consistent with a CIS Benchmark.</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">The script can be tailored to the organization's needs such as by creating </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">exceptions or adding additional event logging.</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">This script can be deployed through various means, including Intune script </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">manager, running it locally, or through any automation tool.</span><br></span></code></pre></div></div>
</li>
<li class="">
<p>User Rights Assignment. This doesn't seem to be getting set correctly. Opening GPEdit and looking at the User Rights Assignments, I see this:</p>
</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Generate Security Audits" src="https://joeloveless.com/assets/images/cis_generate-29f2fa3c69746bd24faebc359cc9f02d.png" width="411" height="354" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Impersonate Client" src="https://joeloveless.com/assets/images/cis_impersonate-4f1eef7a9ac07377bc68c6a00e18d0ce.png" width="418" height="392" class="img_ev3q"></p>
<p>Looking in the registry at PolicyManager (HKLM:\Software\Microsoft\PolicyManager\providers\guid\default\Device\UserRights), I see that Intune says it's setting the policy correctly, but it's not actually setting. Fun times:</p>
<p><img decoding="async" loading="lazy" alt="Registry - Policy Manger" src="data:image/png;base64,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" width="729" height="81" class="img_ev3q"></p>
<p>From here, I would be looking at re-writing the Services script, most likely to separate them out and run as Remediations instead with proper logging. This will allow me to be more granular, as we do have a lot of exceptions for developers and such in our environment. The user policies is an area we have just kind of accepted failure on, and just look for the users we are actually targeting for success/failure, not all the accounts as our administrative accounts are not being sync'd into Entra. The User Rights Assignment, that's a tricky one and most likely something Microsoft needs to look into.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="cis-microsoft-windows-11-enterprise-benchmark">CIS Microsoft Windows 11 Enterprise Benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#cis-microsoft-windows-11-enterprise-benchmark" class="hash-link" aria-label="Direct link to CIS Microsoft Windows 11 Enterprise Benchmark" title="Direct link to CIS Microsoft Windows 11 Enterprise Benchmark" translate="no">​</a></h4>
<p>For the Enterprise benchmark, this is where it becomes more fun. Like I said earlier, the Enterprise benchmark is based off Group Policy, where the Intune benchmark is based off CSP. So we'll be running into some mapping issues, as Microsoft in all their wisdom, is not consistent in how settings are being applied. Some Intune settings map back to GP registry settings, some do not. No errors this time, so that's a win! I've gone through the list, pulled out all the Fails, and then have done a comparison between the Intune benchmark and the Enterprise benchmark. From there, I've documented what is supported in Intune, vs what is not supported. I've also found all the corresponding registry paths. It's a little bit confusing, but the way I describe it to people is like this (hopefully someone can correct me, or point me in a better direction):</p>
<ul>
<li class="">Look at the <a href="https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-configuration-service-provider" target="_blank" rel="noopener noreferrer" class="">Policy CSP documentation</a>
<ul>
<li class="">Anything that begins with ADMX_ is considered ADMX backed<!-- -->
<ul>
<li class="">For the most part ADMX backed settings then write to the GPO based registry path</li>
</ul>
</li>
<li class="">Anything that doesn't begin with ADMX_ is not ADMX backed<!-- -->
<ul>
<li class="">Some might write to the GPO registry location.</li>
<li class="">Some might not.</li>
<li class="">Some in even the same CSP area will have different results.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>All the data from the failures can be found on my <a href="https://github.com/Pacers31Colts18/Intune/blob/main/CIS%20v4.0/CIS_Enterprise_Failures.xlsx" target="_blank" rel="noopener noreferrer" class="">Github</a>. I initially tried to put everything in here, but gave myself more of a headache doing it that way. I've gone through about five iterations of this before just landing on an Excel spreadsheet.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="supplementingremediating-failures">Supplementing/Remediating Failures<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#supplementingremediating-failures" class="hash-link" aria-label="Direct link to Supplementing/Remediating Failures" title="Direct link to Supplementing/Remediating Failures" translate="no">​</a></h2>
<p>Confused yet? So am I. Okay, from here, the focus will be on the Enterprise benchmark from here on out.</p>
<p>Looking at the spreadsheet (Main tab), I see a few different themes:</p>
<ul>
<li class="">Intune supported, not in Intune benchmark<!-- -->
<ul>
<li class="">22 settings</li>
</ul>
</li>
<li class="">Not Intune supported, not in Intune benchmark<!-- -->
<ul>
<li class="">40 settings</li>
</ul>
</li>
<li class="">Intune supported, settings that write to different registry paths<!-- -->
<ul>
<li class="">100 settings</li>
</ul>
</li>
</ul>
<p>Breaking it down by CIS Category:</p>
<ul>
<li class=""><strong>Section 1: Account Policies</strong>
<ul>
<li class="">5 failures<!-- -->
<ul>
<li class="">1 Intune supported, not in Intune benchmark</li>
<li class="">4 Not Intune supported, not in Intune benchmark</li>
</ul>
</li>
</ul>
</li>
<li class=""><strong>Section 2: Local Policies</strong>
<ul>
<li class="">8 failures<!-- -->
<ul>
<li class="">2 Intune supported, not in Intune benchmark</li>
<li class="">6 Not Intune supported, not in Intune benchmark</li>
</ul>
</li>
</ul>
</li>
<li class=""><strong>Section 5: System Services</strong>
<ul>
<li class="">9 failures<!-- -->
<ul>
<li class="">4 Intune supported, failing from the Platform Script (Xbox)<!-- -->
<ul>
<li class="">Created a supplemental Services policy (see below)</li>
</ul>
</li>
<li class="">5 not Intune supported, still failing from the Platform Script</li>
</ul>
</li>
<li class=""><strong>Section 9: Windows Defender Firewall with Advanced Security</strong>
<ul>
<li class="">23 failures<!-- -->
<ul>
<li class="">23 Intune supported, settings that write to different registry paths<!-- -->
<ul>
<li class="">CSP writes to HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\MDM*ProfileName* rather than HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy*ProfilesName*</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class=""><strong>Section 18: Administrative Templates (Computer)</strong>
<ul>
<li class="">98 failures<!-- -->
<ul>
<li class="">33 Not Intune supported, not in Intune benchmark</li>
<li class="">17 Intune supported, not in Intune benchmark</li>
<li class="">46 Intune supported, settings that write to different registry paths</li>
</ul>
</li>
</ul>
</li>
<li class=""><strong>Section 19: Administrative Templates (User)</strong>
<ul>
<li class="">4 failures<!-- -->
<ul>
<li class="">3 Intune supported, not in Intune benchmark</li>
<li class="">1 Intune supported, settings that write to different registry paths</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="intune-supported-not-in-intune-benchmark">Intune Supported, not in Intune benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#intune-supported-not-in-intune-benchmark" class="hash-link" aria-label="Direct link to Intune Supported, not in Intune benchmark" title="Direct link to Intune Supported, not in Intune benchmark" translate="no">​</a></h3>
<p>I've now created four new profiles:</p>
<ul>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/CIS%20v4.0/CIS%20(L1)%20-%20Win11%20Enterprise%20-%20Administrative%20Templates%20(Computer).json" target="_blank" rel="noopener noreferrer" class="">CIS (L1) - Win11 Enterprise - Administrative Templates (Computer)</a></li>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/CIS%20v4.0/CIS%20(L1)%20-%20Win11%20Enterprise%20-%20Administrative%20Templates%20(User).json" target="_blank" rel="noopener noreferrer" class="">CIS (L1) - Win11 Enterprise - Administrative Templates (User)</a></li>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/CIS%20v4.0/CIS%20(L1)%20-%20Win11%20Enterprise%20-%20Local%20Policies.json" target="_blank" rel="noopener noreferrer" class="">CIS (L1) - Win11 Enterprise - Local Policies</a>
<ul>
<li class="">Note: Baseline calls for "Audit All or Higher", Audit All is not a setting in Intune despite the CSP documentation saying otherwise. Great job!</li>
<li class="">I didn't set this in the below results as it was either Allow All, or Deny.</li>
<li class="">See docs: <a href="https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-LocalPoliciesSecurityOptions?WT.mc_id=Portal-fx#networksecurity_restrictntlm_outgoingntlmtraffictoremoteservers" target="_blank" rel="noopener noreferrer" class="">https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-LocalPoliciesSecurityOptions?WT.mc_id=Portal-fx#networksecurity_restrictntlm_outgoingntlmtraffictoremoteservers</a></li>
</ul>
</li>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/CIS%20v4.0/CIS%20(L1)%20-%20Win11%20Enterprise%20-%20System%20Services.json" target="_blank" rel="noopener noreferrer" class="">CIS (L1) - Win11 Enterprise - System Services</a></li>
</ul>
<p>These should cover the settings that are Intune supported, but not in the Intune benchmark.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="not-intune-supported-not-in-intune-benchmark">Not Intune Supported, not in Intune benchmark<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#not-intune-supported-not-in-intune-benchmark" class="hash-link" aria-label="Direct link to Not Intune Supported, not in Intune benchmark" title="Direct link to Not Intune Supported, not in Intune benchmark" translate="no">​</a></h3>
<p>For settings that are not configurable in Intune, there are multiple different ways the settings can still be configured</p>
<ul>
<li class=""><strong>Custom OMA-URI</strong>
<ul>
<li class="">What I consider "Supported" is in the Settings Catalog, I didn't look at what is configurable through Custom OMA-URI.</li>
</ul>
</li>
<li class=""><strong>Remediation Scripts</strong>
<ul>
<li class="">You could do a detection/remediation script through Intune</li>
<li class=""><a href="https://learn.microsoft.com/en-us/intune/intune-service/fundamentals/remediations" target="_blank" rel="noopener noreferrer" class="">Microsoft Learn</a></li>
</ul>
</li>
<li class=""><strong>SCCM Configuration Items/Baselines</strong>
<ul>
<li class="">If you have SCCM or another tool, you could use Configuration Items to configure this.</li>
<li class=""><a href="https://learn.microsoft.com/en-us/intune/configmgr/compliance/deploy-use/create-configuration-items" target="_blank" rel="noopener noreferrer" class="">Microsoft Learn</a></li>
</ul>
</li>
<li class=""><strong>Group Policy</strong>
<ul>
<li class="">If you're hybrid joined, which is what this post is covering, you could also revert back to using Group Policy.</li>
</ul>
</li>
</ul>
<p>My preference (just mine), I prefer to use Remediation Scripts or Configuration Items. We started with Configuration Items, and are now starting to push those towards Remediation Scripts to have everything in one tool.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="settings-that-write-to-different-registry-paths">Settings that write to different registry paths<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#settings-that-write-to-different-registry-paths" class="hash-link" aria-label="Direct link to Settings that write to different registry paths" title="Direct link to Settings that write to different registry paths" translate="no">​</a></h3>
<p>This is where you need to explain to your security team that Microsoft in all it's wisdom is writing values to different registry locations. There are options, if you're a member of the CIS Workbench, you can fork the benchmark and create you're own. Depending on your org structure though, I would say that should be something dependent on the security team to accomplish and not anyone on the endpoint team.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="re-running-the-scans">Re-running the scans<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#re-running-the-scans" class="hash-link" aria-label="Direct link to Re-running the scans" title="Direct link to Re-running the scans" translate="no">​</a></h2>
<p>After setting up the above policies, I re-ran the scans, and achieve the following for the Enterprise benchmark:</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/CIS%20v4.0/Post-SettingsCatalog_CIS_Microsoft_Windows_11_Enterprise_Benchmark.csv" target="_blank" rel="noopener noreferrer" class="">Results file</a></p>
<ul>
<li class="">CIS Microsoft Windows 11 Enterprise Benchmark<!-- -->
<ul>
<li class="">406 settings</li>
<li class="">Pass: 273 settings (up from 257)</li>
<li class="">Fail: 131 settings</li>
<li class="">Error: 0 settings</li>
<li class="">Score: 68%</li>
</ul>
</li>
</ul>
<p>The rest of the results are mainly going to be from a registry path mis-match. One area I do want to call out is the Account Policies section, setting this through Intune can have undesirable affects. For that reason, we've typically left this out of Intune all together.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://joeloveless.com/blog/cis-benchmarks-enterprise-win11#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>As you can see, there is still work to be done, but most of the work from here on out is going to be talking with the security team.</p>
<ul>
<li class="">The banner will need to be updated, have someone (legal?) come up with a banner that suits your org.</li>
<li class="">If using Windows Hello, there are a handful of settings that can be tweaked to make a better experience (new blog post coming later)</li>
<li class="">If using Configuration Manager, don't set the Windows Update settings, let the client take care of that.<!-- -->
<ul>
<li class="">Research Dual Scan issues.</li>
</ul>
</li>
<li class="">Find what does and doesn't work in your org, pilot everything for a while before rolling out.<!-- -->
<ul>
<li class="">Don't just take my word for it, I'm just a random guy on the internet.</li>
</ul>
</li>
<li class="">Explain the settings to your security team<!-- -->
<ul>
<li class="">Point them to this excel spreadsheet showing the registry differences.</li>
</ul>
</li>
</ul>
<p>If you're not subject to regulatory requirements that rely on the Enterprise Benchmark, use the Intune benchmark. If you do rely on the Enterprise benchmark, you have decisions to make. We were already well on the way to Intune, and as more and more settings become configurable, we're only going to see our numbers (hopefully) increase. Still, there is a strong chance Microsoft will write to different registry locations. I would continue to push on the regulatory bodies (and possibly CIS) to include additional checks in the audit files.</p>
<p>Please reach out if you have any questions about this article, always happy to help out when I can.</p>]]></content:encoded>
            <category>Intune</category>
            <category>CIS</category>
            <category>Windows 11</category>
            <category>Hybrid</category>
        </item>
        <item>
            <title><![CDATA[Converting Registry Based SCCM Configuration Items to Intune Remediation Scripts]]></title>
            <link>https://joeloveless.com/blog/configurationitems-to-remediationscripts</link>
            <guid>https://joeloveless.com/blog/configurationitems-to-remediationscripts</guid>
            <pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Script walkthrough on converting SCCM Configuration Items to Intune Remediation Scripts.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/converting-registry-based-sccm-configuration-items-to-intune-remediation-scripts-ba547be700ae8bcf1d566b2aec8642b0.png" width="1200" height="630" class="img_ev3q"></p>
<p>It's Memorial Day Weekend here in the States, which means it's Indy 500 Weekend. It also means my Indiana Pacers are up 2-0 on the New York Knicks. If you know me, I'm a huge Pacers fan. With that comes a hatred for the Knicks, so right now I am giddy. Game 3 is tomorrow in Indianapolis, and the same day as the Indy 500. Back in Indy, we call that Racers and Pacers Weekend. I don't miss Indiana much for anything at all, but events like this, I do miss. Indianapolis knows how to do sporting weekends right. Without being back in Indy, my family and I will most likely be cooking some hot dogs, eating some s'mores, and burning all the downed sticks and brush from the recent storms. My daughter is also looking into getting into mountain biking, so I have been going down the internet rabbit hole looking for a decently priced mountain bike.</p>
<p>I've had this idea on my mind, and asked Steve Jesok the question at MMS during his session on Intune Remediation Scripts and SCCM Configuration Items. "Does anyone have a way to convert registry based configuration items to Intune Remedation Scripts? Steve was unaware of anything out there. I'm also not entirely sure how useful this would be for admins out there. If already using PowerShell based detection and remediation in SCCM, this isn't really applicable. If you're using the "GUI method" of looking for a registry key, and then remediating off of that, then this post might be for you.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-a-remediation-script">What is a Remediation Script?<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#what-is-a-remediation-script" class="hash-link" aria-label="Direct link to What is a Remediation Script?" title="Direct link to What is a Remediation Script?" translate="no">​</a></h2>
<p><a href="https://learn.microsoft.com/en-us/intune/intune-service/fundamentals/remediations" target="_blank" rel="noopener noreferrer" class="">Intune Remediations - Microsoft Learn</a>. Simply put, a Remediation Script is a "detection" and a "remediation". If the detection script detects the state is not correct (Non-Compliant), it will then go onto the Remediation portion.</p>
<p>With Intune, we do have a limit of 200 remediation scripts, which compared to SCCM, is quite small. I like to think of a Remediation Script as being equal to a Configuration Baseline. I don't think we should be doing single actions necessarily, but combining like actions together. In this post, I'll be combining Services together, where I'll want to set multiple Windows Services to a Disabled status.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-do-you-mean-by-gui-method">What do you mean by "GUI Method"?<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#what-do-you-mean-by-gui-method" class="hash-link" aria-label="Direct link to What do you mean by &quot;GUI Method&quot;?" title="Direct link to What do you mean by &quot;GUI Method&quot;?" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://joeloveless.com/assets/images/sccm_gui_method-510429092d035182fba7a6eeb122e4c6.png" title="GUI Method" width="651" height="619" class="img_ev3q"></p>
<p>Anything that's not script based using Powershell/VBScript/etc, but configurable through the GUI.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pre-requisites">Pre-Requisites<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#pre-requisites" class="hash-link" aria-label="Direct link to Pre-Requisites" title="Direct link to Pre-Requisites" translate="no">​</a></h2>
<p>I've added all the files needed for this on my GitHub page.</p>
<ul>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Intune-Registry-Detect.ps1" target="_blank" rel="noopener noreferrer" class="">Intune Detection Template</a></li>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Intune-Registry-Detect.ps1" target="_blank" rel="noopener noreferrer" class="">Intune Remediation Template</a></li>
<li class=""><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Convert-CMConfigurationItemtoIntuneRemediationScript.ps1" target="_blank" rel="noopener noreferrer" class="">Convert Configuration Items to Remediation Script</a></li>
</ul>
<h1>Walkthrough</h1>
<p>Before getting too far into this, my method only works with the templates above for an Intune Detection and Remediation Script. What we're doing is taking these template files, and then doing some manipulation with PowerShell. Working on a team of 6 other engineers, we're trying our best to have repeatable practices, and make the code readable for everyone else. By having a template, this makes things much easier for us to know what is going on. The long-term goal is to also combine this with GitHub Actions and have a repository of code that requires code review and a more DevOps mindset. We're not 100% there yet, but we're working our way to that goal.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="intune-detection-and-remediation-template">Intune Detection and Remediation Template<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#intune-detection-and-remediation-template" class="hash-link" aria-label="Direct link to Intune Detection and Remediation Template" title="Direct link to Intune Detection and Remediation Template" translate="no">​</a></h2>
<p>I feel like these are pretty self explanatory. As I said earlier, we're thinking of this as more of a Configuration Baseline mindset, where you combine multiple Configuration Items into a Baseline. We're trying our best to not have a single script doing one single purpose (although there are use cases for that). With a large environment, we're trying to avoid the unknown of the future with a 200 script limit.</p>
<p>By having a PowerShell array for the registry key detection, we're able to achieve this with this method.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="converting-configuration-items-to-remediation-scripts">Converting Configuration items to Remediation Scripts<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#converting-configuration-items-to-remediation-scripts" class="hash-link" aria-label="Direct link to Converting Configuration items to Remediation Scripts" title="Direct link to Converting Configuration items to Remediation Scripts" translate="no">​</a></h2>
<p>I like to try to walk through what I am doing, mainly to kinda serve as a way to document my thoughts and what the heck I was doing. But hopefully people are finding this useful also. I don't consider myself to be the best at this by any means, so hopefully my stumbling explanations will serve somebody out there!</p>
<p>A couple thoughts before I go into the code:</p>
<ul>
<li class="">I've started using CoPilot to provide the comment-based help in my code. Freaking amazing. Such a time saver, highly recommended.</li>
<li class="">I hate the name of this function. Any suggestions?</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="parameter-block">Parameter Block<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#parameter-block" class="hash-link" aria-label="Direct link to Parameter Block" title="Direct link to Parameter Block" translate="no">​</a></h3>
<p>At the beginning of the function is the parameters. You can input multiple configuration items through an array, or you can provide no configuration item and get a Out-GridView that will allow you to multi-select Configuration Items. We also have a ValidateSet that will allow you to choose if you only want a Detection Method, or a Remediation, or both. I don't really know why you would just want a Remediation Script, but maybe you already have a Detection Method in place? By default, it's going to default to both a Detection and Remediation Script.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [CmdletBinding()]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Param(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $False)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [array]$configurationItem,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $False)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [ValidateSet("Detection", "Remediation", "Detection/Remediation")]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $scriptType = "Detection/Remediation"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    )</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="authentication-declarations">Authentication, Declarations<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#authentication-declarations" class="hash-link" aria-label="Direct link to Authentication, Declarations" title="Direct link to Authentication, Declarations" translate="no">​</a></h3>
<p>I'm capturing the present working directory ($pwd) to put the location back to once the function is finished. We're also providing authentication to CM, as you will need access to CM to pull the Configuration Items. After that, my declarations are very repeatable, but the last 4 lines are customizable to fit your needs.</p>
<p>$templateDetectionScript and $templateRemediationScript should be the files on my GitHub, just change to the location that fits for you. $OutputDetectionScript and $OutputRemediationScript are where the files will be saved to, with the name of the function and the date ran.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">$StartingLocation = $pwd</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region ConfigMgr Authentication</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $SiteCode = "JL1"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Set-Location "$SiteCode`:"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Declarations</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $FunctionName = $MyInvocation.MyCommand.Name.ToString()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $date = Get-Date -Format yyyyMMdd-HHmm</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($outputdir.Length -eq 0) { $outputdir = $pwd }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $OutputFilePath = "$OutputDir\$FunctionName-$date.csv"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $LogFilePath = "$OutputDir\$FunctionName-$date.log"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $templateDetectionScript = "C:\Users\jlove\test\Intune-Registry-Detect.ps1"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $templateRemediationScript = "C:\Users\jlove\test\Intune-Registry-Remediate.ps1"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $OutputDetectionScript = "$OutputDir\detect_$FunctionName-$date.ps1"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $OutputRemediationScript = "$OutputDir\remediate_$FunctionName-$date.ps1"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $ResultsArray = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gathering-the-configuration-items">Gathering the Configuration Items<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#gathering-the-configuration-items" class="hash-link" aria-label="Direct link to Gathering the Configuration Items" title="Direct link to Gathering the Configuration Items" translate="no">​</a></h3>
<p>In the first part, if you do not input a configuration item in the parameter block, you'll get a popup from Out-GridView allowing you to multi-select configuration items. I forget who I found this little trick through, but I've been trying to put the Out-GridView selection into everything I do since then. I find it to be so helpful. If you did input configuration items in the parameter block, then we are doing a search for those configuration items, and then adding them to an array.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> #region Configuration Items</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($null -eq $configurationItem) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $resolvedConfigurationItems = (Get-CMConfigurationItem -Name * -Fast | </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Select-Object DateLastModified, InUse, LocalizedDescription, LocalizedDisplayName, ObjectPath |</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Out-GridView -PassThru).LocalizedDisplayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $resolvedConfigurationItems = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($item in $configurationItem) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $resolvedItem = (Get-CMConfigurationItem -Name $item -Fast).LocalizedDisplayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            if ($resolvedItem) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $resolvedConfigurationItems += $resolvedItem</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                Write-Warning "Could not resolve configuration item: $item"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-configuration-items-for-conversion">Building the Configuration Items for Conversion<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#building-the-configuration-items-for-conversion" class="hash-link" aria-label="Direct link to Building the Configuration Items for Conversion" title="Direct link to Building the Configuration Items for Conversion" translate="no">​</a></h3>
<p>With how the data is stored in CM, I found I had to do some manipulation to then get it to work with PowerShell.</p>
<ul>
<li class="">For the $complianceRules section, in our environment we are looking for a "Value" and checking to make sure the item "Exists". Because of that, I only wanted to grab what is a value, so I put a Where-Object on this.</li>
<li class="">Similar with $complianceSettings, I don't want to grab anything that's already a script, just anything where the sourceType = Registry. If it's not Registry-based, I'm simply skipping over all of that. There are other ways to export those scripts.</li>
<li class="">Switch is somewhat new to me, and I've been trying to use that more also. CM stores the items as Int64, String, and StringArray (amongst some others), but using PowerShell, they need to be DWORD, STRING, MULTISTRING, so I am converting to that format.</li>
<li class="">I also need to change HKEY_LOCAL_MACHINE to HKLM: and HKEY_CURRENT_USER to HKCU:.</li>
<li class="">Once all the data is manipulated how I want, I'm adding everything to a PSCustomObject, and then storing in a $ResultsArray.</li>
</ul>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Compliance Rules/Compliance Settings</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($item in $resolvedConfigurationItems) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Output "Processing configuration item: $item"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $complianceRules = Get-CMComplianceRule -Name $item -WarningAction Ignore | </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Where-Object { $_.expression.operands.methodtype -eq "Value" }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $complianceSettings = Get-CMComplianceSetting -Name $item -WarningAction Ignore | </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Where-Object { $_.sourceType -eq "Registry" } | Select-Object -Unique</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if (!$complianceSettings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Warning "Configuration Item '$item' is not Registry-based. Skipping..."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            continue</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        foreach ($rule in $complianceRules) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            foreach ($setting in $complianceSettings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $settingdatatypeName = switch ($setting.settingdataType.Name) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    "Int64" { "DWORD" }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    "String" { "STRING" }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    "StringArray" { "MULTISTRING" }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    default { $setting.settingdataType.Name }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $settingLocation = $setting.location -replace '^HKEY_LOCAL_MACHINE', 'HKLM:' -replace '^HKEY_CURRENT_USER', 'HKCU:'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $result = New-Object -TypeName PSObject -Property @{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    RuleValue       = $($rule.expression.operands.Value)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SettingDataType = $settingdatatypeName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SettingPath     = $settingLocation</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                    SettingName     = $($setting.ValueName)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $ResultsArray += $result</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($ResultsArray.Count -eq 0) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Warning "No registry-based configuration settings found in any selected Configuration Items."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-registry-keys">Building the Registry Keys<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#building-the-registry-keys" class="hash-link" aria-label="Direct link to Building the Registry Keys" title="Direct link to Building the Registry Keys" translate="no">​</a></h3>
<p>In this region, we're simply taking the results from the $ResultsArray and converting to the format needed in the Detection and Remediation Scripts.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Build RegistryKeys content</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $registryKeysContent = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $registryKeysContent += "`$RegistryKeys = @("</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($result in $ResultsArray) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $registryKeysContent += "    @{ Path = `"$($result.SettingPath)`"; Name = `"$($result.SettingName)`"; Type = `"$($result.SettingDataType)`"; Value = `"$($result.RuleValue)`" }"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $registryKeysContent += ")"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="modifying-the-templates-saving-the-new-files">Modifying the templates, saving the new files.<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#modifying-the-templates-saving-the-new-files" class="hash-link" aria-label="Direct link to Modifying the templates, saving the new files." title="Direct link to Modifying the templates, saving the new files." translate="no">​</a></h3>
<ul>
<li class="">The IndexOf, we're looking for the correct lines in the detection and remediation script, and if not, we're leaving those sections as is. In simple terms, we're only looking to modify a certain block of text in the two template files, and add on to it with whatever is in the ResultsArray.</li>
<li class="">Once all that is finished, we're then saving as a new PowerShell file, with the right encoding.</li>
<li class="">We have checks from the parameter block looking for either Detection, Remediation, or both.</li>
</ul>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> #region Detection Script</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($scriptType -eq "Detection" -or $scriptType -eq "Detection/Remediation") {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $detectiontemplateLines = Get-Content $templateDetectionScript</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $detectionstartIndex = $detectiontemplateLines.IndexOf('$RegistryKeys = @(')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $detectionendIndex = $detectiontemplateLines.IndexOf(')')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ($detectionstartIndex -eq -1 -or $detectionendIndex -eq -1) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Warning "Could not find `$RegistryKeys block in the detection template. Check formatting."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $detectionnewContent = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $detectionnewContent += $detectiontemplateLines[0..($detectionstartIndex - 1)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $detectionnewContent += $registryKeysContent</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $detectionnewContent += $detectiontemplateLines[($detectionendIndex + 1)..($detectiontemplateLines.Length - 1)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $detectionnewContent | Out-File $OutputDetectionScript -Encoding utf8</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Output "Detection script saved as: $OutputDetectionScript"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Remediation Script</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($scriptType -eq "Remediation" -or $scriptType -eq "Detection/Remediation") {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $remediationtemplateLines = Get-Content $templateRemediationScript</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $remediationstartIndex = $remediationtemplateLines.IndexOf('$RegistryKeys = @(')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $remediationendIndex = $remediationtemplateLines.IndexOf(')')</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        if ($remediationstartIndex -eq -1 -or $remediationendIndex -eq -1) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Warning "Could not find `$RegistryKeys block in the remediation template. Check formatting."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $remediationnewContent = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $remediationnewContent += $remediationtemplateLines[0..($remediationstartIndex - 1)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $remediationnewContent += $registryKeysContent</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $remediationnewContent += $remediationtemplateLines[($remediationendIndex + 1)..($remediationtemplateLines.Length - 1)]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $remediationnewContent | Out-File $OutputRemediationScript -Encoding utf8</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Write-Output "Remediation script saved as: $OutputRemediationScript"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Set-Location $StartingLocation</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="script-example">Script Example<a href="https://joeloveless.com/blog/configurationitems-to-remediationscripts#script-example" class="hash-link" aria-label="Direct link to Script Example" title="Direct link to Script Example" translate="no">​</a></h2>
<p>In my example, I have a set of Configuration Items that are setting Windows Services to Disabled in the registry. I'm wanting to take those, and convert them to an Intune Remediation Script.</p>
<p>I ran the function without inputting any configuration items, or choosing what method I want. Who is to remember the names of these anyways?</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" title="Example" width="893" height="224" class="img_ev3q"></p>
<p>I am now prompted to select what Configuration Items I want to include. I want all of them that are Services</p>
<p><img decoding="async" loading="lazy" src="https://joeloveless.com/assets/images/out-gridview-a485a420b43318c9be83c1c0b10ec5f5.png" title="Out-Gridview" width="1056" height="601" class="img_ev3q"></p>
<p>I want some pretty output telling me what is happening</p>
<p><img decoding="async" loading="lazy" src="https://joeloveless.com/assets/images/example2-6f59c017895e46d451e2ffa735f72e3f.png" title="Output" width="586" height="229" class="img_ev3q"></p>
<p>I now have both a Detection and Remediation Script!</p>
<p><img decoding="async" loading="lazy" src="https://joeloveless.com/assets/images/example_detection-c56b7271399c49e3b8ade9049cbad5a1.png" title="Detection Script" width="1319" height="795" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://joeloveless.com/assets/images/example_remediation-1db124b932d8759e4fe83aab256ce958.png" title="Remediation Script" width="1169" height="522" class="img_ev3q"></p>
<h1>Conclusion</h1>
<p>Next step would be to tie this all to a GitHub Action, and have this upload to Intune, following proper code review using branch protection. Again, maybe you already had PowerShell scripts in CM. You were more thoughtful than me (we are about 50/50). If not, you now have a way to convert those more easily.</p>
<p>Have a great weekend everyone! GO PACERS</p>
<p><img decoding="async" loading="lazy" src="https://joeloveless.com/assets/images/tyrese-ee7fb803e90565c0decca498c3f9dddf.jpeg" title="Tyrese Haliburton" width="654" height="436" class="img_ev3q"></p>]]></content:encoded>
            <category>Intune</category>
            <category>SCCM</category>
            <category>PowerShell</category>
        </item>
        <item>
            <title><![CDATA[CIS L1 Benchmarks in Intune with AVD Multi-Session Hosts]]></title>
            <link>https://joeloveless.com/blog/cis-benchmarks-avd-multisession</link>
            <guid>https://joeloveless.com/blog/cis-benchmarks-avd-multisession</guid>
            <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Details on what settings aren't in scope with AVD Multi-Session hosts.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Post Title" src="https://joeloveless.com/assets/images/cisl1-benchmarks-in-intune-with-avd-multi-session-hosts-087e69f526399f63929a3032f290e9e2.png" width="1200" height="630" class="img_ev3q"></p>
<p>What a week! Last week was MMS MOA 2025, and I still have this odd hangover from the week that I'm trying to recover from. The past couple nights I've been in bed by 8-9 p.m., which for my 40 year old self, isn't that far off. The haze in general is starting to wear off, and I'm starting to get back into the swings of every day life and every day work. I always come out of MMS with such excitement, but then typically there are the work challenges that get in the way. Never the fault of the actual team I'm on mind you, just work in general. I thought about making a post about all the sessions, all the fun, and everything else that makes MMS great, but my brain is foggy, and I don't know that I could do the week proper justice.</p>
<p>One of the questions I asked in a session about the Open Intune Baseline was surrounding AVD, and how to manage multi-session hosts. For those unfamiliar, AVD Multi-Session is running a "ServerRDSH" SKU, which, accurate or not, I think of these as Terminal Servers running a Windows 11 like operating system. With that, there are challenges with managing these in Intune. Microsoft, has IMO a pretty poorly written [Microsoft Learn[(<a href="https://learn.microsoft.com/en-us/intune/intune-service/fundamentals/azure-virtual-desktop-multi-session" target="_blank" rel="noopener noreferrer" class="">https://learn.microsoft.com/en-us/intune/intune-service/fundamentals/azure-virtual-desktop-multi-session</a>)]<!-- --> article, that vaguely lays out the limitations. As we've started on the AVD path, and with the amount of clicks it takes in Intune to see what settings are actually applying and what are Not Applicable, we noticed quite the drift in our settings, especially when configuring the CIS L1 Baselines. In our case, due to auditing purposes, we use CIS Windows 11 Enterprise L1, rather than the Intune baseline that has been developed.</p>
<p>Once we figured out there was a limitation in the settings, I got thinking, how can we gather this data and actually know what is applying?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="graph-api-for-the-win">Graph API for the win<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#graph-api-for-the-win" class="hash-link" aria-label="Direct link to Graph API for the win" title="Direct link to Graph API for the win" translate="no">​</a></h2>
<p>When the Graph API first came out, I was confused on how this worked. Granted, I'm still confused, but just not as confused. In ITIL terms, that is considered progressing iteratively. We looked at the Graph SDK, and I quickly got frustrated by the length of some of the cmdlets, and the inconsistencies. So rather than fumbling with the cmdlets, I've really dove into the Graph API, and found it so much more repeatable and enjoyable. There are still gotchas, and I'm still trying to really figure out the syntax on searching, but I'm getting better.</p>
<p>I wrote a quick function, called <a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneConfigurationSettings.ps1" target="_blank" rel="noopener noreferrer" class="">Export-IntuneConfigurationSettings</a>. What this allows me to do is search the Graph URI "<a href="https://graph.microsoft.com/beta/deviceManagement/configurationSettings" target="_blank" rel="noopener noreferrer" class="">https://graph.microsoft.com/beta/deviceManagement/configurationSettings</a>", for settings and return the data from that. (Settings Catalog backed only)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="walking-through-the-script">Walking through the script<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#walking-through-the-script" class="hash-link" aria-label="Direct link to Walking through the script" title="Direct link to Walking through the script" translate="no">​</a></h2>
<p>I don't like to just link a script and leave it at that, so let's walk through it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="parameters-connection-declarations">Parameters, Connection, Declarations<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#parameters-connection-declarations" class="hash-link" aria-label="Direct link to Parameters, Connection, Declarations" title="Direct link to Parameters, Connection, Declarations" translate="no">​</a></h3>
<p>If you've seen past posts from me, a lot of what I write I like to make very repeatable. To be honest, I do a lot of copying and pasting. But, it makes things easier for me, and I like to think easier for the people I work with when they look at our modules and functions and see repeatable code, rather than something crazy.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $uri = "https://graph.microsoft.com/beta/deviceManagement/configurationSettings"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $Settings = (Invoke-MGGraphRequest -Method Get -Uri $uri).value</span><br></span></code></pre></div></div>
<p>Based off the connection above, I was able to find all the different operating system skus that are available on the Windows side in Intune. For this purposes (and most), I don't really care too much about iOS, Android, or Mac, so I'm just including Windows based operating systems.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">$settings.applicability.windowsskus</span><br></span></code></pre></div></div>
<p>This will give you the direct path to the full list of the WindowsSkus.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">[CmdletBinding()]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Param(</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [Parameter(Mandatory = $True,</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            HelpMessage = "Choose from the following: All, windowsCloudN, windows11SE, iotEnterpriseSEval, windowsCPC, windowsEnterprise, windowsProfessional, windowsEducation, holographicForBusiness, windowsMultiSession, iotEnterprise")]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [ValidateSet('All', 'windowsCloudN', 'windows11SE', 'windows11SE', 'iotEnterpriseSEval', 'windowsCPC', 'windowsEnterprise', 'windowsProfessional', 'windowsEducation', 'holographicForBusiness', 'windowsMultiSession', 'iotEnterprise')]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        [string]$Scope</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    )</span><br></span></code></pre></div></div>
<p>As you can see above, I'm doing a ValidateSet and giving options on the different SKUs to choose from, or you can select All.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Microsoft Graph Connection check</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($null -eq (Get-MgContext)) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Error "Authentication needed. Please connect to Microsoft Graph."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Break</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Declarations</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $FunctionName = $MyInvocation.MyCommand.Name.ToString()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $date = Get-Date -Format yyyyMMdd-HHmm</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($outputdir.Length -eq 0) { $outputdir = $pwd }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $OutputFilePath = "$OutputDir\$FunctionName-$date.csv"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $LogFilePath = "$OutputDir\$FunctionName-$date.log"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    $ResultsArray = @()</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<p>This section is basically in all the code I have. In my profile, I have an $outputDir that I am setting, just a default path to dump all my files. If that is not present, than it will dump to present working directory.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gathering-the-settings">Gathering the settings<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#gathering-the-settings" class="hash-link" aria-label="Direct link to Gathering the settings" title="Direct link to Gathering the settings" translate="no">​</a></h3>
<p>Now, we're ready to gather the settings. Based on the $Scope above, I have a Switch statement, one that is looking for <strong>ALL</strong> the settings, and one looking for settings based on a specific SKU.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"> #region Gather the settings</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    Switch ($Scope) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        "All" {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $uri = "https://graph.microsoft.com/beta/deviceManagement/configurationSettings"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            $Settings = (Invoke-MGGraphRequest -Method Get -Uri $uri).value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Default {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Try {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $uri = "https://graph.microsoft.com/beta/deviceManagement/configurationSettings?`$search=%22|||WindowsSkus=$Scope|||%22"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                $Settings = (Invoke-MGGraphRequest -Method Get -Uri $uri).value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Catch {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                Write-Error "Error gathering Settings: $_"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-object-outputting-the-results">Building the object, outputting the results<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#building-the-object-outputting-the-results" class="hash-link" aria-label="Direct link to Building the object, outputting the results" title="Direct link to Building the object, outputting the results" translate="no">​</a></h3>
<p>Now that we have the data, we can then build it into a PSCustomObject, and output the results. Nothing too crazy here, this is the data I found most intriguing, but there is a ton of information in the Graph API for this. Once that is done, we're outputting the results to a CSV file to look at later.</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">#region Build Object</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    foreach ($item in $Settings) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $result = New-Object -TypeName PSObject -Property @{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Name                    = $item.Name</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            Keywords                = $item.Keywords -join "; "</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            RootDefinitionId        = $item.RootDefinitionId</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            DisplayName             = $item.DisplayName</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            HelpText                = $item.HelpText</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            OffsetURI               = $item.OffsetURI</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            InfoUrls                = $item.InfoUrls -join "; "</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            MinimumSupportedVersion = $item.applicability.MinimumSupportedVersion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            WindowsSkus             = $item.applicability.WindowsSkus -join "; "</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $ResultsArray += $result</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #region Results</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if ($ResultsArray.Count -ge 1) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        $ResultsArray | Sort-Object -Property DisplayName | Export-Csv -Path $OutputFilePath -NoTypeInformation</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    # Test if output file was created</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    if (Test-Path $OutputFilePath) {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Output "Output file = $OutputFilePath."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    else {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        Write-Warning "No output file created."</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    #endregion</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-data">The Data<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#the-data" class="hash-link" aria-label="Direct link to The Data" title="Direct link to The Data" translate="no">​</a></h2>
<p>I've uploaded the raw CSV file on my Github, along with some other files.</p>
<p><a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneConfigurationSettings-20250513-1630_All.csv" target="_blank" rel="noopener noreferrer" class="">CSV output for All</a>
<a href="https://github.com/Pacers31Colts18/Intune/blob/main/Export-IntuneConfigurationSettings-20250513-1628_multi-session.csv" target="_blank" rel="noopener noreferrer" class="">CSV output for Multi-Session</a></p>
<p>Initial count:</p>
<ul>
<li class="">16232 lines/settings for All</li>
<li class="">13288 lines/settings for Multi-Session</li>
<li class="">13739 lines/settings for Windows Enterprise</li>
</ul>
<p>Quite the difference, even between Multi-Session and Enterprise</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cis-benchmarks">CIS Benchmarks<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#cis-benchmarks" class="hash-link" aria-label="Direct link to CIS Benchmarks" title="Direct link to CIS Benchmarks" translate="no">​</a></h3>
<p>The last bit of this post, is how this relates to CIS Benchmarks. When I first started looking at this, we were (and still) are on Enterprise v3.0.0. Since then, v4.0.0 has been released, along with v4.0.0 of the Intune benchmark. Rather than go back and re-work all the data, which was becoming to annoying for me. I've included three tabs, one for CIS Windows 11 Enterprise v3.0.0, one for CIS Windows 11 Enterprise v4.0.0 (only the new settings), and one for CIS Windows 11 Intune v4.0.0</p>
<p>The data for that can also be found on my <a href="https://github.com/Pacers31Colts18/Intune/blob/main/Intune_CISBenchmarks_MultiSession.xlsx" target="_blank" rel="noopener noreferrer" class="">GitHub</a></p>
<p>Breakdown of each tab:</p>
<ul>
<li class="">v3.0.0 Windows 11 Enterprise L1<!-- -->
<ul>
<li class="">61/384 settings are Intune configurable but not AVD Multi-Session scoped.<!-- -->
<ul>
<li class="">Intune configurable = Settings Catalog configurable, no custom ADMX, CSP, Remediation, etc.</li>
</ul>
</li>
<li class="">82/384 settings are not Intune configurable, meaning they are not an option in the Settings Catalog<!-- -->
<ul>
<li class="">Or they could be names I couldn't find, looking at you One Drive instead of OneDrive!</li>
</ul>
</li>
<li class="">143/384 settings are not supported in Intune for AVD Multi-Session hosts</li>
</ul>
</li>
<li class="">v4.0.0 Windows 11 Enterprise L1<!-- -->
<ul>
<li class="">Like I said, a new benchmark released, so this tab is a little bit of a work in progress here.</li>
<li class="">3/31 settings are Intune configurable but not AVD Multi-Session scoped.</li>
<li class="">10/31 settings are not Intune configurable, meaning they are not an option in the Settings Catalog</li>
<li class="">13/31 settings  are not supported in Intune for AVD Multi-Session hosts (adding in the v3.0.0 settings to get the full picture)</li>
</ul>
</li>
</ul>
<p>Before I go too much further, I do want to recognize that CIS says the Intended Audience for these benchmarks are:</p>
<p><img decoding="async" loading="lazy" alt="CIS Guidance" src="https://joeloveless.com/assets/images/win11_guidance-f390ad882f251527a59d7d033f1e98f4.png" width="1274" height="451" class="img_ev3q"></p>
<p>We're in this situation where we have multiple forests, hybrid joined, and no longer wish to manage our settings with Group Policy. Due to regulations, we also need to adhere to IRS config standards, which use the Enterprise benchmarks. I don't believe we're the only ones out there doing this, as Microsoft's stance has been to push users towards Intune management rather than Group Policy management, so this is the pickle we are in.</p>
<p><em>Mini disclaimer over</em></p>
<p>For work purposes, the Enterprise benchmarks is all I really care about, but for everyone else, I wanted to provide the Intune benchmark data also.</p>
<ul>
<li class="">v4.0.0 Microsoft Intune for Windows 11<!-- -->
<ul>
<li class="">86/337 settings/lines are Intune configurable but not AVD Multi-session scoped.</li>
<li class="">16/337 settings are not Intune configurable, meaning they are not an option in the Settings Catalog.<!-- -->
<ul>
<li class="">Note: These are named properly from CIS, where the Enterprise uses Group Policy naming convention.<!-- -->
<ul>
<li class="">Next rant: You think they could standardize the naming scheme for us between GPO and Intune? That'd be super neat.</li>
</ul>
</li>
</ul>
</li>
<li class="">102/337 settings are not supported in Intune for AVD Multi-Session Hosts</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="big-takeways">Big Takeways<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#big-takeways" class="hash-link" aria-label="Direct link to Big Takeways" title="Direct link to Big Takeways" translate="no">​</a></h3>
<ul>
<li class="">For some reason, User Rights Assignments aren't supported. Why? Beats me. It seems like you would want these to be configurable, but maybe I am missing something.</li>
<li class="">ASR Rules aren't supported. I'm assuming you just wouldn't want processes to start getting blocked on these and locking users out?</li>
<li class="">Services, not Intune supported at all, but maybe one day?</li>
<li class="">The rest seem like random settings to me. I don't really see a rhyme or reason really to why some would be supported, but some aren't. Example: Allow Cortana = Supported. Allow Cortana Above Lock = Not Supported</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://joeloveless.com/blog/cis-benchmarks-avd-multisession#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Really, this was more of an awareness post more than anything. I think a lot of us are new to the AVD/VDI/Virtual world, coming mainly from a physical workstation side of the house. I've touched Citrix, but it's been many years ago. With Intune and now Microsoft having AVD, it seems orgs are sort of combining the teams and hoping to set the policies the same as a workstation. To me it makes total sense to go this route, but there are some catches and things to consider, especially with Intune. Hopefully this will help someone else out, and be able to catch settings not applying more easily.</p>]]></content:encoded>
            <category>Intune</category>
            <category>CIS</category>
            <category>AVD</category>
        </item>
    </channel>
</rss>