<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Automation on Nicola Suter</title><link>https://tech.nicolonsky.ch/tags/automation/</link><description>Recent content in Automation on Nicola Suter</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 Nicola Suter</copyright><lastBuildDate>Sun, 24 May 2026 12:00:03 +0000</lastBuildDate><atom:link href="https://tech.nicolonsky.ch/tags/automation/rss.xml" rel="self" type="application/rss+xml"/><item><title>Manage Microsoft Sentinel Table Tiers and Retention as Code with Bicep</title><link>https://tech.nicolonsky.ch/sentinel-table-retention-bicep/</link><pubDate>Sun, 24 May 2026 12:00:03 +0000</pubDate><guid>https://tech.nicolonsky.ch/sentinel-table-retention-bicep/</guid><description>&lt;p&gt;Managing Microsoft Sentinel table retention and tiers is typically done through scripts or the portal, but neither approach fits well into an infrastructure-as-code workflow and it can be difficult to maintain tables at scale. While exploring the Log Analytics Bicep resource provider, I came across the &lt;code&gt;Microsoft.OperationalInsights/workspaces/tables&lt;/code&gt; resource type&lt;cite&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/cite&gt;, which makes it possible to manage table tier and retention declaratively with Bicep: version-controlled, reviewable, and reproducible across environments.&lt;/p&gt;
&lt;p&gt;For each table we typically want to control:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Table tier&lt;/strong&gt;: Analytics or Data Lake (Auxiliary)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive retention&lt;/strong&gt;: the hot, queryable period&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total retention&lt;/strong&gt;: the overall retention period of the table&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Table Settings"
 width="1670"
 height="1172"
 src="https://tech.nicolonsky.ch/sentinel-table-retention-bicep/table-settings_hu_94023cc300a6c6fb.png"
 srcset="https://tech.nicolonsky.ch/sentinel-table-retention-bicep/table-settings_hu_94023cc300a6c6fb.png 800w, https://tech.nicolonsky.ch/sentinel-table-retention-bicep/table-settings_hu_6435ce0747b7b6f8.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="https://tech.nicolonsky.ch/sentinel-table-retention-bicep/table-settings.png"&gt;&lt;figcaption&gt;Typical Table Configuration Settings&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Managing the total retention per table is necessary whenever you want to keep logs longer than the 90-day default to satisfy logging or compliance requirements. Since there is no workspace-level default for total retention, it has to be configured table by table, a perfect fit for a declarative approach.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Identify tables with recent ingestion
 &lt;div id="identify-tables-with-recent-ingestion" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#identify-tables-with-recent-ingestion" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To identify &amp;lsquo;active&amp;rsquo; tables, both a KQL- and API-based approach exist.&lt;/p&gt;

&lt;h3 class="relative group"&gt;KQL
 &lt;div id="kql" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#kql" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;The following KQL query lists tables that have received data in the last 90 days along with their current tier, which is helpful when deciding what to put under Bicep management:&lt;/p&gt;</description></item><item><title>Optimising Microsoft Graph PowerShell scripts</title><link>https://tech.nicolonsky.ch/optimising-microsoft-graph-powershell-scripts/</link><pubDate>Wed, 22 Feb 2023 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/optimising-microsoft-graph-powershell-scripts/</guid><description>&lt;p&gt;We all have probably been there and developed a PowerShell script that took some fair amount of time until the execution completed, weren’t we? Of course one could argue and say that as long a script ‘works’ it is good enough but depending on the use case and environment a PowerShell script that runs 30 to 60 minutes exceeds the patience of most (IT) people and can also lead to increased costs. But what makes those kinds of scripts that awfully slow and can’t we just tweak them to run faster?&lt;/p&gt;
&lt;p&gt;The following examples and script will be related to PowerShell and the Microsoft Graph API but the mentioned approaches can be adapted for any kind of RESTful API and scripting language. For all interactions with the Graph API I use the &lt;a href="https://github.com/microsoftgraph/msgraph-sdk-powershell" target="_blank" rel="noreferrer"&gt;Microsoft Graph PowerShell SDK&lt;/a&gt; which is available as a PowerShell module from the PowerShell gallery.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Reasons why your script is slow
 &lt;div id="reasons-why-your-script-isslow" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#reasons-why-your-script-isslow" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;I would say I have already read and (tried to) understand hundreds of PowerShell scripts out there and I often notice the following flaws which lead to poor performance:&lt;/p&gt;

&lt;h4 class="relative group"&gt;&lt;strong&gt;Slow algorithms and wrong data structures&lt;/strong&gt;
 &lt;div id="slow-algorithms-and-wrong-data-structures" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#slow-algorithms-and-wrong-data-structures" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;Slow algorithms are extricably linked to the understanding of data structures when it comes to PowerShell scripting. Here the top two cases I often observe:&lt;/p&gt;</description></item><item><title>Migrating to the new Windows Store experience</title><link>https://tech.nicolonsky.ch/migrating-to-the-new-windows-store-experience/</link><pubDate>Mon, 30 Jan 2023 18:56:24 +0000</pubDate><guid>https://tech.nicolonsky.ch/migrating-to-the-new-windows-store-experience/</guid><description>&lt;p&gt;The Microsoft Store for Business will be discontinued mid 2023 and Intune recently introduced the new Windows Store experience backed by winget to distribute apps to your Intune managed endpoints.&lt;/p&gt;
&lt;p&gt;To simplify the migration to the new Windows Store experience I created a PowerShell Script that migrates all currently assigned Windows Store for Business apps to the new Windows Store experience.&lt;/p&gt;
&lt;p&gt;Kudos to &lt;a href="https://www.rozemuller.com/add-microsoft-store-app-with-icon-into-intune-automated/" target="_blank" rel="noreferrer"&gt;Sander Rozemuller&lt;/a&gt; for providing detailed instructions about creating winget apps as PowerShell code samples.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Challenges
 &lt;div id="challenges" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#challenges" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;While scripting and extracting the existing Windows Store for Business (WSfB) apps I encountered the following issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not all apps in WSfB have valid privacy and information URLs, therefore I added a check whether the URL starts with http(s).&lt;/li&gt;
&lt;li&gt;Some apps have characters present (äöüë….) that require UTF-8 encoding. So I explicitly set the HTTP &lt;em&gt;content-type&lt;/em&gt; header to UTF8.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Script prerequisites
 &lt;div id="script-prerequisites" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#script-prerequisites" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To run the script you need to have the Microsoft Graph PowerShell SDK modules installed on your machine. You can install them with the following command:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;Install-Module&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Devices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;CorporateManagement&lt;/span&gt; &lt;span class="n"&gt;-Scope&lt;/span&gt; &lt;span class="n"&gt;CurrentUser&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;From a permissions perspective you need an Azure AD Application Administrator for the initial OAuth permission consent and for regular execution the Intune Administrator role.&lt;/p&gt;</description></item><item><title>GitHub Actions with Entra Workload Identity Federation</title><link>https://tech.nicolonsky.ch/github-actions-entra-workload-identity-federation/</link><pubDate>Mon, 23 Jan 2023 17:03:47 +0000</pubDate><guid>https://tech.nicolonsky.ch/github-actions-entra-workload-identity-federation/</guid><description>&lt;p&gt;Workload Identity Federation (let’s just call this WIF) allows app principals not residing within Azure to request short lived access tokens. This removes the need of storing client secrets or certificates within GitHub as Action secrets. One drawback ist that currently only the Azure modules support the usage of WIF.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How it works
 &lt;div id="how-itworks" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-itworks" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;WIF relies on trust. This trust is directly configured on the Azure AD app registration and scoped to an individual GitHub repository and optionally fine grained by limiting the usage to single git refs, specifically branches and tags. By trusting GitHub as external identity provider (IdP), a GitHub Action can request an identity token from the GitHub IdP and exchange this one against an access token within Azure AD.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Entra Workload Identity Federation"
 src="https://cdn-images-1.medium.com/max/800/1*r7XrBMrGtQ1M-Kd0biGjVA.png"
 &gt;&lt;/figure&gt;
&lt;p&gt;A GitHub IdP issued token (decoded) contains the following information:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;jti&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1a9fe224-c936-46f6-a38b-3300e76251b0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sub&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;repo:nicolonsky/musical-octo-telegram:ref:refs/heads/main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;aud&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;api://AzureADTokenExchange&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;refs/heads/main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sha&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9f112c0b82547e35b10250d7f3165789bf97862f&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;repository&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nicolonsky/musical-octo-telegram&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;repository_owner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nicolonsky&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;repository_owner_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;32899754&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;run_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3986560796&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;run_number&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;run_attempt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;repository_visibility&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;private&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;repository_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;592303002&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;actor_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;32899754&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;actor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nicolonsky&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;workflow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.github/workflows/wif.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;head_ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;base_ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event_name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;push&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ref_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;branch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;workflow_ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nicolonsky/musical-octo-telegram/.github/workflows/wif.yaml@refs/heads/main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;workflow_sha&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9f112c0b82547e35b10250d7f3165789bf97862f&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;job_workflow_ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nicolonsky/musical-octo-telegram/.github/workflows/wif.yaml@refs/heads/main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;job_workflow_sha&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9f112c0b82547e35b10250d7f3165789bf97862f&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;iss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://token.actions.githubusercontent.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;nbf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1674486850&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;exp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1674487750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;iat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1674487450&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Did you recognize the matching &lt;code&gt;sub&lt;/code&gt; attribute within the GitHub issued token and the Azure AD configuration? For a successful authentication the:&lt;/p&gt;</description></item><item><title>Automatically sign your PowerShell scripts with GitHub actions</title><link>https://tech.nicolonsky.ch/github-actions-powershell-signing/</link><pubDate>Fri, 09 Jul 2021 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/github-actions-powershell-signing/</guid><description>&lt;p&gt;Based on one of my older posts about &lt;a href="https://tech.nicolonsky.ch/sign-powershell-az-devops" &gt;PowerShell script signing with Azure DevOps&lt;/a&gt; I recently implemented a PowerShell script signing workflow with GitHub actions I wanted to share with the community.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Prerequisites
 &lt;div id="prerequisites" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prerequisites" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;For this post the following prerequisites are required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code signing certificate in PFX format&lt;/li&gt;
&lt;li&gt;GitHub account&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Add variables to GitHub actions
 &lt;div id="add-variables-to-github-actions" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#add-variables-to-github-actions" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Because GitHub variables can only be of string content we need to get the contents of the pfx file as base64 encoded string:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$pfxCertFilePath = &amp;#34;~\Downloads\CodeSigningCertificate.pfx&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$pfxContent = Get-Content $pfxCertFilePath -Encoding Byte
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[System.Convert]::ToBase64String($pfxContent) | Set-Clipboard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;GitHub action variables
 &lt;div id="github-action-variables" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#github-action-variables" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Add two variables as actions secrets:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BASE64_PFX&lt;/code&gt;: Base 64 encoded string of the PFX (automatically copied into your clipboard with the above commands)
&lt;code&gt;PFX_PASSWORD&lt;/code&gt;: Password for the private key of the pfx file&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/32899754/120798485-b6791e80-c53d-11eb-851f-dcb27a08b567.png" target="_blank" rel="noreferrer"&gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt=""
 src="https://user-images.githubusercontent.com/32899754/120798485-b6791e80-c53d-11eb-851f-dcb27a08b567.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;GitHub action workflow
 &lt;div id="github-action-workflow" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#github-action-workflow" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Place the following workflow file within your git repository:
&lt;code&gt;.github/workflows/SignPowerShell.yaml&lt;/code&gt; whereas the name of the YAML file can be freely chosen.&lt;/p&gt;</description></item><item><title>Securely sending emails from PowerShell scripts with modern authentication enforced</title><link>https://tech.nicolonsky.ch/sending-emails-with-modern-auth/</link><pubDate>Fri, 19 Mar 2021 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/sending-emails-with-modern-auth/</guid><description>&lt;p&gt;The &lt;code&gt;Send-MailMessage&lt;/code&gt; cmdlet has been around for a couple of years and is mostly used to send email messages from PowerShell. But with the deprecation and security flaws of legacy authentication it&amp;rsquo;s time for a better option which actually supports modern authentication. For this purpose we can use the Microsoft Graph API and the Microsoft Graph PowerShell SDK. The best thing is that this solution works without any service account and does not need any exclusions from conditional access.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Microsoft Graph resource
 &lt;div id="microsoft-graph-resource" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#microsoft-graph-resource" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To send a mail we simply specify the user account from which we want to send the email:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;POST: https://graph.microsoft.com/v1.0/users/sean.connery@dev.nicolonsky.ch/sendMail&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Create an app registration
 &lt;div id="create-an-app-registration" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#create-an-app-registration" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Simply create a new app registration with the &lt;code&gt;Mail.Send&lt;/code&gt; permissions and use a certificate for the authentication.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="New App registration"
 src="https://tech.nicolonsky.ch/content/images/2021/03/App-Permissions.png"
 &gt;&lt;/figure&gt;
&lt;p&gt;We need to take additional steps to limit the permissions of the app registration. Otherwise the app can send mails on behalf of &lt;strong&gt;any user&lt;/strong&gt; in your tenant. To limit the permissions we leverage &lt;a href="https://docs.microsoft.com/en-us/powershell/module/exchange/new-applicationaccesspolicy?view=exchange-ps" target="_blank" rel="noreferrer"&gt;exchange application access policies&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Connect to Exchange Online with the ExchangeOnlineManagement PowerShell module
&lt;code&gt;Connect-ExchangeOnline&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Dealing with Intune OMA-URI encoding and applocker rules</title><link>https://tech.nicolonsky.ch/intune-oma-uri-encoding/</link><pubDate>Tue, 16 Feb 2021 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/intune-oma-uri-encoding/</guid><description>&lt;p&gt;While fine-tuning and adjusting applocker policies for co-managed Windows 10 clients I got really annoyed by special characters commonly used in the German/Swiss language. The Intune portal seemed to use different encoding and didn&amp;rsquo;t allow me to just copy/paste the currently deployed policy and extend it with a new rule. I needed to request the original file that was uploaded to the tenant in order to adjust the rule. Instead of just accepting this I decided that it is time for an easier approach which I will share with you.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The actual issue
 &lt;div id="the-actual-issue" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-actual-issue" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;After uploading the XML with the applocker policies (in my case EXE rules), special characters like &amp;lsquo;ö&amp;rsquo; or &amp;lsquo;ü&amp;rsquo; have a weird encoding displayed in the portal. The next person that wants to edit the policy needs to take care to fix the unrecognized characters otherwise the publisher rule won&amp;rsquo;t work anymore.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2021/02/Applocker-OMA-URI.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="OMA-URI"
 src="https://tech.nicolonsky.ch/content/images/2021/02/Applocker-OMA-URI.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Top: Portal view of the special characters, bottom: original file.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2021/02/Encoding-Issue.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Encoding issue"
 src="https://tech.nicolonsky.ch/content/images/2021/02/Encoding-Issue.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Fixing things
 &lt;div id="fixing-things" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fixing-things" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Save file with UTF-8 encoding
 &lt;div id="save-file-with-utf-8-encoding" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#save-file-with-utf-8-encoding" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;First, make sure that you saved an uploaded the file with UTF-8 encoding as this introduces support for most character sets. You can do this by selecting the encoding box in the right bottom of Visual Studio Code:&lt;/p&gt;</description></item><item><title>Microsoft Graph Access Token Acquisition with PowerShell explained in depth</title><link>https://tech.nicolonsky.ch/explaining-microsoft-graph-access-token-acquisition/</link><pubDate>Mon, 04 Jan 2021 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/explaining-microsoft-graph-access-token-acquisition/</guid><description>&lt;p&gt;When working with the Microsoft Graph API or introducing the API to colleagues I often get asked about the steps required to obtain an access token for the API with PowerShell. Out in the wild, I&amp;rsquo;ve spotted many different ways and lots of implementations still relying on the ADAL (Active Directory Authentication Library) despite the fact that this client library is superseded by MSAL (Microsoft Authentication Library). So let&amp;rsquo;s talk about acquiring access token &amp;ldquo;in stile&amp;rdquo; with the most simple method available.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why do we need an access token?
 &lt;div id="why-do-we-need-an-access-token" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-do-we-need-an-access-token" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;When talking about the Microsoft Graph API an access token fulfills two roles, first: prove authentication (proof of identity) second prove authorization (permissions). Each request needs to submit a request-header that contains the access token.&lt;/p&gt;
&lt;p&gt;For an API it&amp;rsquo;s crucial to validate the authentication and authorization for every request. Otherwise, requests could be made to resources the actor has no access to.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What&amp;rsquo;s inside the access token
 &lt;div id="whats-inside-the-access-token" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#whats-inside-the-access-token" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;You did probably stumble over the terms &amp;ldquo;bearer authentication&amp;rdquo; or &amp;ldquo;bearer token&amp;rdquo; these describe a mechanism within the OAuth 2.0 Authorization framework to authenticate requests with access tokens. Tokens are issued by the authorization server (Azure AD) and contain a server-generated string in the format of a JSON Web Token (JWT) with the following information (the list is not exhaustive and truncated to only contain the most interesting parts):&lt;/p&gt;</description></item><item><title>Housekeeping for stale MEM profiles</title><link>https://tech.nicolonsky.ch/cleanup-mem-profiles/</link><pubDate>Wed, 16 Dec 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/cleanup-mem-profiles/</guid><description>&lt;p&gt;When involved in new projects I often find a bunch of old profiles in the Microsoft Endpoint Management Console.
Before going ahead with a new implementation it&amp;rsquo;s the best time to clean-up all the leftovers from past ramblings.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How to identify stale profiles
 &lt;div id="how-to-identify-stale-profiles" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-to-identify-stale-profiles" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;If one or multiple statements are met for a profile it is very likely to be a stale profile:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No assignments, assignments to a group without members&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Test&amp;rdquo; included within the profile name or description&lt;/li&gt;
&lt;li&gt;Last modified points back in time for more than a year&lt;/li&gt;
&lt;li&gt;No devices reported success/failure status for the given profile type&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;What to do with stale profiles
 &lt;div id="what-to-do-with-stale-profiles" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-to-do-with-stale-profiles" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;So let&amp;rsquo;s be brave and &lt;strong&gt;delete&lt;/strong&gt; them. But Intune doesn&amp;rsquo;t offer any [CTRL] + [Z] or recycle bin possibilities so we might want to have some kind of archive, just in case?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s agree that we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check the points from the list above&lt;/li&gt;
&lt;li&gt;Ask our colleagues if they know something about the profiles and their usage&lt;/li&gt;
&lt;li&gt;Take a backup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;deleting them afterward is a reasonable action which is probably beneficial for everyone.&lt;/p&gt;</description></item><item><title>Export and import MEM Endpoint Security Profiles</title><link>https://tech.nicolonsky.ch/endpoint-security-profiles/</link><pubDate>Thu, 19 Nov 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/endpoint-security-profiles/</guid><description>&lt;p&gt;Recently I got a DM on Twitter with a question about how to export and import Endpoint Security profiles with Microsoft Graph. Besides a technical answer which might be of interest for you, I&amp;rsquo;d like to show you the workflow I used to give a proper reply.&lt;/p&gt;
&lt;p&gt;Original question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hi &lt;a href="https://twitter.com/nicolonsky" target="_blank" rel="noreferrer"&gt;@nicolonsky&lt;/a&gt;, I was advised on the MS Elite Partner focus groups team (MEM Automation) to reach out to you regarding my question about export/import policies from Endpoint Security in Intune. I&amp;rsquo;ve been able to export the Disk Encryption policy (via graph explorer), but haven&amp;rsquo;t been able to find the correct format to use to upload/import it. I was hoping that you would be able to advise on how to go about achieving this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Workflow
 &lt;div id="workflow" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#workflow" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Discover request URL&amp;rsquo;s and payload
 &lt;div id="discover-request-urls-and-payload" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#discover-request-urls-and-payload" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;To discover the request URLs and payloads I used the methodology I explained in the &lt;a href="https://tech.nicolonsky.ch/discover-mem-graph-urls/" &gt;this post&lt;/a&gt; a while ago.
Basically, I tracked the network activity and used a filter to only include requests made to the Graph API while doing the following activities:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/11/Examine-Request-Urls-And-Payload.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="examine microsoft graph request urls with your browsers dev tools"
 src="https://tech.nicolonsky.ch/content/images/2020/11/Examine-Request-Urls-And-Payload.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Build an Azure DevOps pipeline to automatically sign your PowerShell scripts</title><link>https://tech.nicolonsky.ch/sign-powershell-az-devops/</link><pubDate>Thu, 01 Oct 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/sign-powershell-az-devops/</guid><description>&lt;p&gt;Too lazy to sign your PowerShell scripts? Yes of course it provides security benefits but performing the steps manually can be easily forgotten and re-signing needs to happen after every script change. Because I like CI/CD topics and have not found a solution on the internet I decided to build a solution based on Azure capabilities. Furthermore, I wanted a solution which does not require to hand out the code signing certificate to the respective script author which can be useful if you have a bunch of people writing PowerShell scripts.&lt;/p&gt;
&lt;p&gt;From a personal perspective, I would also recommend signing scripts you hand over to customers to ensure the integrity of the scripts because as soon as the script gets changed the signature is invalid.&lt;/p&gt;
&lt;p&gt;You can find more general recommendations about script signing in the &lt;a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_signing" target="_blank" rel="noreferrer"&gt;PowerShell docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Solution overview
 &lt;div id="solution-overview" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#solution-overview" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The key vault will store the code signing certificate with an access policy that allows access from the Azure DevOps pipeline.&lt;/p&gt;
&lt;p&gt;The pipeline consists of the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Import code signing certificate
&lt;ul&gt;
&lt;li&gt;The certificate is supplied as secret in a variable group which is linked to the key vault&lt;/li&gt;
&lt;li&gt;Access to the key vault is granted with a service connection (service principal)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sign PowerShell scripts which contain a &amp;ldquo;magic token&amp;rdquo;
&lt;ul&gt;
&lt;li&gt;All &lt;code&gt;*.ps1&lt;/code&gt; within the attached repository will be enumerated&lt;/li&gt;
&lt;li&gt;To control which scripts will be signed only those with the &amp;ldquo;magic token&amp;rdquo; get processed&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;magic token&amp;rdquo; gets removed before signing the script&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Publish signed PowerShell scripts as pipeline artifacts
&lt;ul&gt;
&lt;li&gt;To make them available for download&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/10/PoshSigning_DevOps.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Solution overview"
 src="https://tech.nicolonsky.ch/content/images/2020/10/PoshSigning_DevOps.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Bulk create Intune mobile app deployment groups and assignments</title><link>https://tech.nicolonsky.ch/intune-mobile-app-assignment-bulk/</link><pubDate>Wed, 19 Aug 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/intune-mobile-app-assignment-bulk/</guid><description>&lt;p&gt;Creating assignments and software deployment groups for Intune mobile apps is quite a repetitive and manual task. Because of that, I want to share a PowerShell script with you which allows you to automatically create software deployment groups in Azure AD and the assignments for various intents.&lt;/p&gt;
&lt;p&gt;The script allows you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create Azure AD groups (install uninstall purpose)
&lt;ul&gt;
&lt;li&gt;Pick existing groups based on displayName&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Assign Intune mobile apps (tested for Win32 and MSI LOB apps)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/08/intune-mobile-app-assignment-bulk.gif" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Script in Action"
 src="https://tech.nicolonsky.ch/content/images/2020/08/intune-mobile-app-assignment-bulk.gif"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nicolonsky/Techblog/tree/master/IntuneMobileAppAssignment" target="_blank" rel="noreferrer"&gt;You can find the script on my techblog GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because of the &lt;a href="https://github.com/nicolonsky/Techblog/blob/master/IntuneMobileAppAssignment/New-IntuneAppAssignment.ps1#L220" target="_blank" rel="noreferrer"&gt;configurable group prefixes&lt;/a&gt; the script helps you to keep your Intune environment clean and implement a standard app assignment configuration.&lt;/p&gt;
&lt;p&gt;The script uses the Microsoft Graph API and the following resources&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://graph.microsoft.com/beta/deviceAppmanagement/mobileApps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://graph.microsoft.com/beta/deviceAppmanagement/mobileApps/{AppID}/Assignments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://graph.microsoft.com/beta/groups&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It uses the preregistered app &amp;ldquo;Microsoft Intune PowerShell&amp;rdquo; which exists by default in all tenants. If you want to run the Script with PowerShell 7 you need to create an adjust the MSAL token section with the &lt;code&gt;-DeviceCode&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;You can bulk select the apps you want to create the assignment and AAD deployment groups:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/08/intune-mobile-app-assignment-bulk-select.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="App assignment"
 src="https://tech.nicolonsky.ch/content/images/2020/08/intune-mobile-app-assignment-bulk-select.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hope this saves you some time.&lt;/p&gt;</description></item><item><title>Add PowerShell modules to Azure functions</title><link>https://tech.nicolonsky.ch/azure-functions-powershell-modules/</link><pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/azure-functions-powershell-modules/</guid><description>&lt;p&gt;Azure functions for PowerShell natively ship without additional cmdlets or PowerShell modules. In this post, I will show you how to add both public modules from the PowerShell gallery with automatic dependency management and custom modules.&lt;/p&gt;
&lt;p&gt;For both options, we use the Kudu tools to adjust the configuration of our function app. You can launch them from the &lt;em&gt;&amp;ldquo;Advanced Tools&amp;rdquo;&lt;/em&gt; section of your function app:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/08/add-powershell-modules-to-azure-functions-kudu.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Kudu tools"
 src="https://tech.nicolonsky.ch/content/images/2020/08/add-powershell-modules-to-azure-functions-kudu.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Afterwards, launch the PowerShell debug console and navigate to the &lt;code&gt;wwwroot&lt;/code&gt; folder of your app:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/08/add-powershell-modules-to-azure-functions-kudu.gif" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Kudu debug console"
 src="https://tech.nicolonsky.ch/content/images/2020/08/add-powershell-modules-to-azure-functions-kudu.gif"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Option 1: Automatic dependency management
 &lt;div id="option-1-automatic-dependency-management" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#option-1-automatic-dependency-management" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Installing modules which require license acceptance (e.g. the MSAL.PS module) currently cannot be installed with automatic dependency management.
You can track the issue status &lt;a href="https://github.com/Azure/azure-functions-powershell-worker/issues/417" target="_blank" rel="noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/MicrosoftDocs/azure-docs/issues/60701" target="_blank" rel="noreferrer"&gt;here&lt;/a&gt;.
{: .notice&amp;ndash;warning}&lt;/p&gt;
&lt;p&gt;Azure function apps running PowerShell come with a nice feature called managed dependencies. You can specify the modules you want to import from the PowerShell Gallery and the function app host will automatically process the dependencies.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;requirements.psd1&lt;/code&gt; add the module details:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# This file enables modules to be automatically managed by the Functions service.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# See https://aka.ms/functionsmanageddependency for additional information.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;# For latest supported version, go to &amp;#39;https://www.powershellgallery.com/packages/Az&amp;#39;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Az&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;4.*&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Microsoft.Graph.Authentication&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0.*&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can either specify an exact version available from the PowerShell Gallery or specify the major version with a wildcard. With the wildcard option it will use the latest version available.&lt;/p&gt;</description></item><item><title>Playing around with the Office 365 Service Communications API</title><link>https://tech.nicolonsky.ch/microsoft365-service-status/</link><pubDate>Mon, 10 Aug 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/microsoft365-service-status/</guid><description>&lt;p&gt;The Office 365 Service Communications API provides information about Microsoft 365 service status for your tenant including service messages. I built a little PowerShell module to access the API with PowerShell cmdlets. In this post I want to show you some examples which help you to use the API.&lt;/p&gt;

&lt;h2 class="relative group"&gt;PowerShell Module
 &lt;div id="powershell-module" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#powershell-module" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I built a PowerShell module to access Microsoft 365 service status details natively with PowerShell. The PowerShell module and documentation is available on the &lt;a href="https://www.powershellgallery.com/packages/Microsoft365ServiceStatus" target="_blank" rel="noreferrer"&gt;PowerShell Gallery&lt;/a&gt; and on &lt;a href="https://github.com/nicolonsky/Microsoft365ServiceStatus" target="_blank" rel="noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before using the module an app registration is required. Setup instructions are also provided on GitHub.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/32899754/89341042-fe6eb100-d6a0-11ea-80a7-e7357f9717c5.gif" target="_blank" rel="noreferrer"&gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Module"
 src="https://user-images.githubusercontent.com/32899754/89341042-fe6eb100-d6a0-11ea-80a7-e7357f9717c5.gif"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;CI/CD
 &lt;div id="cicd" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cicd" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;By leveraging Azure DevOps I created a build and release pipeline which automatically builds the PowerShell module with Plaster.&lt;/p&gt;
&lt;p&gt;Builds are only created if the commit on GitHub includes a version tag. This version tag gets automatically populated to the module manifest.&lt;/p&gt;
&lt;p&gt;The build artifact gets then automatically published to the PowerShell Gallery as a new version. Furthermore, a new GitHub release including the module artifact is added to the project.&lt;/p&gt;
&lt;p&gt;This process fully automates the publishing and build process for the module. For local development and maintenance, the module can also be built with &lt;a href="https://github.com/nightroman/Invoke-Build" target="_blank" rel="noreferrer"&gt;&lt;code&gt;Invoke-Build&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Azure AD guest user review solution</title><link>https://tech.nicolonsky.ch/azure-ad-guest-user-review-solution/</link><pubDate>Tue, 14 Jul 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/azure-ad-guest-user-review-solution/</guid><description>&lt;p&gt;Azure Active Directory guest users really simplify the process to collaborate with external users. Although keeping a good governance on guest accounts can become quite a challenge.
The two biggest challenges I often observe are: &lt;em&gt;&amp;ldquo;Who invited that guest user?&amp;rdquo;&lt;/em&gt; and &lt;em&gt;&amp;ldquo;Does this guest user still need access to our infrastructure?&amp;rdquo;&lt;/em&gt;. Inspired by a recent post of Thomas Kurth regarding &lt;a href="https://www.wpninjas.ch/2020/06/azure-ad-guest-account-governance-and-cleanup/" target="_blank" rel="noreferrer"&gt;Azure AD Guest Account - Governance and Cleanup&lt;/a&gt; I also developed a solution which comes quite close to an &amp;ldquo;Azure AD Access review&amp;rdquo; like user experience.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Notable features
 &lt;div id="notable-features" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#notable-features" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The &amp;lsquo;Manager&amp;rsquo; attribute of your guest users get&amp;rsquo;s automatically populated with the identity of the inviter&lt;/li&gt;
&lt;li&gt;All Azure AD app registration information is stored in Azure Key Vault&lt;/li&gt;
&lt;li&gt;Almost zero touch deployment with ARM templates&lt;/li&gt;
&lt;li&gt;You can integrate existing guest users into this solution by populating the manager attribute in Azure AD&lt;/li&gt;
&lt;li&gt;You can configure the approval frequency for guest accounts&lt;/li&gt;
&lt;li&gt;Approval frequency respects last approval date for each guest account&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Architecture
 &lt;div id="architecture" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#architecture" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://tech.nicolonsky.ch/content/images/2020/07/AzureADGuestReview-AzScheme.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Azure AD Guest User Review"
 src="https://tech.nicolonsky.ch/content/images/2020/07/AzureADGuestReview-AzScheme.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The solution leverages function of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Azure Logic App&lt;/p&gt;</description></item><item><title>10 suggestions to improve your next PowerShell script</title><link>https://tech.nicolonsky.ch/10-suggestions-to-improve-your-next-powershell-script/</link><pubDate>Wed, 08 Jul 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/10-suggestions-to-improve-your-next-powershell-script/</guid><description>&lt;p&gt;Most of the time PowerShell is my favourite choice to automate processes and tasks. In order to improve the maintainability of my scripts I usually try to focus on some standards combined with a clean scripting style. In this post I want to show you 10 suggestions to improve your next PowerShell script. I&amp;rsquo;ve tried to order the suggestions according to an actual PowerShell starting from the very first line till the last line.&lt;/p&gt;

&lt;h2 class="relative group"&gt;1. Script prerequisites
 &lt;div id="1-script-prerequisites" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-script-prerequisites" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Your PowerShell script might need specific modules or elevated user rights to run. The &lt;code&gt;#Requires&lt;/code&gt; statement ensures that these prerequisites are met before the actual script get&amp;rsquo;s executed. So you don&amp;rsquo;t need to implement your own checks to verify prerequisites.&lt;/p&gt;
&lt;p&gt;Simply use the &lt;code&gt;#Requires&lt;/code&gt; statement at the very first line of your script. &lt;a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_requires" target="_blank" rel="noreferrer"&gt;Find out more about #Requires statement&lt;/a&gt;.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Modules
 &lt;div id="modules" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#modules" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Another benefit of specifying the modules within the requires statement is that scripts hosted on the PowerShell Gallery automatically install the modules mentioned in the &lt;code&gt;#Requires&lt;/code&gt; list.&lt;/p&gt;
&lt;p&gt;To make sure a specific module is installed use:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="k"&gt;Requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;-module&lt;/span&gt;&lt;span class="na"&gt; &amp;#34;Microsoft.Graph.Intune&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To ensure a module with a specific version is available:&lt;/p&gt;</description></item><item><title>Remove Azure AD direct License Assignments with PowerShell</title><link>https://tech.nicolonsky.ch/remove-azure-ad-direct-license-assignments-with-powershell/</link><pubDate>Wed, 08 Jul 2020 00:00:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/remove-azure-ad-direct-license-assignments-with-powershell/</guid><description>&lt;p&gt;Who doesn&amp;rsquo;t love a clean and tidy environment, do you? This also applies for your license assignments in Office 365 and Azure AD. As time passess it is likely to have users with direct license assignments or users which still have old trial licenses assigned. To get rid of those assignments I created a PowerShell script with removal and reporting functionality.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nicolonsky/Techblog/tree/master/CleanupAzureADLicensing" target="_blank" rel="noreferrer"&gt;Direct link to the script&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Identify direct license assignments
 &lt;div id="identify-direct-license-assignments" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#identify-direct-license-assignments" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;In the Azure Portal we recognize direct license assignments on a user account by viewing the &amp;ldquo;Assignment Paths&amp;rdquo;:
&lt;a href="https://tech.nicolonsky.ch/content/images/2020/07/Remove-Azure-AD-direct-License-Assignments-with-PowerShell-Portal-View.png" &gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Azure AD Direct License Assignment Portal View"
 src="https://tech.nicolonsky.ch/content/images/2020/07/Remove-Azure-AD-direct-License-Assignments-with-PowerShell-Portal-View.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With the MSOnline PowerShell module we can view the &lt;code&gt;Licenses&lt;/code&gt; property of a user and retrieve a nested property called: &lt;code&gt;GroupsAssigningLicense&lt;/code&gt;. The &lt;code&gt;GroupsAssigningLicense&lt;/code&gt; property contains either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An empty array if the license was not inherited from a group -&amp;gt; direct assignment&lt;/li&gt;
&lt;li&gt;An array with objectId&amp;rsquo;s
&lt;ul&gt;
&lt;li&gt;If the array contains the user&amp;rsquo;s objectId -&amp;gt; direct assignment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example 1: User with objectId &lt;code&gt;36c9b091-fe88-4dc2-a9e1-2662020b4bab&lt;/code&gt; has group based license assignment and direct assignment:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AccountSkuId : nicolasuter:SPE_E5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GroupsAssigningLicense : {0a918505-d0d5-4078-9891-0e8bec67cb65, 36c9b091-fe88-4dc2-a9e1-2662020b4bab}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Example 2: User has no inherited licenses from a group:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AccountSkuId : nicolasuter:SPE_E5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GroupsAssigningLicense : {}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;PowerShell Script
 &lt;div id="powershell-script" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#powershell-script" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;You find the PowerShell script &lt;a href="https://github.com/nicolonsky/Techblog/tree/master/CleanupAzureADLicensing" target="_blank" rel="noreferrer"&gt;on my techblog GitHub repository&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Document Conditional Access Configuration with my Modern Workplace Concierge</title><link>https://tech.nicolonsky.ch/document-conditional-access-configuration/</link><pubDate>Mon, 20 Apr 2020 19:22:33 +0000</pubDate><guid>https://tech.nicolonsky.ch/document-conditional-access-configuration/</guid><description>&lt;p&gt;Documenting things sucks. If it involves a lot of klick(edi klack klack) in portals and copying information around even more. But there&amp;rsquo;s hope. And it&amp;rsquo;s called automation. For the Intune part Thomas Kurt did already an awesome job with his &lt;a href="https://github.com/ThomasKur/IntuneDocumentation" target="_blank" rel="noreferrer"&gt;IntuneDocumentation&lt;/a&gt;.  Now the &lt;a href="https://mwconcierge.azurewebsites.net/" target="_blank" rel="noreferrer"&gt;Modern Workplace Concierge&lt;/a&gt; is ready to help you with documenting your Conditional Access configuration. I promise you: we will get through this within under 15 minutes! Afterwards you can make an impression on your fellow Enterprise Mobility teammates.&lt;/p&gt;

&lt;h3 class="relative group"&gt;What&amp;rsquo;s inside?
 &lt;div id="whats-inside" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#whats-inside" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;A Conditional Access policy is returned by the Microsoft Graph API in the following JSON representation:&lt;/p&gt;
&lt;pre&gt;
&lt;code&gt;
{
 "id": "714b5737-5f13-415e-bf96-d659f3a5928e",
 "displayName": "PROD - Admin protection - Azure management: Require MFA",
 "createdDateTime": null,
 "modifiedDateTime": null,
 "state": "enabled",
 "grantControls": {
 "operator": "OR",
 "builtInControls": [
 "mfa"
 ],
 "customAuthenticationFactors": [],
 "termsOfUse": []
 },
 "conditions": {
 "signInRiskLevels": [],
 "clientAppTypes": [],
 "platforms": null,
 "locations": null,
 "deviceStates": null,
 "applications": {
 "includeApplications": [
 "797f4846-ba00-4fd7-ba43-dac1f8f63013"
 ],
 "excludeApplications": [],
 "includeUserActions": []
 },
 "users": {
 "includeUsers": [
 "All"
 ],
 "excludeUsers": [],
 "includeGroups": [],
 "excludeGroups": [
 "04988d96-ad01-4569-9aee-a199a1cb4f8e"
 ],
 "includeRoles": [],
 "excludeRoles": []
 }
 },
 "sessionControls": null
}
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;That&amp;rsquo;s not really human readable. Especially the object id&amp;rsquo;s  (32 character UUIDs) make it difficult to guess to which users or apps a policy is assigned. But an API has definitely other goals than showing pretty formatted reports.&lt;/p&gt;</description></item><item><title>Export and import Intune and Conditional Access configuration</title><link>https://tech.nicolonsky.ch/export-and-import-intune-and-conditional-access-configuration/</link><pubDate>Tue, 03 Dec 2019 07:28:18 +0000</pubDate><guid>https://tech.nicolonsky.ch/export-and-import-intune-and-conditional-access-configuration/</guid><description>&lt;p&gt;With Microsoft Graph we have powerful automation and configuration management capabilities. To further simplify this process I built the &amp;ldquo;&lt;a href="https://mwconcierge.azurewebsites.net/" target="_blank" rel="noreferrer"&gt;Modern Workplace Concierge&amp;rdquo;&lt;/a&gt;.  It is an ASP.NET application which uses an Azure AD multi tenant app to access the Microsoft Graph API on behalf to perform export and import tasks. The project uses the Microsoft Graph Beta API to access your tenant&amp;rsquo;s data.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Modern Workplace Concierge
 &lt;div id="modern-workplace-concierge" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#modern-workplace-concierge" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://mwconcierge.azurewebsites.net/" target="_blank" rel="noreferrer"&gt;Modern Workplace Concierge&lt;/a&gt; allows you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Import and export Intune configuration and settings&lt;/li&gt;
&lt;li&gt;Import and export Conditional Access policies&lt;/li&gt;
&lt;li&gt;Download OSD ready offline Autopilot profiles&lt;/li&gt;
&lt;li&gt;Download stored PowerShell scripts in Intune (as PowerShell)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This allows you to import your existing Intune and Conditional Access configuration in new tenants or demo tenants. The files in JSON format can be used for further processing or documentation.&lt;/p&gt;
&lt;p&gt;And this all via web browser no client side prerequisites or PowerShell code is required!&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://tech.nicolonsky.ch/content/images/2019/12/MwConcierge.png" class="kg-image"&gt;&lt;figcaption&gt;The Modern Workplace Concierge&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The project and more information is &lt;a href="https://github.com/nicolonsky/ModernWorkplaceConcierge" target="_blank" rel="noreferrer"&gt;available on GitHub&lt;/a&gt; feel free to provide feedback there.&lt;/p&gt;
&lt;figure class="kg-card kg-embed-card"&gt;&lt;blockquote class="twitter-tweet"&gt;
&lt;p lang="en" dir="ltr"&gt;That's how I backup my Intune configuration with the &lt;a href="https://twitter.com/hashtag/ModernWorkplaceConcierge?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#ModernWorkplaceConcierge&lt;/a&gt;. Curious? Give it a try. Of course also supporting imports .&lt;a href="https://t.co/30H8b0olLn"&gt;https://t.co/30H8b0olLn&lt;/a&gt; &lt;a href="https://t.co/g5X58l1e1i"&gt;pic.twitter.com/g5X58l1e1i&lt;/a&gt;&lt;/p&gt;— Nicola Suter (@nicolonsky) &lt;a href="https://twitter.com/nicolonsky/status/1201745635545952256?ref_src=twsrc%5Etfw"&gt;December 3, 2019&lt;/a&gt;
&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;/figure&gt;
&lt;p&gt;More Information:&lt;/p&gt;</description></item><item><title>Automating network drive mapping configuration with Intune</title><link>https://tech.nicolonsky.ch/next-level-network-drive-mapping-with-intune/</link><pubDate>Fri, 19 Jul 2019 07:32:46 +0000</pubDate><guid>https://tech.nicolonsky.ch/next-level-network-drive-mapping-with-intune/</guid><description>&lt;p&gt;I&amp;rsquo;m thrilled to introduce the &lt;a href="https://intunedrivemapping.azurewebsites.net/DriveMapping" target="_blank" rel="noreferrer"&gt;intune-drive-mapping-generator&lt;/a&gt; which creates PowerShell scripts to map network drives with Intune. The tool is open source and built on ASP.NET Core MVC.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://intunedrivemapping.azurewebsites.net/DriveMapping" target="_blank" rel="noreferrer"&gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="intune-drivemapping-generator"
 src="https://tech.nicolonsky.ch/content/images/2019/07/intune-drivemapping-generator.png"
 &gt;&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The intune-drive-mapping-generator is your tool of choice to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate an Intune PowerShell script to map network drives on Azure AD joined devices&lt;/li&gt;
&lt;li&gt;Seamlessly migrate existing network drive mapping group policies&lt;/li&gt;
&lt;li&gt;Generate a network drive mapping configuration from scratch&lt;/li&gt;
&lt;li&gt;Use an existing Active Directory group as a filter to deploy all your drive mapping configurations within one script&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;This all happens without scripting effort. You receive a fully functional PowerShell script for the deployment with Intune.&lt;/em&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Architecture
 &lt;div id="architecture" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#architecture" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This tool is designed to work best with the following components although it can be useful for other purposes(?) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Azure AD Joined and Intune enrolled Windows 10 devices&lt;/li&gt;
&lt;li&gt;Synced user account from Active Directory to Azure Active Directory (Azure AD Connect)&lt;/li&gt;
&lt;li&gt;On-premises file servers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Howto
 &lt;div id="howto" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#howto" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Export existing group policy
 &lt;div id="export-existing-group-policy" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#export-existing-group-policy" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;To convert your existing drive mapping group policy configuration, save the GPO as XML report with the group policy management console.&lt;/p&gt;</description></item><item><title>Clean up stale Azure AD devices</title><link>https://tech.nicolonsky.ch/clean-up-azure-ad-devices/</link><pubDate>Thu, 10 Jan 2019 22:25:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/clean-up-azure-ad-devices/</guid><description>&lt;p&gt;If you are using Azure AD and the time passes you&amp;rsquo;ll have a lot of old device entries. If you enable the automatic device cleanup rule in Microsoft Intune the device is only removed within MDM and the Azure AD entry still exists.&lt;/p&gt;
&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img src="https://tech.nicolonsky.ch/content/images/2019/01/image-1.png" class="kg-image"&gt;&lt;figcaption&gt;Intune device cleanup rule&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;For this reason I created a tiny PowerShell snippet to create a report with all devices which didn&amp;rsquo;t contact your Azure AD tenant since the treshold date specified. If you confirm the operation you can also delete all affected devices.&lt;/p&gt;
&lt;!--kg-card-begin: markdown--&gt;
&lt;p&gt;&lt;mark&gt;&lt;em&gt;Please be careful when running the script because when removing a device from Azure AD the stored Bitlocker recovery keys are also removed. I can recommend &lt;a href="http://rzander.azurewebsites.net/bitlocker-management-with-azure-storage-table/"&gt;Roger Zander&amp;rsquo;s Azure table-based Bitlocker recovery key solution&lt;/a&gt;.&lt;/em&gt;&lt;/mark&gt;&lt;/p&gt;
&lt;!--kg-card-end: markdown--&gt;&lt;!--kg-card-begin: markdown--&gt;&lt;script src="https://gist.github.com/nicolonsky/231844d2c383396331a94024bffbd7ff.js"&gt; &lt;/script&gt;&lt;!--kg-card-end: markdown--&gt;</description></item><item><title>Set Office 365 UsageLocation property with Azure automation</title><link>https://tech.nicolonsky.ch/office-usage-location-azure-automation/</link><pubDate>Wed, 09 Jan 2019 15:23:00 +0000</pubDate><guid>https://tech.nicolonsky.ch/office-usage-location-azure-automation/</guid><description>&lt;p&gt;If you want to assign Microsoft licenses to your Azure AD users e.g. Microsoft 365 E3 licenses you can do this with group based licensing as described &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-licensing-whatis-azure-portal" target="_blank" rel="noreferrer"&gt;here&lt;/a&gt;. &lt;del&gt;The problem is that even with group based licensing the UsageLocation property for each user must be set individually.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;mark&gt;Update: 13.01.2019: Since group based licensing is GA the tenant location is used if no UsageLocation is set on a user object. Use this guide if you want to manually assign licenses or override the tenant settings if you need to configure different UsageLocations.&lt;/mark&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Possible bulk and automation solutions
 &lt;div id="possible-bulk-and-automation-solutions" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#possible-bulk-and-automation-solutions" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;You can achieve this with the following options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Manual&amp;rdquo; trough Azure or Office 365 portal&lt;/li&gt;
&lt;li&gt;PowerShell (must be triggered manually or through scheduled task)&lt;/li&gt;
&lt;li&gt;Azure AD Connect synchronisation (UsageLocation populated in on prem AD)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure automation with PowerShell runbook as in this post 🙂&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Azure automation sounds expensive?
 &lt;div id="azure-automation-sounds-expensive" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#azure-automation-sounds-expensive" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Fortunately Azure automation offers 500 minutes of script runtime for free. Find more details under Automation pricing. Just to give you an idea: If the executed script has an average runtime of 1 minute you could run it (500 minutes / (30 calendear days / 1 minute script runtime)) = 16x per day. Each month. For free.&lt;/p&gt;</description></item><item><title>PowerShell Script Test Open TCP Ports</title><link>https://tech.nicolonsky.ch/power-shell-script-test-open-tcp-ports/</link><pubDate>Wed, 18 Oct 2017 13:28:12 +0000</pubDate><guid>https://tech.nicolonsky.ch/power-shell-script-test-open-tcp-ports/</guid><description>&lt;p&gt;Recently I was troubleshooting ADFS connection issues when I discovered a nice little Cmdlet called &amp;ldquo;Test-NetConnection&amp;rdquo;. With this Cmdelet you can verify TCP connectivity, in my case from a client to the ADFS server.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The &lt;strong&gt;Test-NetConnection&lt;/strong&gt; cmdlet displays diagnostic information for a connection. It supports ping test, TCP test, route tracing, and route selection diagnostics. Depending on the input parameters, the output can include the DNS lookup results, a list of IP interfaces, IPsec rules, route/source address selection results, and/or confirmation of connection establishment.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Find a full documentation on the &lt;a href="https://docs.microsoft.com/en-us/powershell/module/nettcpip/test-netconnection?view=win10-ps" target="_blank" rel="noreferrer"&gt;Microsoft Docs Page&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;About the script
 &lt;div id="about-the-script" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#about-the-script" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;With this Script you are able to specify server names and port numbers to check in a CSV File. The Script generates an CSV output file as a report. You can use this script for troubleshooting or engineering purposes to verify if TCP ports are opened.&lt;/p&gt;
&lt;p&gt;Simply add the hostname and TCP port to the &amp;ldquo;CheckList.csv&amp;rdquo; and the script checks the specified servers and ports.&lt;/p&gt;
&lt;p&gt;The script will generate an output file for the same path containing the suffix &amp;ldquo;Report_&amp;rdquo; with the test results.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CheckList.csv: &lt;br&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="CheckList.csv"
 src="https://tech.nicolonsky.ch/content/images//2017/10/2017-10-18_1242.png"
 &gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Report_CheckList.csv generated after script execution:
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Report_CheckList.csv"
 src="https://tech.nicolonsky.ch/content/images//2017/10/2017-10-18_1252.png"
 &gt;&lt;/figure&gt;
&lt;/p&gt;</description></item><item><title>PowerShell Function Validate Object Properties Using ValidateScript</title><link>https://tech.nicolonsky.ch/power-shell-function-validate-object-properties/</link><pubDate>Thu, 12 Oct 2017 10:25:09 +0000</pubDate><guid>https://tech.nicolonsky.ch/power-shell-function-validate-object-properties/</guid><description>&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Recently I was working on a PowerShell script with many custom functions. When I started to use PowerShell custom objects I wanted to be able to pass them to a function. So I faced the challenge of validating my object for all required properties and came up with this solution, using the ValidateScript block to test the object:&lt;/p&gt;
&lt;script src="https://gist.github.com/nicolonsky/9c09096dd91ceb7a8fa43ab5cf4c3740.js"&gt;&lt;/script&gt;

&lt;h4 class="relative group"&gt;Customizing the ValidateScript
 &lt;div id="customizing-the-validatescript" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#customizing-the-validatescript" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;p&gt;As you can see I use a ValidateScript for the parameter validation to test the object for the required properties. The properties can be specified in an array:
&lt;code&gt;$requiredProperties=@(&amp;quot;Property1&amp;quot;,&amp;quot;Property2&amp;quot;,&amp;quot;Property3&amp;quot;, &amp;quot;Property4&amp;quot;)&lt;/code&gt;
When we call the Function with an appropriate object:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;PSCUSTOMOBJECT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property3&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property4&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We receive the following output:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;PS &lt;/span&gt;&lt;span class="n"&gt;H:&lt;/span&gt;&lt;span class="p"&gt;\&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;New-Example&lt;/span&gt; &lt;span class="n"&gt;-InputObject&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Succesfully&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="nb"&gt;ValidateScript&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Result
 &lt;div id="result" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#result" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;If we remove one or more properties from our custom object, an error is thrown:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;PS &lt;/span&gt;&lt;span class="n"&gt;H:&lt;/span&gt;&lt;span class="p"&gt;\&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;PSCUSTOMOBJECT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;property3&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;New-Example&lt;/span&gt; &lt;span class="n"&gt;-InputObject&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;New-Example&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Cannot&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="nb"&gt;parameter&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;InputObject&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Property4&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="n"&gt;At&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;New-Example&lt;/span&gt; &lt;span class="n"&gt;-InputObject&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="p"&gt;~~~~~~~&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CategoryInfo&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InvalidData&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;New-Example&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ParameterBindingValidationException&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;FullyQualifiedErrorId&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParameterArgumentValidationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;New-Example&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you want to go a step further you could extend the ValidateScript to&amp;hellip;&lt;/p&gt;

&lt;h3 class="relative group"&gt;Prevent passing properties with a NULL or empty value
 &lt;div id="prevent-passing-properties-with-a-null-or-empty-value" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#prevent-passing-properties-with-a-null-or-empty-value" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;PSObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;ForEach-Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="no"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;))){&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;Throw&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;System.Management.Automation.ValidationMetadataException&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Property &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; has either a NULL or empty value&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If we call our function again with the added IsNullOrEmpty validation NULL or emtpy values throw an exception:&lt;/p&gt;</description></item></channel></rss>