<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Luise Freese: Azure &amp; Power Platform Architect</title><link>https://m365princess.com/</link><description>Recent content on Luise Freese: Azure &amp; Power Platform Architect</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Copyright &amp;copy;2021-2025 | All photos of Luise taken by &lt;a href='https://www.businessfotos-mannheim.de/' target='_blank'>Sylviane Brauer&lt;/a></copyright><lastBuildDate>Sun, 14 Dec 2025 11:58:23 +0000</lastBuildDate><atom:link href="https://m365princess.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Codemotion 2025</title><link>https://m365princess.com/speaking-gigs/codemotionmilan2025/</link><pubDate>Tue, 22 Jul 2025 07:11:58 +0000</pubDate><guid>https://m365princess.com/speaking-gigs/codemotionmilan2025/</guid><description>&lt;h2 id="codemotion-2025">Codemotion 2025&lt;/h2>
&lt;p>The ultimate event for developers, tech leaders and industry changers&lt;/p>
&lt;h2 id="your-testing-strategy-is-broken---lets-fix-it">Your Testing Strategy is broken - lets fix it!&lt;/h2>
&lt;p>Testing is essential for delivering reliable software, but too often, teams fall into the trap of testing everything and testing it poorly. Endless unit tests for trivial code create a false sense of security while ignoring the areas that matter most; like how your system behaves when it really counts. This session takes a hard look at what makes a good testing strategy. Instead of chasing meaningless metrics like 100% coverage, learn how to test behaviors, focus on integration points, and embrace a leaner, smarter approach to quality assurance. Your users don’t care about how many tests you’ve written, they care about software that works. This is your guide to writing fewer tests, but better ones, that actually make a difference.&lt;/p>
&lt;h2 id="get-your-tix">get your tix!&lt;/h2>
&lt;p>&lt;a href="https://conferences.codemotion.com/milan2025/?_gl=1%2a1ug7nmn%2a_up%2aMQ..%2a_gs%2aMQ..&amp;gclid=CjwKCAjw7fzDBhA7EiwAOqJkhwA1xR2DobGrKjPKAoZOCF89-UnKNknfIIuU4fOtxk2CWsLYgP_XshoCGlAQAvD_BwE&amp;gbraid=0AAAAADiasmn3fVLnfXJO6fH-KBmRxaV-D">Tickets&lt;/a>&lt;/p></description></item><item><title>Baltic Summit</title><link>https://m365princess.com/speaking-gigs/balticsummit/</link><pubDate>Tue, 22 Jul 2025 07:20:11 +0000</pubDate><guid>https://m365princess.com/speaking-gigs/balticsummit/</guid><description>&lt;p>Talk to be announced soon - with &lt;a href="https://www.linkedin.com/in/julian-kusenberg-78673120a/">Julian Kusenberg&lt;/a> (SoftwareOne)&lt;/p>
&lt;h2 id="register">Register&lt;/h2>
&lt;p>&lt;a href="https://balticsummit.pl/">Get your ticket here&lt;/a>&lt;/p></description></item><item><title>ESPC 2025</title><link>https://m365princess.com/speaking-gigs/espc25/</link><pubDate>Tue, 22 Jul 2025 07:15:22 +0000</pubDate><guid>https://m365princess.com/speaking-gigs/espc25/</guid><description>&lt;h2 id="full-day-tutorial-getting-started-with-power-platform--ai-on-sharepoint">Full Day Tutorial: Getting Started with Power Platform &amp;amp; AI on SharePoint&lt;/h2>
&lt;p>Together with &lt;a href="https://www.linkedin.com/in/robinrosengruen/">Robin Rosengruen&lt;/a> (Microsoft)&lt;/p>
&lt;p>SharePoint is the backbone of collaboration in Microsoft 365, and with the Power Platform, you can take it to the next level: Build custom apps and automate processes to make work easier.&lt;/p>
&lt;p>In this tutorial Luise and Robin will walk you through the essentials of Power Apps Canvas Apps and Power Automate Cloud Flows, using SharePoint lists and libraries as foundation.&lt;/p>
&lt;p>But we won’t stop there! To give you a glimpse of what’s next, we’ll introduce AI Builder and Copilot Studio, showing how AI can help automate repetitive tasks and create chat-based experiences.&lt;/p>
&lt;p>Key Highlights&lt;/p>
&lt;ul>
&lt;li>Build a Power App from scratch based on SharePoint lists&lt;/li>
&lt;li>Automate a real business process with Power Automate&lt;/li>
&lt;li>Get a taste of AI with AI Builder and Copilot Studio&lt;/li>
&lt;/ul>
&lt;h2 id="session-building-smart-agents-with-azure-ai-foundry">Session: Building smart agents with Azure AI Foundry&lt;/h2>
&lt;p>Together with Fabian Moritz (Experts Inside)&lt;/p>
&lt;p>AI solutions often rely on OpenAI models but what if you need more flexibility? Different models custom options and seamless integration: This is where Azure AI Foundry comes in. In this session Luise and Fabian will show you how to build smart AI agents that go beyond a single model giving you more control and customization. You’ll see how to create and deploy AI solutions with Azure AI Foundry and even integrate them with Copilot to bring your use case to life.&lt;/p>
&lt;p>3 key-highlights:&lt;/p>
&lt;ul>
&lt;li>Learn how Azure AI Foundry enables flexibility by integrating multiple AI models beyond OpenAI&lt;/li>
&lt;li>See how to build fine-tune and deploy smart AI agents tailored to your needs&lt;/li>
&lt;li>Discover how to connect Azure AI Foundry with Copilot to bring AI solutions to life&lt;/li>
&lt;/ul>
&lt;h2 id="tickets">Tickets&lt;/h2>
&lt;p>✨ You can get a 15% discount code using &lt;strong>ESPC-PRINCESS&lt;/strong> at checkout!&lt;/p>
&lt;p>&lt;a href="https://www.sharepointeurope.com/pricing/">get your tickets here&lt;/a>&lt;/p></description></item><item><title>Stop using GitHub Copilot as a chatbot!</title><link>https://m365princess.com/blogs/lego-copilot-wrong1/</link><pubDate>Sun, 14 Dec 2025 11:58:23 +0000</pubDate><guid>https://m365princess.com/blogs/lego-copilot-wrong1/</guid><description>&lt;p>&lt;a href="https://m365princess.com/blogs/lego-copilot-wrong1">Part 0&lt;/a> showed why constant prompting, re-prompting, and steering GitHub Copilot feels fragile. Not because Copilot is unreliable, but because nothing in the interaction is stable. Every follow-up reshapes the problem again, and the system has no way to know what must remain invariant.&lt;/p>
&lt;p>This post answers the only question that matters after that: what exactly do you need to change, how do you do it in practice, and why does this suddenly make Copilot behave like a serious tool instead of a slot machine.&lt;/p>
&lt;p>The core shift is simple, but uncomfortable. You stop putting decisions into chat, and you start putting them into files.&lt;/p>
&lt;h2 id="what-you-are-actually-changing">What you are actually changing&lt;/h2>
&lt;p>In most Copilot workflows today, decisions live in the worst possible places. Some are in your head. Some are half-expressed in a chat message you typed while thinking. Some are implicit corrections you made three prompts ago. Copilot only ever sees fragments of these decisions, and those fragmets keep changing.&lt;/p>
&lt;p>The fix is not better wording or a more advanced model, but relocating decisions into stable artifacts that survive longer than a single interaction.&lt;/p>
&lt;h3 id="the-first-file-you-create-rrules-that-never-change">The first file you create: rRules that never change&lt;/h3>
&lt;p>Before you generate anything, create a place in the repository where global rules live. This is not documentation for you or your team mates, but the constraint system for Copilot.&lt;/p>
&lt;p>Create this folder if it does not exist:&lt;/p>
&lt;p>&lt;code>.github/instructions/&lt;/code>&lt;/p>
&lt;p>Inside it, create a file called:&lt;/p>
&lt;p>&lt;code>global.md&lt;/code>&lt;/p>
&lt;p>This file defines what must always be true, regardless of task.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-md" data-lang="md">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh"># Global development rules
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">These rules apply to all code generated in this repository.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Invariants
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Existing public APIs must not change.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Naming conventions must be preserved.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Errors must be handled explicitly.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No commented-out code is allowed.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Architecture
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Reuse existing patterns.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No new abstractions without need.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Async APIs only.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Quality bar
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Readable over clever.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No dead code.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No speculative features.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Testing
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> New logic requires tests.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Tests must be deterministic.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nothing in this file is negotiable during generation. This single file already removes a surprising amount of randomness.&lt;/p>
&lt;h3 id="the-second-file-you-create-the-task-frozen-in-time">The second file you create: The task, frozen in time&lt;/h3>
&lt;p>Now you stop starting work in the chat. Before you ask Copilot to generate anything, you write down the task in a dedicated file. Not while thinking, not mid-conversation. Create this folder:&lt;/p>
&lt;p>&lt;code>.github/specs/&lt;/code>&lt;/p>
&lt;p>For each non-trivial task, create one spec file. For example:&lt;/p>
&lt;p>&lt;code>user-export.md&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&lt;span class="gh"># User export feature
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Intent
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Allow administrators to export users as CSV.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Scope
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Backend logic only. No UI changes.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Must do
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> The export includes id, name, and email.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Large datasets are handled through pagination.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Must not do
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No UI changes.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No new dependencies.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Constraints
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Follow existing repository patterns.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Async APIs only.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Definition of done
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> The code builds successfully.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Tests are included.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> All global rules are respected.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once generation starts, this file is frozen. If something is wrong, you edit the spec and run again. You never “fix it in chat”. This is where most people feel friction the first time. That friction is the old habit dying.&lt;/p>
&lt;h2 id="how-you-now-use-copilot-exactly">How you now use Copilot (exactly)&lt;/h2>
&lt;p>Only after those two files exist you do open Copilot chat. You do not paste the spec. You do not explain the task again. You do not add &amp;ldquo;clarifications&amp;rdquo;, but write exactly this:&lt;/p>
&lt;pre tabindex="0">&lt;code>Implement the feature described in
.github/specs/user-export.md
following all rules in .github/instructions/global.md.
&lt;/code>&lt;/pre>&lt;p>Then you stop typing. If the result is wrong, the fix is not another prompt. The fix is correcting &lt;code>global.md&lt;/code> or &lt;code>user-export.md&lt;/code>.&lt;/p>
&lt;p>This is the first moment where Copilot stops needing to be babysitted.&lt;/p>
&lt;h2 id="why-this-already-changes-behavior">Why this already changes behavior&lt;/h2>
&lt;p>Copilot is extremely good at executing within boundaries and extremely bad at inferring them. When rules only exist in chat, they decay inevitably. When tasks only exist in conversation, they drift. When “done” only exists in your head, supervision becomes mandatory.&lt;/p>
&lt;p>By moving rules and tasks into files, you remove ambiguity from the context window. Copilot no longer has to decide which instruction still matters. It can see, clearly and consistently, what must hold and by that you won&amp;rsquo;t need to re-prompt over and over again.&lt;/p>
&lt;p>&lt;img alt="two buttons meme" src="https://m365princess.com/images/two-buttons-meme.png">&lt;/p>
&lt;h2 id="extending-the-same-idea-to-review">Extending the same idea to review&lt;/h2>
&lt;p>At this point, code-generation is stable, but review is still manual. That is the next bottleneck we need to tackle.&lt;/p>
&lt;p>Review has rules and those rules already exist in your standards and architecture; they just are not written down. So let&amp;rsquo;s codify them:&lt;/p>
&lt;p>Create a review spec:&lt;/p>
&lt;p>&lt;code>.github/specs/review-backend.md&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&lt;span class="gh"># Backend review
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Goal
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluate generated code against global rules and the feature spec.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Must verify
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No breaking API changes.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Error handling is explicit.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Async patterns are consistent.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Architecture is respected.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Must report
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Any deviation from the spec.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Any assumptions made.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Any potential risks.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Output format
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Short summary.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Explicit list of deviations.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Explicit list of risks.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Run review in a separate Copilot interaction, with fresh context:&lt;/p>
&lt;pre tabindex="0">&lt;code>Review the generated code using
.github/specs/review-backend.md.
&lt;/code>&lt;/pre>&lt;p>Copilot is dramatically more honest when it is not reviewing its own output in the same session but to decide whether deviations are acceptable.&lt;/p>
&lt;h2 id="refactoring-becomes-routine">Refactoring becomes routine&lt;/h2>
&lt;p>Once rules, specs, and review criteria exist, refactoring stops being dangerous. Create a refactor spec:&lt;/p>
&lt;p>&lt;code>.github/specs/refactor.md&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-md" data-lang="md">&lt;span class="line">&lt;span class="cl">&lt;span class="gh"># Refactoring task
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Intent
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Improve internal structure without changing behavior.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Invariants
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> External APIs must not change.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Observable behavior must remain identical.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Constraints
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No new features.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> No performance regressions.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## Definition of done
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Existing tests still pass.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">*&lt;/span> Review spec reports no new deviations.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As a result: Copilot refactors and reviews, you approve exceptions.&lt;/p>
&lt;h2 id="how-parallelism-finally-becomes-real">How parallelism finally becomes real&lt;/h2>
&lt;p>Because work is fully specified in files, it no longer lives in your head. While Copilot implements one spec, it can review another and refactor a third. Meanwhile, you write the next spec.&lt;/p>
&lt;p>This is not you multitasking or task-switching, but actual parallel progress. The productivity gain comes from removing the need to supervise.&lt;/p>
&lt;blockquote>
&lt;p>Never fix output before fixing the definition. If you violate this, the entire system collapses back into prompting.&lt;/p>
&lt;/blockquote>
&lt;h2 id="the-litmus-test">The litmus test&lt;/h2>
&lt;p>After a Copilot session, ask yourself this:&lt;/p>
&lt;p>&lt;em>If I deleted the entire chat history, could I reproduce this result using only the files in the repository?&lt;/em> If the answer is yes, you built a system. If the answer is no, you are still improvising.&lt;/p>
&lt;p>At this point, you are no longer “using GitHub Copilot”. You are running an AI-assisted development process. Ready or the next part?&lt;/p></description></item><item><title>You are holding GitHub Copilot Wrong!</title><link>https://m365princess.com/blogs/lego-copilot-wrong0/</link><pubDate>Sat, 13 Dec 2025 11:58:23 +0000</pubDate><guid>https://m365princess.com/blogs/lego-copilot-wrong0/</guid><description>&lt;p>Most developers think that one can&amp;rsquo;t really use GitHub Copilot wrong. There is a chat interface that lets you also choose a model, so the obvious choice is to choose a model (trust the LinkedIn bros on that) write prompts, get some code, adjust a few things, maybe ask a follow-up, rinse and repeat. Some say, it&amp;rsquo;s like working with a junior dev who has amnesia, some say it feels productive, even efficient.&lt;/p>
&lt;blockquote>
&lt;p>That feeling is exactly the problem.&lt;/p>
&lt;/blockquote>
&lt;p>What’s actually happening in most teams is not collaboration with Copilot, but a loop of prompting, re-prompting, and patching. You ask for something, the result is slightly off, so you add more detail. That fixes one thing and breaks another. So you clarify again, this time longer, more specific, more desperate. Eventually you either wrestle the output into shape or give up and finish it yourself.&lt;/p>
&lt;p>From the outside, this looks like “using GitHub Copilot”. From the inside, it’s just interactive trial and error, with really nice autocomplete, too many rocket 🚀 emojis and ridiculous responses from Copilot that your system would now be &lt;em>✨ production ready✨&lt;/em>. Spoiler: It is not. Effectively, it means that you are babysitting Copilot.&lt;/p>
&lt;p>The common reaction is to assume the prompt wasn’t good enough. So we respond by adding more words. More context. More instructions. More constraints. The interaction grows longer, but not necessarily clearer. Each new prompt tries to compensate for the last one, without ever fixing the underlying issue.&lt;/p>
&lt;blockquote>
&lt;p>The ugly truth is that this isn’t a prompting skill problem. It’s a structural one.&lt;/p>
&lt;/blockquote>
&lt;p>GitHub Copilot is not confused because it lacks intelligence or memory. It produces inconsistent results because we keep changing the shape of the problem while asking it to solve it. Intent, constraints, quality bars, and validation are all mixed together in free-form text, rewritten slightly differently every time. The model does exactly what it’s supposed to do in that situation; it adapts to the latest input and optimizes for that moment, without any stable frame of reference.&lt;/p>
&lt;h2 id="the-problem-with-the-context-window">The problem with the context window&lt;/h2>
&lt;p>The context window makes this worse, not better. Copilot does not reason over “the conversation” in the way humans intuitively imagine. It sees a sliding snapshot of recent input, where newer instructions naturally outweigh older ones and subtle phrasing differences matter more than you think. Each follow-up prompt does not refine a shared understanding; it reshapes the problem again, often implicitly undoing assumptions you thought were still in place.&lt;/p>
&lt;p>This is why re-prompting feels so fragile. You believe you are adding clarification, but from the model’s perspective you are changing the weighting of intent, constraints, and priorities inside a limited window of attention. Without a stable structure anchoring those elements, the model has no way to know what must remain invariant and what is allowed to change. So it does the only reasonable thing it can do: it treats the most recent input as the most important truth.&lt;/p>
&lt;p>If you did this with humans, you would get the same outcome. Imagine changing the requirements, definition of done, and architectural constraints in every conversation, then being surprised that the result varies. We accept this behavior from ourselves, then blame the tool when it responds accordingly.&lt;/p>
&lt;p>&lt;img alt="prompt meme" src="https://m365princess.com/images/prompt-meme.jpg">&lt;/p>
&lt;p>This is why so many GitHub Copilot interactions feel oddly fragile. Small wording changes produce disproportionately different results. A follow-up meant to “just tweak one thing” suddenly derails the whole solution. You spend more time steering than building, and yet it still feels faster than starting from scratch, which makes the pattern hard to question.&lt;/p>
&lt;p>Autocomplete reinforces this illusion. It works often enough to feel helpful, but not reliably enough to be trusted. For many developers, Copilot never graduates beyond that role; a slightly smarter snippet generator that occasionally needs correction. That’s not a failure of the technology, but the consequence of how it’s being used.&lt;/p>
&lt;p>The real issue is that most GitHub Copilot usage is entirely ad-hoc (and chaotical as well). Every task starts from a blank chat, every prompt is handcrafted in the moment, and every success dies with the session. Nothing is reusable, nothing is stable, and nothing improves over time. The only learning that happens is in the developer’s head, not in the system. And with that, it contradicts all the good dev practices that we established over the years.&lt;/p>
&lt;h2 id="staying-engaged-doesnt-help">Staying engaged doesn&amp;rsquo;t help&lt;/h2>
&lt;p>I even read on social media that you should stay actively engaged with GitHub Copilot while it is “thinking”, avoid doing anything distracting, and carefully watch how it generates code in real time. Oh boy, please just don’t.&lt;/p>
&lt;p>This advice sounds productive, but it completely misses the point of what’s actually broken in most Copilot workflows. The problem is not that developers are insufficiently attentive during generation. The problem is that the interaction model itself is wrong.&lt;/p>
&lt;p>Watching Copilot generate code line by line is just prompt-by-prompt coding with better animations. It optimizes for micro-level engagement while locking you into the same fragile loop: prompt, wait, inspect, fix, re-prompt. You might feel more “in control”, but you’re still reacting to output instead of designing the system that produces it.&lt;/p>
&lt;p>Staying glued to the editor during those 30–60 seconds does not scale your thinking; it fragments it. You are forced to hold the entire problem in working memory while the model explores a solution space you did not properly constrain in the first place. The moment something goes slightly off, the only lever you have is another prompt. So you interrupt, redirect, clarify, and steer, over and over and over.&lt;/p>
&lt;p>The idea that you should study the model’s intermediate steps also assumes those steps are stable, meaningful, and reusable. They are not. What you are watching is an super short-lived reasoning trace for a one-off interaction that will never happen in exactly the same way again. Any “intuition” you build there is about how to rescue a weak setup, not how to avoid it.&lt;/p>
&lt;h2 id="the-cognitive-drain">The cognitive drain&lt;/h2>
&lt;p>There is also a deeper cost that rarely gets mentioned. Prompt-by-prompt coding trains you to think in very small increments, because that’s the only scale the interaction supports. You focus on the next function, the next file, the next tweak, because that’s the granularity the interaction forces on you. Architecture, constraints, invariants, and definitions of done all get deferred, because they don’t fit well into reactive steering. Over time, this erodes your ability to reason about systems at a higher level. That’s the brain-rot people are intuitively pointing at, even if they don’t always articulate it well.&lt;/p>
&lt;p>A more mature approach flips this completely. Instead of babysitting the model while it types, you invest your attention upfront. You define specifications, constraints, and guardrails clearly enough that the model can work unattended for longer stretches. While it does that, you move on. You think about the next spec, the next boundary, the next decision. There is no “wait time” to fill with mindfulness exercises; waiting disappears as a concept.&lt;/p>
&lt;p>Once you stop coding prompt by prompt, parallelism becomes possible. Not in the sense of typing faster, but in the sense of progressing multiple pieces of work at once without context loss. That is where the real productivity gains come from: Not from watching tokens stream by, but from designing interactions that no longer require your constant supervision.&lt;/p>
&lt;p>&lt;img alt="Prompt Gru" src="https://m365princess.com/images/prompt-gru.jpe">&lt;/p>
&lt;p>To help you out, I put together a series: Not about writing better prompts or finding clever phrasing, but about stepping out of the prompt-repair loop altogether. The shift is from reacting to outputs to designing interactions and from improvising one-off wants to engineering instructions.&lt;/p>
&lt;p>In the &lt;a href="https://www.m365princess.com/blogs/lego-copilot-wrong1/">next part&lt;/a>, we’ll start by dismantling a surprisingly harmful assumption; the idea that GitHub Copilot is a chatbot you talk to, rather than a system you shape.&lt;/p></description></item><item><title>Building a Multi-Hierarchy Ticket Classification System</title><link>https://m365princess.com/blogs/ticket-hierarchy/</link><pubDate>Wed, 10 Dec 2025 18:58:37 +0000</pubDate><guid>https://m365princess.com/blogs/ticket-hierarchy/</guid><description>&lt;p>Look, I love a good keyword-based system as much as the next developer. They&amp;rsquo;re fast, predictable, and when your user says &amp;ldquo;VPN,&amp;rdquo; you &lt;em>know&lt;/em> they mean network issues. But what happens when someone writes &amp;ldquo;Can&amp;rsquo;t access the customer portal from home&amp;rdquo;? Is that Network? Access? Business Applications? Remote Work Setup?&lt;/p>
&lt;p>Welcome to the world where simple pattern matching falls apart, and you need something smarter.&lt;/p>
&lt;h2 id="why-i-built-this-and-why-you-might-need-it-too">Why I built this (and why you might need it too)&lt;/h2>
&lt;p>One of my customers tried to build an IT support automation system that processes emails and creates tickets. Their keyword-based triage worked great for the obvious stuff:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;password&amp;rdquo; → Access&lt;/li>
&lt;li>&amp;ldquo;VPN&amp;rdquo; → Network&lt;/li>
&lt;li>&amp;ldquo;invoice&amp;rdquo; → Billing&lt;/li>
&lt;/ul>
&lt;p>But then I looked at their analytics. 23% of tickets were getting classified as &amp;ldquo;Other&amp;rdquo;. That&amp;rsquo;s more a surrender flag than a valid category.&lt;/p>
&lt;p>So I built them a hierarchical classification system using embeddings and vector similarity. I want to share a few things I learned along the way.&lt;/p>
&lt;h2 id="the-architecture-3-tier-classification">The architecture: 3-tier classification&lt;/h2>
&lt;p>The key insight? Don&amp;rsquo;t replace your keyword system – augment it with AI validation.&lt;/p>
&lt;pre tabindex="0">&lt;code>Incoming Ticket
↓
Tier 1: Keyword Triage (fast, covers 80%)
↓
Category = &amp;#34;Other&amp;#34;?
├─ No → Done (200ms)
└─ Yes → Tier 2: Vector Similarity (+300ms)
↓
Tier 3: LLM Validation (+50ms)
↓
Final Classification (350ms total)
&lt;/code>&lt;/pre>&lt;p>This gives you:&lt;/p>
&lt;ul>
&lt;li>Speed for common cases (keyword matching is microseconds)&lt;/li>
&lt;li>Accuracy for edge cases (semantic understanding via embeddings)&lt;/li>
&lt;li>Confidence boosting through LLM validation (semantic reranking)&lt;/li>
&lt;li>Cost control (only pay for embedding + LLM calls when needed)&lt;/li>
&lt;/ul>
&lt;h2 id="step-1-design-your-taxonomy-this-is-the-hard-part">Step 1: Design your taxonomy (this is the hard part)&lt;/h2>
&lt;p>Forget the code for a minute. The quality of your classification entirely depends on your taxonomy design. This again is a &lt;em>garbage in - garbage out&lt;/em> situation&lt;/p>
&lt;p>Here&amp;rsquo;s what worked for me:&lt;/p>
&lt;h3 id="15-main-categories">15 main categories&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Access&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Network&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Email &amp;amp; Calendar&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Identity&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Hardware&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Software&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Microsoft Teams&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;SharePoint &amp;amp; OneDrive&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Business Applications&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Security&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Payments &amp;amp; Billing&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Device Management&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Printing &amp;amp; Scanning&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Remote Work Setup&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Other&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="7-10-subcategories-each-125-total">7-10 subcategories each (125 total)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;mainCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Business Applications&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;subCategories&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;SAP Access&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;SAP Errors&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Dynamics Access&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Dynamics Data Issues&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;CRM Login&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ERP Transactions&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Custom App Failure&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Browser Compatibility&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;API Errors&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="key-principles">Key principles&lt;/h3>
&lt;ol>
&lt;li>Balanced distribution: Avoid one category with 50 subcategories and another with 3&lt;/li>
&lt;li>Mutually exclusive: &amp;ldquo;Printer offline&amp;rdquo; shouldn&amp;rsquo;t overlap with &amp;ldquo;Network issues&amp;rdquo;&lt;/li>
&lt;li>User language: Use terms your users actually say, not IT jargon&lt;/li>
&lt;li>Future-proof: Leave room to add subcategories without redesigning&lt;/li>
&lt;/ol>
&lt;p>I spent 2 days just refining the taxonomy with actual ticket data. Don&amp;rsquo;t skip this.&lt;/p>
&lt;h2 id="step-2-generate-enriched-embeddings-for-your-taxonomy">Step 2: Generate enriched embeddings for your taxonomy&lt;/h2>
&lt;p>Now comes the magic. You need to convert each category pair into a vector that captures its semantic meaning.&lt;/p>
&lt;p>I used Azure OpenAI&amp;rsquo;s &lt;code>text-embedding-3-large&lt;/code> (3072 dimensions), but for the love of unicorns: don&amp;rsquo;t just embed the category name – enrich it with common user phrases. Ask me how I know!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AzureOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_enriched_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">main_category&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sub_category&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add common user phrases to boost embedding quality&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enrichment_map&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Network&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;VPN&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;VPN, virtual private network, VPN disconnects, VPN keeps dropping, cannot connect VPN, VPN not working&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;WiFi Connectivity&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;WiFi, wireless, cannot connect WiFi, WiFi network not found, WiFi disconnects&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Microsoft Teams&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Meeting Audio&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Teams audio, cannot hear, microphone, no sound in meeting, audio not working, mic not working&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Payments &amp;amp; Billing&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Double Charge&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;double charged, billed twice, duplicate charge, charged twice&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ... 125 total enrichments&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enrichment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">main_category&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">enrichment_map&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enrichment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enrichment_map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">main_category&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sub_category&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">enrichment&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">main_category&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sub_category&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">. Common issues include: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">enrichment&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">main_category&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sub_category&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">build_taxonomy_index&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Load your taxonomy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;taxonomy.json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;r&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">taxonomy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AzureOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;AZURE_OPENAI_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;2024-08-01-preview&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">azure_endpoint&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;AZURE_OPENAI_ENDPOINT&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">main_category&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">taxonomy&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">main_cat_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">main_category&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;mainCategory&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">sub_category&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">main_category&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;subCategories&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Generate ENRICHED text with common user phrases&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_enriched_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">main_cat_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sub_category&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Generate 3072-dim vector&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embeddings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-3-large&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">input&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">index&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;mainCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">main_cat_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;subCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">sub_category&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;embedding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Save to file (yes, just JSON – keep it simple)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;taxonomy_index.json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;w&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dump&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">index&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="why-enrich-with-user-phrases">Why enrich with user phrases?&lt;/h3>
&lt;p>Because raw embeddings max out around 60-70% similarity even for perfect matches. Adding &amp;ldquo;VPN disconnects, VPN keeps dropping&amp;rdquo; gives the vector more semantic surface area to match against real user language.&lt;/p>
&lt;h3 id="the-enrichment-strategy">The enrichment strategy&lt;/h3>
&lt;ul>
&lt;li>Map common user phrases to each subcategory (&amp;ldquo;cannot print, printer offline, print job stuck&amp;rdquo;)&lt;/li>
&lt;li>Include synonyms and variations (&amp;ldquo;MFA, 2FA, authentication app, verification code&amp;rdquo;)&lt;/li>
&lt;li>Use actual language from your ticket history (not IT jargon)&lt;/li>
&lt;/ul>
&lt;h2 id="step-3-build-the-classification-engine">Step 3: Build the classification engine&lt;/h2>
&lt;p>The classification is &lt;a href="https://www.geeksforgeeks.org/dbms/cosine-similarity/">cosine similarity&lt;/a>. It finds which taxonomy vector is closest to your ticket&amp;rsquo;s vector.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HierarchicalClassifier&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">index_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;taxonomy_index.json&amp;#39;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">index_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;r&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Pre-compute numpy arrays for speed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embeddings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">array&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;embedding&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">categories&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;mainCategory&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;subCategory&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">cosine_similarity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vec1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vec2&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Fast vectorized cosine similarity&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vec1_norm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vec1&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linalg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vec2_norm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vec2&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linalg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">axis&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">keepdims&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec2_norm&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vec1_norm&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">classify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">text_embedding&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">top_k&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Return top K matches with confidence scores&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">similarities&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cosine_similarity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text_embedding&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embeddings&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">top_indices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argsort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">similarities&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">top_k&lt;/span>&lt;span class="p">:][::&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">top_indices&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">main_cat&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sub_cat&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">categories&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">idx&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">float&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">similarities&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">idx&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;mainCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">main_cat&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;subCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">sub_cat&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;confidence&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">confidence&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="performance-notes">Performance notes&lt;/h3>
&lt;ul>
&lt;li>Numpy vectorization is critical – 125 similarity calculations in ~5ms&lt;/li>
&lt;li>Pre-computing the embedding matrix at startup saves repeated conversions&lt;/li>
&lt;li>Cosine similarity works better than &lt;a href="https://www.geeksforgeeks.org/maths/euclidean-distance/">Euclidean distance&lt;/a> for semantic tasks. I tested both.&lt;/li>
&lt;/ul>
&lt;h2 id="step-4-create-the-api-endpoint-with-llm-validation">Step 4: Create the API endpoint with LLM validation&lt;/h2>
&lt;p>Here&amp;rsquo;s where it gets interesting. Pure vector similarity topped out at 60-70% confidence. To hit 80%+, I added LLM validation as a semantic reranker.&lt;/p>
&lt;p>I deployed this as an Azure Function alongside my RAG search endpoint:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">azure.functions&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">func&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;hierarchical-classify&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">methods&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">auth_level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">AuthLevel&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FUNCTION&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hierarchical_classify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">req&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">HttpRequest&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Parse request&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">req&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_json&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;text&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Step 1: Generate embedding for incoming text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">openai_client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embeddings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-3-large&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">input&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text_embedding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">embedding_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Step 2: Vector similarity classification&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">classifier&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HierarchicalClassifier&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">classifier&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">classify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text_embedding&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">top_k&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">top_match&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;confidence&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Step 3: LLM validation (semantic reranking)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">validation_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;You are a ticket classification validator.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">User&amp;#39;s issue: &amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Proposed classification:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- Main Category: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;mainCategory&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- Sub Category: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;subCategory&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Does this classification seem accurate? Respond with ONLY:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;EXCELLENT&amp;#34; if perfect match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;GOOD&amp;#34; if good match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;FAIR&amp;#34; if reasonable match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;POOR&amp;#34; if incorrect&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">validation_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">openai_client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chat&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">completions&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gpt-4o-mini&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;system&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;You are a classification validator. Respond with ONLY: EXCELLENT, GOOD, FAIR, or POOR.&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">validation_prompt&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temperature&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_tokens&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">validation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validation_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">upper&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Step 4: Boost confidence based on LLM agreement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">validation&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;EXCELLENT&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">1.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.99&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 80%+ boost&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">validation&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;GOOD&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">1.3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.95&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">validation&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;FAIR&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">1.1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.90&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># POOR&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">0.8&lt;/span> &lt;span class="c1"># Reduce confidence&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Return result with validation metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">HttpResponse&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;mainCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;mainCategory&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;subCategory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;subCategory&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;confidence&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">round&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">final_confidence&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;validationQuality&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">validation&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;baseConfidence&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">round&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;alternativeMatches&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mimetype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="why-return-alternatives">Why return alternatives?&lt;/h3>
&lt;p>Because confidence alone doesn&amp;rsquo;t tell the full story. If &amp;ldquo;Printer offline&amp;rdquo; scores 0.72 and &amp;ldquo;Network issues&amp;rdquo; scores 0.71, you want visibility into that close call.&lt;/p>
&lt;h2 id="step-5-integration-pattern-the-smart-part">Step 5: Integration pattern (the smart part)&lt;/h2>
&lt;p>Here&amp;rsquo;s where the three-tier system pays off. In my TypeScript triage service:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="nx">triageTicket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">emailBody&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TriageResult&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Tier 1: Fast keyword matching
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">keywordResult&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keywordBasedTriage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">emailBody&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Tier 2 &amp;amp; 3: Only use AI for &amp;#34;Other&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">keywordResult&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">category&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;Other&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Tier 2: Vector similarity + Tier 3: LLM validation
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">hierarchical&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hierarchicalClassify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">emailBody&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Confidence threshold: 0.6 (after LLM boost)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">hierarchical&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">confidence&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mf">0.6&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">category&lt;/span>: &lt;span class="kt">hierarchical.mainCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">subCategory&lt;/span>: &lt;span class="kt">hierarchical.subCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">priority&lt;/span>: &lt;span class="kt">keywordResult.priority&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Keep keyword priority
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">confidence&lt;/span>: &lt;span class="kt">hierarchical.confidence&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">validationQuality&lt;/span>: &lt;span class="kt">hierarchical.validationQuality&lt;/span> &lt;span class="c1">// EXCELLENT/GOOD/FAIR/POOR
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Hierarchical classification failed:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Graceful degradation: keep keyword result
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">keywordResult&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="why-this-works">Why this works&lt;/h3>
&lt;ol>
&lt;li>80% of tickets hit keywords (Tier 1), never touch the API (fast + free)&lt;/li>
&lt;li>20% edge cases get semantic understanding (Tier 2: vector similarity)&lt;/li>
&lt;li>All edge cases get LLM validation (Tier 3: confidence boosting)&lt;/li>
&lt;li>Failures gracefully degrade to &amp;ldquo;Other&amp;rdquo; instead of crashing&lt;/li>
&lt;li>Priority detection stays rule-based (urgent/high/medium/low keywords are reliable)&lt;/li>
&lt;/ol>
&lt;h2 id="real-world-results">Real-world results:&lt;/h2>
&lt;p>After deploying the LLM-validated system with enriched taxonomy, I tested 500+ diverse scenarios spanning all 15 main categories. Here&amp;rsquo;s a representative sample showing the three-tier scoring in action:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Ticket Text&lt;/th>
&lt;th style="text-align: left">Category&lt;/th>
&lt;th style="text-align: left">Subcategory&lt;/th>
&lt;th style="text-align: left">Base&lt;/th>
&lt;th style="text-align: left">Validation&lt;/th>
&lt;th style="text-align: left">Final&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Printer is showing offline status&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Printing &amp;amp; Scanning&lt;/td>
&lt;td style="text-align: left">Printer Offline&lt;/td>
&lt;td style="text-align: left">71.2%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Outlook emails are not syncing&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Email &amp;amp; Calendar&lt;/td>
&lt;td style="text-align: left">Sync Issues&lt;/td>
&lt;td style="text-align: left">72.8%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;OneDrive files not syncing&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">SharePoint &amp;amp; OneDrive&lt;/td>
&lt;td style="text-align: left">File Sync&lt;/td>
&lt;td style="text-align: left">74.1%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Teams meeting audio broken&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Microsoft Teams&lt;/td>
&lt;td style="text-align: left">Meeting Audio&lt;/td>
&lt;td style="text-align: left">76.3%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Cannot access SAP production&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Business Applications&lt;/td>
&lt;td style="text-align: left">SAP Access&lt;/td>
&lt;td style="text-align: left">68.9%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Double charge on invoice&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Payments &amp;amp; Billing&lt;/td>
&lt;td style="text-align: left">Double Charge&lt;/td>
&lt;td style="text-align: left">70.4%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Remote desktop connection failed&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Remote Work Setup&lt;/td>
&lt;td style="text-align: left">Remote Desktop&lt;/td>
&lt;td style="text-align: left">65.7%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">98.5% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Getting malware warning&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Security&lt;/td>
&lt;td style="text-align: left">Malware Detection&lt;/td>
&lt;td style="text-align: left">67.1%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Cannot login to company portal&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Device Management&lt;/td>
&lt;td style="text-align: left">Company Portal Issues&lt;/td>
&lt;td style="text-align: left">72.8%&lt;/td>
&lt;td style="text-align: left">GOOD&lt;/td>
&lt;td style="text-align: left">94.7% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Cannot schedule Teams meeting&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Email &amp;amp; Calendar&lt;/td>
&lt;td style="text-align: left">Meeting Scheduling&lt;/td>
&lt;td style="text-align: left">63.1%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">94.6% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;VPN keeps dropping&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Network&lt;/td>
&lt;td style="text-align: left">VPN&lt;/td>
&lt;td style="text-align: left">68.0%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Printer won&amp;rsquo;t connect to WiFi&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Printing &amp;amp; Scanning&lt;/td>
&lt;td style="text-align: left">Network Printer Access&lt;/td>
&lt;td style="text-align: left">72.0%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Need to reset Entra ID password&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Identity&lt;/td>
&lt;td style="text-align: left">Entra ID Login&lt;/td>
&lt;td style="text-align: left">69.3%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">99.0% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Need to install Adobe Creative Cloud&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Software&lt;/td>
&lt;td style="text-align: left">Installation Request&lt;/td>
&lt;td style="text-align: left">54.1%&lt;/td>
&lt;td style="text-align: left">EXCELLENT&lt;/td>
&lt;td style="text-align: left">81.2% ✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&amp;ldquo;Laptop screen is flickering&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Hardware&lt;/td>
&lt;td style="text-align: left">Monitor Setup&lt;/td>
&lt;td style="text-align: left">42.1%&lt;/td>
&lt;td style="text-align: left">GOOD&lt;/td>
&lt;td style="text-align: left">54.7% ⚠️&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Average confidence: 85.2% 🎯 (Target: 80%+)&lt;/p>
&lt;h3 id="key-insights-from-500-test-scenarios">Key insights from 500+ test scenarios&lt;/h3>
&lt;ul>
&lt;li>93% above 60% threshold – only highly ambiguous cases fall below (e.g., &amp;ldquo;laptop screen&amp;rdquo; could be laptop OR monitor)&lt;/li>
&lt;li>Enriched taxonomy boosts base confidence by 15-25% compared to unenriched version (e.g., WiFi printer: 48% → 72% base)&lt;/li>
&lt;li>LLM validation adds final 20-30% boost for most scenarios (EXCELLENT rating = 1.5x multiplier)&lt;/li>
&lt;li>100% accuracy on primary category across all 500+ tests (never misclassified main category)&lt;/li>
&lt;li>EXCELLENT validation in 87% of cases – LLM confirms vector similarity is semantically correct&lt;/li>
&lt;/ul>
&lt;h3 id="the-validation-quality-distribution-500-scenarios">The validation quality distribution (500+ scenarios)&lt;/h3>
&lt;ul>
&lt;li>EXCELLENT (87%): Perfect semantic match, boost to 80%+&lt;/li>
&lt;li>GOOD (11%): Reasonable match with slight ambiguity, moderate boost&lt;/li>
&lt;li>FAIR (2%): Questionable match, minimal boost&lt;/li>
&lt;li>POOR (0%): No poor classifications in test set (by design - taxonomy is well-tuned)&lt;/li>
&lt;/ul>
&lt;h3 id="the-06-threshold-is-still-tunable">The 0.6 threshold is still tunable&lt;/h3>
&lt;ul>
&lt;li>0.5 = More aggressive (covers 100% of test set)&lt;/li>
&lt;li>0.6 = Balanced (my sweet spot, 93% coverage)&lt;/li>
&lt;li>0.7 = Conservative (87% coverage, only slam dunks)&lt;/li>
&lt;/ul>
&lt;h3 id="why-some-cases-stay-below-threshold">Why some cases stay below threshold&lt;/h3>
&lt;p>&amp;ldquo;Laptop screen flickering&amp;rdquo; is genuinely ambiguous – it could be laptop hardware (Hardware/Laptop Issues) OR external monitor (Hardware/Monitor Setup). Base confidence was 42.1%, and even with GOOD validation, it only reached 54.7%. This is correct behavior – the system should flag ambiguous cases for human review rather than guess with false confidence. Across 500+ scenarios, only 7% fell into this &amp;ldquo;needs human&amp;rdquo; category.&lt;/p>
&lt;h3 id="how-we-get-these-scores-the-math-behind-confidence">How we get these scores: the math behind confidence&lt;/h3>
&lt;p>Let me explain exactly where these numbers come from, because the three-tier system has distinct scoring at each level.&lt;/p>
&lt;h4 id="tier-1-vector-similarity-base-confidence">Tier 1: Vector similarity (base confidence)&lt;/h4>
&lt;p>When you generate an embedding for user text and compare it to taxonomy embeddings, you get a cosine similarity score between -1 and 1:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">cosine_similarity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vec2&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Cosine similarity = dot product of normalized vectors&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vec1_norm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vec1&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linalg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vec2_norm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vec2&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linalg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec1_norm&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vec2_norm&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="what-these-scores-mean">What these scores mean&lt;/h5>
&lt;ul>
&lt;li>1.0 = Identical vectors (same text embedded twice)&lt;/li>
&lt;li>0.9-0.99 = Extremely similar semantic meaning (rare without enrichment)&lt;/li>
&lt;li>0.7-0.9 = Strong semantic similarity (good match)&lt;/li>
&lt;li>0.5-0.7 = Moderate similarity (related topics)&lt;/li>
&lt;li>0.0-0.5 = Weak similarity (different topics)&lt;/li>
&lt;li>Negative = Opposite meaning (never happens with our use case)&lt;/li>
&lt;/ul>
&lt;h5 id="why-enrichment-matters">Why enrichment matters&lt;/h5>
&lt;p>Without enrichment (just &amp;ldquo;Network: VPN&amp;rdquo;), base scores max out around 54-58% even for perfect matches like &amp;ldquo;VPN keeps dropping&amp;rdquo;. With enrichment (&amp;ldquo;Network: VPN. Common issues include: VPN, virtual private network, VPN disconnects, VPN keeps dropping, cannot connect VPN, VPN not working&amp;rdquo;), base scores jump to 65-70% because there&amp;rsquo;s more semantic surface area to match.&lt;/p>
&lt;h5 id="example-from-my-tests">Example from my tests&lt;/h5>
&lt;ul>
&lt;li>User text: &lt;code>&amp;quot;VPN keeps dropping&amp;quot;&lt;/code>&lt;/li>
&lt;li>Taxonomy text: &lt;code>&amp;quot;Network: VPN. Common issues include: VPN, virtual private network, VPN disconnects, VPN keeps dropping...&amp;quot;&lt;/code>&lt;/li>
&lt;li>Cosine similarity: 0.540 (54.0% base confidence)&lt;/li>
&lt;/ul>
&lt;p>The embedding model sees &amp;ldquo;VPN keeps dropping&amp;rdquo; in both texts and recognizes the semantic overlap, but it&amp;rsquo;s not perfect because the user text is short and the taxonomy text has extra context.&lt;/p>
&lt;h4 id="tier-2-llm-validation-semantic-reranking">Tier 2: LLM validation (semantic reranking)&lt;/h4>
&lt;p>This is where GPT-4o-mini acts as a semantic reranker, conceptually identical to Azure AI Search&amp;rsquo;s semantic ranking feature. Just like Azure AI Search uses a transformer model to re-score BM25/vector results, we use an LLM to validate vector similarity matches.&lt;/p>
&lt;p>We ask it a binary question: &amp;ldquo;Does this classification make sense?&amp;rdquo;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">validation_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;You are a ticket classification validator.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">User&amp;#39;s issue: &amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Proposed classification:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- Main Category: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;mainCategory&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- Sub Category: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">top_match&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;subCategory&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Does this classification seem accurate? Respond with ONLY:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;EXCELLENT&amp;#34; if perfect match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;GOOD&amp;#34; if good match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;FAIR&amp;#34; if reasonable match
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;POOR&amp;#34; if incorrect&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="the-llm-evaluates-semantic-fit">The LLM evaluates semantic fit&lt;/h5>
&lt;ul>
&lt;li>EXCELLENT: User text perfectly matches the category intent (e.g., &amp;ldquo;VPN drops&amp;rdquo; → Network/VPN)&lt;/li>
&lt;li>GOOD: Reasonable match but slight ambiguity (e.g., &amp;ldquo;can&amp;rsquo;t login to portal&amp;rdquo; → Device Management/Company Portal)&lt;/li>
&lt;li>FAIR: Somewhat related but not ideal (e.g., &amp;ldquo;laptop keyboard broken&amp;rdquo; → Hardware/Keyboard not Hardware/Laptop)&lt;/li>
&lt;li>POOR: Misclassification (would trigger in edge cases like &amp;ldquo;laptop screen&amp;rdquo; → Monitor Setup)&lt;/li>
&lt;/ul>
&lt;h5 id="why-this-works-1">Why this works&lt;/h5>
&lt;p>The LLM has reasoning capabilities that pure vector similarity lacks. It understands that:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;Printer won&amp;rsquo;t connect to WiFi&amp;rdquo; is primarily a printer issue (Printing &amp;amp; Scanning / Network Printer) not Network/WiFi&lt;/li>
&lt;li>&amp;ldquo;Need to reset Entra ID password&amp;rdquo; is Identity/Entra ID not Access/Password (even though both are related)&lt;/li>
&lt;li>&amp;ldquo;Teams audio broken&amp;rdquo; is Teams/Meeting Audio not Hardware/Audio (context matters)&lt;/li>
&lt;/ul>
&lt;p>This is exactly what semantic reranking does in Azure AI Search – it goes beyond lexical/vector matching to understand &lt;em>semantic intent&lt;/em>. The difference is we&amp;rsquo;re using a general-purpose LLM instead of a specialized ranking model, which gives us explainability (we can see WHY it rated something EXCELLENT vs POOR) at the cost of ~50ms extra latency.&lt;/p>
&lt;h4 id="tier-3-confidence-boosting-final-score">Tier 3: Confidence boosting (final score)&lt;/h4>
&lt;p>We apply multiplicative boosts based on LLM validation quality:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">validation&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;EXCELLENT&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">1.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.99&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 50% boost, cap at 99%&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">elif&lt;/span> &lt;span class="n">validation&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;GOOD&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">1.3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.95&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 30% boost, cap at 95%&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">elif&lt;/span> &lt;span class="n">validation&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;FAIR&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">1.1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.90&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 10% boost, cap at 90%&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># POOR&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_confidence&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">base_confidence&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">0.8&lt;/span> &lt;span class="c1"># 20% penalty&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="example-calculation-vpn-case">Example calculation (VPN case)&lt;/h5>
&lt;ol>
&lt;li>Base confidence: 54.0% (cosine similarity)&lt;/li>
&lt;li>LLM validation: EXCELLENT (perfect semantic match)&lt;/li>
&lt;li>Boost: 54.0% × 1.5 = 81.0%&lt;/li>
&lt;li>Final confidence: 81.0% ✅ (above 60% threshold)&lt;/li>
&lt;/ol>
&lt;h5 id="example-calculation-printer-wifi-case">Example calculation (Printer WiFi case)&lt;/h5>
&lt;ol>
&lt;li>Base confidence: 50.4% (moderate similarity)&lt;/li>
&lt;li>LLM validation: EXCELLENT (LLM understands it&amp;rsquo;s a printer issue, not pure network)&lt;/li>
&lt;li>Boost: 50.4% × 1.5 = 75.6%&lt;/li>
&lt;li>Final confidence: 75.7% ✅ (rounded, above threshold)&lt;/li>
&lt;/ol>
&lt;h5 id="example-calculation-laptop-screen-case">Example calculation (Laptop screen case)&lt;/h5>
&lt;ol>
&lt;li>Base confidence: 38.1% (weak similarity - ambiguous: laptop vs monitor?)&lt;/li>
&lt;li>LLM validation: GOOD (LLM sees ambiguity, not perfect)&lt;/li>
&lt;li>Boost: 38.1% × 1.3 = 49.5%&lt;/li>
&lt;li>Final confidence: 49.5% ❌ (below 60% threshold, correctly flagged as uncertain)&lt;/li>
&lt;/ol>
&lt;p>Why the caps?&lt;/p>
&lt;ul>
&lt;li>99% cap on EXCELLENT: Never claim 100% certainty (machine learning isn&amp;rsquo;t perfect)&lt;/li>
&lt;li>95% cap on GOOD: Decent match but not perfect&lt;/li>
&lt;li>90% cap on FAIR: Questionable matches shouldn&amp;rsquo;t be high-confidence&lt;/li>
&lt;/ul>
&lt;h5 id="the-beauty-of-this-approach">The beauty of this approach&lt;/h5>
&lt;p>The base confidence acts as a quality filter (weak semantic matches stay low even with LLM boost), and the LLM validation acts as a confidence amplifier for cases where vector similarity undersells the match quality.&lt;/p>
&lt;p>Think of it like Azure AI Search&amp;rsquo;s two-stage ranking:&lt;/p>
&lt;ol>
&lt;li>First-stage ranker (vector similarity): Fast, returns candidates with scores&lt;/li>
&lt;li>Semantic reranker (LLM validation): Slower, validates and boosts the best match&lt;/li>
&lt;/ol>
&lt;p>The key insight: Don&amp;rsquo;t replace your vector search with an LLM. Use the LLM to validate and boost the vector results. This is the same hybrid approach that makes Azure AI Search&amp;rsquo;s semantic ranking so effective.&lt;/p>
&lt;h2 id="what-id-do-differently-and-what-you-can-do">What I&amp;rsquo;d do differently (and what you can do)&lt;/h2>
&lt;h3 id="1-train-with-historical-ticket-data">1. Train with historical ticket data&lt;/h3>
&lt;p>Right now, the enrichment map uses my best guesses for common user phrases. But if you have actual ticket history, you can do better:&lt;/p>
&lt;p>Option A: Generate enrichments from historical data (recommended)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Analyze 1000 tickets categorized by humans&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">historical_tickets&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_ticket_history&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Extract actual user language per category&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enrichments&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">category&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tickets&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">historical_tickets&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">common_phrases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">extract_frequent_phrases&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tickets&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">enrichments&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">category&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;, &amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">common_phrases&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Use real user language in taxonomy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enriched_text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">category&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">subcategory&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">. Users say: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">enrichments&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">category&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This gives you actual user language instead of guessed phrases. If your users say &amp;ldquo;VPN tunnel keeps failing&amp;rdquo; instead of &amp;ldquo;VPN disconnects&amp;rdquo;, you&amp;rsquo;ll capture that.&lt;/p>
&lt;p>Option B: Fine-tune the embedding model (advanced)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AzureOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Fine-tune text-embedding-3-large on your domain&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">training_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;VPN tunnel fails every morning&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;label&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Network: VPN&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Cannot print documents&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;label&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Printing: Printer Offline&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ... 100+ examples per category&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fine_tuning&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">jobs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">training_file&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;training_data.jsonl&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-3-large&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This creates a custom embedding model tuned to your specific ticket language. Requires 100+ labeled examples per category, but can push base confidence from 60% to 75%+.&lt;/p>
&lt;p>Option C: Use historical examples in LLM validation (quick win)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">validation_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;You are a ticket classification validator.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">User&amp;#39;s issue: &amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Proposed: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">category&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> / &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">subcategory&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Historical examples of this category:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;VPN disconnects every 5 minutes&amp;#34; ✓
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;Cannot establish VPN tunnel&amp;#34; ✓
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">- &amp;#34;VPN connection drops frequently&amp;#34; ✓
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Does the user&amp;#39;s issue match? EXCELLENT/GOOD/FAIR/POOR&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This gives the LLM context from real tickets without retraining anything. Easy to implement, moderate improvement (~5% confidence boost).&lt;/p>
&lt;p>Which approach should you use?&lt;/p>
&lt;ul>
&lt;li>Option A if you have 100+ tickets per category and want better embeddings&lt;/li>
&lt;li>Option B if you have 1000+ tickets per category and need maximum accuracy&lt;/li>
&lt;li>Option C if you want quick wins without retraining (start here!)&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Want the full guide on learning from historical ticket data? Blog post on that coming soon (not Microsoft Soon™️)&lt;/p>
&lt;/blockquote>
&lt;h3 id="2-add-active-learning-loop">2. Add active learning loop&lt;/h3>
&lt;p>Track when humans reclassify tickets and use that as training data to refine the taxonomy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// When support agent manually changes category
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="nx">logReclassification&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ticketId&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalCategory&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">correctedCategory&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ticketText&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">trackingService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">store&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ticketId&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">correctedCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ticketText&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">timestamp&lt;/span>: &lt;span class="kt">new&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Every 100 reclassifications, retrain
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">await&lt;/span> &lt;span class="nx">trackingService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">retrainTaxonomy&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This creates a feedback loop where the system learns from mistakes and improves over time.&lt;/p>
&lt;h3 id="3-cost-optimization">3. Cost optimization&lt;/h3>
&lt;p>Cache embeddings for common phrases. &amp;ldquo;VPN not working&amp;rdquo; appears 50 times – embed it once:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">hashlib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">redis&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">redis&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Redis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;localhost&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">port&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">6379&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_cached_embedding&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cache_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;embedding:&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hashlib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">md5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hexdigest&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Check cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cached&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cache_key&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">cached&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cached&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Generate and cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">openai_client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embeddings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-3-large&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">input&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">setex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cache_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">86400&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 24hr TTL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">embedding&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At 1000 tickets/month with 30% duplicate phrases, this saves ~$0.03/month. Not huge, but it adds up at scale.&lt;/p>
&lt;h3 id="4-multi-language-support">4. Multi-language support&lt;/h3>
&lt;p>&lt;code>text-embedding-3-large&lt;/code> handles multiple languages, but my taxonomy is English-only. Expanding subcategories with translations would be valuable.&lt;/p>
&lt;h2 id="the-tech-stack">The tech stack&lt;/h2>
&lt;h4 id="what-i-used">What I used&lt;/h4>
&lt;ul>
&lt;li>Azure OpenAI – &lt;code>text-embedding-3-large&lt;/code> (3072 dims)&lt;/li>
&lt;li>Python 3.11 – Classification engine&lt;/li>
&lt;li>Numpy – Fast vector operations&lt;/li>
&lt;li>Azure Functions – Serverless API endpoint&lt;/li>
&lt;li>TypeScript – Integration layer&lt;/li>
&lt;/ul>
&lt;h4 id="costs-with-llm-validation">Costs (with LLM validation)&lt;/h4>
&lt;ul>
&lt;li>Embedding generation (one-time): ~$0.02 for 125 categories&lt;/li>
&lt;li>Per-classification embedding: ~$0.0001 per &amp;ldquo;Other&amp;rdquo; ticket&lt;/li>
&lt;li>Per-classification LLM validation: ~$0.00005 (GPT-4o-mini, 10 tokens)&lt;/li>
&lt;li>Total per classification: ~$0.00015&lt;/li>
&lt;li>At 1000 tickets/month with 20% &amp;ldquo;Other&amp;rdquo; rate: ~$0.03/month&lt;/li>
&lt;/ul>
&lt;p>The cost is negligible. The accuracy improvement (60% → 83% average) is not.&lt;/p>
&lt;h4 id="performance">Performance&lt;/h4>
&lt;ul>
&lt;li>Vector similarity: ~300ms (embedding + cosine)&lt;/li>
&lt;li>LLM validation: ~50ms (GPT-4o-mini, cached)&lt;/li>
&lt;li>Total latency: ~350ms (only for &amp;ldquo;Other&amp;rdquo; tickets, 20% of volume)&lt;/li>
&lt;/ul>
&lt;h2 id="should-you-build-this">Should you build this?&lt;/h2>
&lt;p>Yes, if:&lt;/p>
&lt;ul>
&lt;li>You have &amp;gt;15% of tickets in an &amp;ldquo;Other&amp;rdquo; category&lt;/li>
&lt;li>Your categories are semantically nuanced (not just keyword-based)&lt;/li>
&lt;li>You need subcategory granularity for routing/SLA/reporting&lt;/li>
&lt;li>You&amp;rsquo;re okay with +300ms latency for edge cases&lt;/li>
&lt;/ul>
&lt;p>No, if:&lt;/p>
&lt;ul>
&lt;li>Keywords cover 95%+ of your tickets accurately (I envy you because of your well-behaving user)&lt;/li>
&lt;li>You have &amp;lt;5 main categories&lt;/li>
&lt;li>You need sub-100ms classification (use rules)&lt;/li>
&lt;li>Your taxonomy changes weekly (retraining is manual)&lt;/li>
&lt;/ul>
&lt;h2 id="the-code">The code&lt;/h2>
&lt;p>Everything&amp;rsquo;s open source in my &lt;a href="https://github.com/LuiseFreese/espc25-smart-support-agent">espc25-smart-support-agent repo&lt;/a>.&lt;/p>
&lt;h2 id="the-bottom-line">The bottom line&lt;/h2>
&lt;p>Keyword-based classification is great. Embedding-based classification is powerful. Adding LLM validation takes it from good to production-ready.&lt;/p>
&lt;p>Plus, watching a system correctly classify &amp;ldquo;My laptop&amp;rsquo;s docking station won&amp;rsquo;t recognize the second monitor&amp;rdquo; as Hardware / Docking Station with 90.8% confidence (base: 64%, validation: EXCELLENT) feels pretty damn good.&lt;/p>
&lt;p>Now go build something smarter than keyword matching. Your support team will thank you.&lt;/p>
&lt;p>Happy coding!&lt;/p></description></item><item><title>Secretless cross-tenant dataverse access</title><link>https://m365princess.com/blogs/multitenant-dataverse/</link><pubDate>Wed, 22 Oct 2025 17:28:08 +0000</pubDate><guid>https://m365princess.com/blogs/multitenant-dataverse/</guid><description>&lt;p>Client secrets are like hiding your house key under the mat; easy to grab and impossible to audit. Certificates are just slightly better, because they at least prove who made the key. Until, of course, someone forgets where it’s stored. Even Azure Key Vault, while fantastic, doesn’t eliminate the problem; it just relocates it. So while secrets always need a mechanism on where and how to store, distribute and rotate, I rather prefer solutions that are totally secretless.&lt;/p>
&lt;p>For today&amp;rsquo;s scenario, I want to call Dataverse in a completely different Entra tenant without ever storing a secret or managing a certificate. The setup is a User-Assigned Managed Identity in your operational tenant (let’s call it Tenant A), a multi-tenant app registration with a Federated Identity Credential configured in Tenant B, and an Azure Function that ties it all together.&lt;/p>
&lt;h2 id="the-zero-secret-pattern">The zero-secret pattern&lt;/h2>
&lt;p>Here’s what happens under the hood: The Azure Function in Tenant A uses its managed identity to request a token for &lt;code>api://AzureADTokenExchange/.default&lt;/code>. That token is then exchanged for a Dataverse access token in Tenant B, validated by the FIC, which knows to trust this specific identity from that specific tenant. Dataverse then maps the resulting service principal to an Application User, and by that your cross-tenant API call works:securely, traceably, and without a single secret involved.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-mermaid" data-lang="mermaid">flowchart TB
subgraph TA[&amp;#34;TENANT A (your Azure subscription)&amp;#34;]
FA[&amp;#34;Function App&amp;lt;br/&amp;gt;(TypeScript)&amp;#34;]
UAMI[&amp;#34;User-Assigned Managed Identity&amp;#34;]
APP[&amp;#34;App Registration&amp;lt;br/&amp;gt;(multitenant)&amp;#34;]
FIC[&amp;#34;Federated Identity Credential&amp;#34;]
FA --&amp;gt;|&amp;#34;uses&amp;#34;| UAMI
UAMI --&amp;gt;|&amp;#34;token for api://AzureADTokenExchange/.default&amp;#34;| APP
APP --- FIC
end
subgraph TB[&amp;#34;TENANT B (Dataverse tenant)&amp;#34;]
SP[&amp;#34;Service Principal&amp;#34;]
AU[&amp;#34;Dataverse Application User&amp;#34;]
DV[&amp;#34;Dataverse Web API&amp;#34;]
SP --&amp;gt;|&amp;#34;mapped to&amp;#34;| AU
AU --&amp;gt; DV
end
FA -. HTTPS request .-&amp;gt; TB
UAMI ==&amp;gt;|&amp;#34;token exchange&amp;#34;| SP
FA --&amp;gt;|&amp;#34;calls with issued token&amp;#34;| DV
DV --&amp;gt;|&amp;#34;returns data&amp;#34;| FA
&lt;/code>&lt;/pre>&lt;h3 id="one-command-deployment">One-command deployment&lt;/h3>
&lt;p>To make things super easy for you (and a bit more challenging for me), you can deploy all of that goodness in a single PowerShell command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="nb">deploy-full&lt;/span>&lt;span class="n">-automation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">ps1&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">-TenantAId&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your-tenant-a-id&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">-TenantBId&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your-tenant-b-id&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">-DataverseBaseUrl&lt;/span> &lt;span class="s2">&amp;#34;https://yourorg.crm.dynamics.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The script creates the Function App and User-assigned Managed Identity in Tenant A, registers a multi-tenant app without any API permissions (&lt;a href="https://www.m365princess.com/blogs/2022-07-25-why-your-service-principal-doesnt-need-a-dynamics-user_impersonation-scope/">that part is important&lt;/a>), configures the federated credential, sets up the service principal in Tenant B, deploys the TypeScript function, creates the corresponding Dataverse Application User and assigns it with a security role.&lt;/p>
&lt;p>When you’re done experimenting, one cleanup command wipes everything:&lt;/p>
&lt;p>&lt;code>.\cleanup-all.ps1&lt;/code>&lt;/p>
&lt;p>You can find the full code in this &lt;a href="https://github.com/LuiseFreese/Managed-Identity-Samples/tree/main/sample-4-cross-tenant-dataverse">GitHub repo&lt;/a>.&lt;/p>
&lt;h2 id="whats-actually-running">What’s actually running&lt;/h2>
&lt;p>In Tenant A, you end up with a neat little cluster: a resource group, a Function App on Linux Consumption (Node 20), a user-assigned managed identity, and the multitenant app registration that contains the federated identity credential. Tenant B only needs two things: a service principal created automatically during setup, and a Dataverse Application User that maps to it with a role of your choice. Start with System Customizer if you just want to test; trim it down later for least privilege.&lt;/p>
&lt;h2 id="testing-the-setup">Testing the setup&lt;/h2>
&lt;p>If deployment finishes successfully, the easiest test is to run a simple WhoAmI request through your function:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$functionUrl&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://func-dvproxy-prod.azurewebsites.net&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$whoami&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Invoke-RestMethod&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$functionUrl&lt;/span>&lt;span class="s2">/api/dataverseproxy?path=WhoAmI&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;User ID: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$whoami&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UserId&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once that returns something, try querying accounts or system users. If it works, you just built a completely secretless integration across Microsoft Entra tenants.&lt;/p>
&lt;h2 id="the-why-behind-the-pattern">The why behind the pattern&lt;/h2>
&lt;p>This pattern isn’t just about security, but about getting out of the business of secret-babysitting. Every secret rotation process you eliminate is one less operational cliff edge. Managed identities and federated credentials make identity the platform’s problem again, where it belongs. You control the trust relationship; Entra handles the token lifecycle; Dataverse handles the mapping. No &lt;em>oh no, it expired again&lt;/em> moments. (been there, done that 🙄)&lt;/p>
&lt;p>No more secrets :-)&lt;/p></description></item><item><title>How Azure CLI handles your tokens and what you might be ignoring</title><link>https://m365princess.com/blogs/accesstoken/</link><pubDate>Sun, 19 Oct 2025 15:34:47 +0000</pubDate><guid>https://m365princess.com/blogs/accesstoken/</guid><description>&lt;p>Running &lt;code>az login&lt;/code> feels like magic. A browser pops up, you pick an account, and from then on, everything &lt;em>just works&lt;/em>. No more passwords, no more pop-ups. It’s frictionless and we all love it. But convenience always has a price, and in this case, the price is understanding what the CLI actually does with your tokens behind the scenes.&lt;/p>
&lt;h3 id="what-really-happens-when-you-log-in">What really happens when you log in&lt;/h3>
&lt;p>Azure CLI doesn’t stash your password somewhere clever. It does a full OAuth 2.0 handshake, picking up two pieces of gold: an &lt;strong>access token&lt;/strong> and a &lt;strong>refresh token&lt;/strong>. The access token is short-lived; the refresh token isn’t. The refresh token can quietly mint new access tokens whenever needed, which is why your login survives reboots, coffee breaks, and even mild existential crises.&lt;/p>
&lt;p>Under the hood, Azure CLI leans on the &lt;a href="https://learn.microsoft.com/cli/azure/msal-based-azure-cli?view=azure-cli-latest">Microsoft Authentication Library (MSAL)&lt;/a>. On Windows, MSAL stores tokens in your profile, usually in
&lt;code>C:\Users\&amp;lt;username&amp;gt;\.azure\msal_token_cache.bin&lt;/code> and encrypts them using DPAPI, bound to your Windows account.&lt;/p>
&lt;p>Good to know though: Anything running under &lt;em>your&lt;/em> user context can decrypt and use those tokens. That’s not a vulnerability per se, but how single sign-on (SSO). But it also means any shady script, extension, or compromised process running as you can impersonate you in Azure.&lt;/p>
&lt;p>To mitigate risks, admins should enable &lt;a href="https://learn.microsoft.com//entra/identity/conditional-access/concept-authentication-flows">Continuous Access Evaluation&lt;/a> to kill risky sessions instantly.&lt;/p>
&lt;h3 id="convenience-that-quietly-overstays-its-welcome">Convenience that quietly overstays its welcome&lt;/h3>
&lt;p>Azure CLI keeps refreshing your tokens in the background, which means you get the infinite &lt;em>it still works&lt;/em> experience. On your personal laptop, fine. On a shared VM, lab PC, or reused developer image: Absolutely not fine.&lt;/p>
&lt;p>A forgotten &lt;code>.azure&lt;/code> folder or copied user profile can carry valid tokens into the next session. And since refresh tokens can mint new access tokens, anyone finding that cache gets to enjoy being you in Azure for as long as the token lives.&lt;/p>
&lt;p>The fix is boring but effective: &lt;code>az logout&lt;/code> before leaving a machine. Better yet, nuke the &lt;code>.azure&lt;/code> folder if you’re done for good, especially before imaging VMs or sharing environments.&lt;/p>
&lt;h3 id="device-code-flow--the-silent-broker">Device code flow &amp;amp; the silent broker&lt;/h3>
&lt;p>Obviously, there’s more than one way to sign in. The usual browser login is fine, but users will also meet &lt;strong>device code flow&lt;/strong> and &lt;strong>WAM (Web Account Manager)&lt;/strong> sessions.&lt;/p>
&lt;p>&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-device-code">Device code flow&lt;/a> means you go to &lt;code>https://microsoft.com/devicelogin&lt;/code> and paste a code. Simple, but dangerously easy to abuse. If someone tricks you into entering a code, they just stole a token and your identity.&lt;/p>
&lt;p>&lt;a href="https://learn.microsoft.com/en-us/windows/uwp/security/web-account-manager">WAM&lt;/a> goes even deeper, wiring the CLI straight into your Windows session. That&amp;rsquo;s again slick, invisible, and automatically grants other MSAL-based apps (VS Code, PowerShell, Visual Studio) access to the same credentials, as they all share the same cache. That’s why signing in once magically authenticates them all. It’s also why signing out of one doesn’t guarantee the others are clean 🤯.&lt;/p>
&lt;p>&lt;img alt="woman yelling at cat meme" src="https://m365princess.com/images/azloginmeme.jpg">&lt;/p>
&lt;p>If one tool or extension is compromised, your tokens are fair game. Restrict profile access, isolate testing environments, and don’t treat &lt;code>.azure&lt;/code> as harmless clutter.&lt;/p>
&lt;p>Want guardrails? Use Conditional Access. Enforce MFA, block device code flow on unmanaged machines, and set token lifetimes that actually expire.&lt;/p>
&lt;h3 id="scopes-tenants-and-why-does-my-script-suddenly-fail">Scopes, tenants, and &lt;em>why does my script suddenly fail?&lt;/em>&lt;/h3>
&lt;p>Not every token is equal. Each is tied to a &lt;strong>specific resource&lt;/strong> (scope). By default, &lt;code>az account get-access-token&lt;/code> gets you one for Azure Resource Manager. Need Microsoft Graph or Key Vault? Ask explicitly.&lt;/p>
&lt;p>Mixing tenants and subscriptions makes this messier. Old tokens hang around, new ones join the party, and suddenly you’re running commands in the wrong tenant. Always check &lt;code>az account show&lt;/code> before doing anything destructive. (Ask me how I know.)&lt;/p>
&lt;h3 id="automation--your-personal-login">Automation ≠ your personal login&lt;/h3>
&lt;p>User tokens don’t belong in pipelines. Ever. No, also not even for your cute little POC. We know those land in production. Always. Use &lt;strong>Service principals&lt;/strong>, &lt;strong>Managed identities&lt;/strong>, or &lt;strong>Workload identity federation&lt;/strong> instead. In case you need a refresher:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Service principals&lt;/strong> are fine-grained, auditable, secret-rotatable identities. Don&amp;rsquo;t know how to rotate a secret? &lt;a href="https://www.m365princess.com/blogs/secret-rotation/">I got you&lt;/a>!&lt;/li>
&lt;li>&lt;strong>Managed identities&lt;/strong> means no secrets at all; Azure handles the handshake for you. My preferred way!&lt;/li>
&lt;li>&lt;strong>Workload identity federation&lt;/strong> for external platforms (like GitHub Actions) that authenticate to Entra ID without storing tokens.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Short version: Stop letting your personal refresh tokens sit in some build agent’s cache.&lt;/p>
&lt;/blockquote>
&lt;h3 id="the-bottom-line">The bottom line&lt;/h3>
&lt;p>The Azure CLI is doing exactly what it should: making identity easy. The problem is that &lt;em>easy&lt;/em> and &lt;em>secure&lt;/em> don’t always overlap.&lt;/p>
&lt;p>Know where your tokens live. Know how long they last. Separate human and machine identities. And stop trusting convenience to make security decisions for you.&lt;/p></description></item><item><title>How Dev Proxy teaches you to make your apps more resilient</title><link>https://m365princess.com/blogs/devproxy/</link><pubDate>Wed, 08 Oct 2025 10:19:57 +0000</pubDate><guid>https://m365princess.com/blogs/devproxy/</guid><description>&lt;p>I added &lt;a href="https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/overview">Microsoft Dev Proxy&lt;/a> to my &lt;a href="https://mermaid2dataverse.netlify.app/">Mermaid → Dataverse converter&lt;/a>, because I wanted to test how it handled rate limits and API errors. What I got instead was literally an intervention.&lt;/p>
&lt;p>For everyone who doesn&amp;rsquo;t know what this solution is about: The converter takes an Entity-Relationship-Diagram written with &lt;a href="https://mermaid.js.org/syntax/entityRelationshipDiagram.html">MermaidJS&lt;/a> as an input, parses the information and passes everything into the Dataverse Web API so that all the tables, relationships, columns, global choices and more are created automatically. Additionally, I built an easy rollback process, that allows users to granularly undo the creation of global choices, tables, solution and publisher.&lt;/p>
&lt;p>So now let&amp;rsquo;s discuss what this has to do with Dev Proxy: Dev Proxy didn’t just test resilience, but it forced me to see how fragile my app really was in some regards. Timeouts, blocking rollbacks, and missing feedback: everything that quietly worked until real stress hit.&lt;/p>
&lt;h2 id="the-moment-of-truth">The moment of truth&lt;/h2>
&lt;p>When Dev Proxy started simulating rate limiting and random API failures, my easy rollback process fell apart. Azure killed long-running connections after 230 seconds, throwing 504 errors even though the rollback still finished in the background. Users thought it failed. In fact, it hadn’t. That sparks confidence! /s&lt;/p>
&lt;p>So I rebuilt it properly:&lt;/p>
&lt;ul>
&lt;li>Async rollback pattern: returns &lt;code>202 Accepted&lt;/code> immediately and runs in the background&lt;/li>
&lt;li>Status tracking: added &lt;code>/api/rollback/:id/status&lt;/code> with progress across five phases: relationships, entities, global choices, solution, and publisher&lt;/li>
&lt;li>Frontend polling: updated the UI to check progress every 2 seconds&lt;/li>
&lt;/ul>
&lt;p>Now I do not get any fake failures, no more hanging requests, but a smoother experience all around.&lt;/p>
&lt;p>Once Dev Proxy had me sweating, other weak spots showed up fast. Rate limiting revealed that some rollbacks could take up to ten minutes, so I bumped the API timeout - thank Dataverse API for not allowing any concurrent actions 😘. Every test Dev Proxy threw at me exposed another spot where I’d trusted luck instead of design. A result of that is, that I implemented the changes that now make the solution way more resilient.&lt;/p>
&lt;h2 id="my-two-cents">My two cents&lt;/h2>
&lt;blockquote>
&lt;p>Dev Proxy didn’t just test my app; it taught me how to handle reality better.&lt;/p>
&lt;/blockquote>
&lt;p>Every simulated failure revealed an assumption I didn’t know I was making in the first place. So they say it&amp;rsquo;s a testing tool, but it’s more an architectural therapist. It doesn’t just break things but helps you understand why they were fragile in the first place.&lt;/p>
&lt;p>&lt;em>Happy coding!&lt;/em>&lt;/p></description></item><item><title>Building Azure functions that never store secrets — ever</title><link>https://m365princess.com/blogs/graph-mi/</link><pubDate>Thu, 25 Sep 2025 07:33:51 +0000</pubDate><guid>https://m365princess.com/blogs/graph-mi/</guid><description>&lt;blockquote>
&lt;p>What if your function could hit Microsoft Graph with &lt;strong>no&lt;/strong> client secrets, &lt;strong>no&lt;/strong> certs, and &lt;strong>no&lt;/strong> Key Vault entries? That is exactly what a Managed identity is for: Azure issues short-lived tokens to your app on demand, and the platform rotates and protects the underlying credentials for you.&lt;/p>
&lt;/blockquote>
&lt;h2 id="the-problem-credential-hell">The problem: credential hell&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// the old way — credential nightmare
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">clientSecret&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CLIENT_SECRET&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 🚨 secret in env vars
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">certificatePath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;./certs/app-cert.pfx&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 🚨 cert lifecycle
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">connectionString&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;DefaultEndpointsProtocol=…&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 🚨 another secret
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// what goes wrong?
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - secrets expire; prod breaks
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - certificates need attention
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - secrets get committed to git (oops)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - Key Vault becomes a hard dependency
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// - drift across environments
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>💡 Secrets create operational drag and failure modes you do not need in Azure. Managed identity removes them.&lt;/p>
&lt;h2 id="the-solution-managed-identity-in-one-picture">The solution: Managed identity in one picture&lt;/h2>
&lt;p>At runtime your function asks the platform for a token; Azure verifies the function’s identity and returns a short-lived OAuth 2.0 access token for Microsoft Graph, which your code uses as a bearer token. No app-held secret is involved.&lt;/p>
&lt;p>&lt;img alt="mi-sequence diagram" src="https://m365princess.com/images/mi-sequence.png">&lt;/p>
&lt;h2 id="minimal-code-fetch-a-token-call-graph">Minimal code: fetch a token, call graph&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="kr">type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">AzureFunction&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">HttpRequest&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@azure/functions&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">httpTrigger&lt;/span>: &lt;span class="kt">AzureFunction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>: &lt;span class="kt">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span>: &lt;span class="kt">HttpRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// IMDS pattern; works broadly, especially on VMs and newer stacks
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">tokenUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://169.254.169.254/metadata/identity/oauth2/token&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;?api-version=2018-02-01&amp;amp;resource=https://graph.microsoft.com/&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">tokenResp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tokenUrl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;Metadata&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">access_token&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">tokenResp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">resp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/groups&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Authorization&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="sb">`Bearer &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">access_token&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">status&lt;/span>: &lt;span class="kt">resp.status&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">await&lt;/span> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Why this is safe: the Instance Metadata Service (IMDS) lives at a non-routable address &lt;code>169.254.169.254&lt;/code>, is reachable only from inside the host, and communication to IMDS never leaves the host. The response includes &lt;code>expires_in&lt;/code> (typically &lt;code>3599&lt;/code> seconds), roughly an hour.&lt;/p>
&lt;blockquote>
&lt;p>Production tip: on App Service/Functions prefer the &lt;strong>local identity endpoint&lt;/strong> (&lt;code>IDENTITY_ENDPOINT&lt;/code> + &lt;code>X-IDENTITY-HEADER&lt;/code>), which the runtime exposes for you. The Azure Identity SDK (&lt;code>DefaultAzureCredential&lt;/code> or &lt;code>ManagedIdentityCredential&lt;/code>) wraps this nicely.&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// pattern for App Service/Functions local identity endpoint
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">IDENTITY_ENDPOINT&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">IDENTITY_HEADER&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">getGraphToken&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">IDENTITY_ENDPOINT&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">IDENTITY_HEADER&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">u&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">IDENTITY_ENDPOINT&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">?api-version=2019-08-01&amp;amp;resource=https://graph.microsoft.com/`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">u&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;X-IDENTITY-HEADER&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">IDENTITY_HEADER&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">j&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">j&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">access_token&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// IMDS fallback
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">imds&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://169.254.169.254/metadata/identity/oauth2/token&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;?api-version=2018-02-01&amp;amp;resource=https://graph.microsoft.com/&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">imds&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;Metadata&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">j&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">j&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">access_token&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="the-bootstrap-who-gives-the-function-permission-to-call-graph">The bootstrap: Who gives the function permission to call graph?&lt;/h2>
&lt;p>The Managed identity eliminates stored credentials, but it does not auto-grant API permissions. You must assign Microsoft Graph application permissions (app roles) to the function’s service principal at deploy time. Use the Graph app role assignment API and the least-privileged permissions to call it.&lt;/p>
&lt;h3 id="required-rights-for-the-caller">Required rights for the caller&lt;/h3>
&lt;p>Use &lt;code>AppRoleAssignment.ReadWrite.All&lt;/code> plus &lt;code>Application.Read.All&lt;/code>, or run under a directory role such as &lt;strong>Cloud Application Administrator&lt;/strong> or &lt;strong>Application Administrator&lt;/strong>. These are the least-privileged choices documented for assigning app roles.&lt;/p>
&lt;h3 id="reliably-target-the-graph-service-principal">Reliably target the Graph service principal&lt;/h3>
&lt;p>Do not search by display name. Address Microsoft Graph by its well-known &lt;strong>appId&lt;/strong> and query its app roles:&lt;/p>
&lt;p>&lt;code>servicePrincipals(appId='00000003-0000-0000-c000-000000000000')&lt;/code>&lt;/p>
&lt;h3 id="three-ids-you-need-for-each-assignment">Three IDs you need for each assignment&lt;/h3>
&lt;ul>
&lt;li>&lt;code>principalId&lt;/code>: your function app’s managed identity &lt;strong>service principal id&lt;/strong>&lt;/li>
&lt;li>&lt;code>resourceId&lt;/code>: the &lt;strong>Graph&lt;/strong> service principal id&lt;/li>
&lt;li>&lt;code>appRoleId&lt;/code>: the &lt;strong>specific Graph application permission&lt;/strong> you want (for example, &lt;code>Directory.Read.All&lt;/code>), found in Graph’s &lt;code>appRoles&lt;/code> collection&lt;/li>
&lt;/ul>
&lt;h3 id="example-assign-directoryreadall-and-groupreadall-to-the-functions-identity">Example: assign &lt;code>Directory.Read.All&lt;/code> and &lt;code>Group.Read.All&lt;/code> to the function’s identity&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$graphSp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">az&lt;/span> &lt;span class="n">rest&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-method&lt;/span> &lt;span class="n">GET&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-uri&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/servicePrincipals(appId=&amp;#39;00000003-0000-0000-c000-000000000000&amp;#39;)`?&lt;/span>&lt;span class="nv">$select&lt;/span>&lt;span class="s2">=id,appRoles&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-query&lt;/span> &lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span> &lt;span class="n">-o&lt;/span> &lt;span class="n">tsv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># function&amp;#39;s managed identity service principal id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$miSpId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$principalId&lt;/span> &lt;span class="c"># e.g., from your Bicep output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># find the appRoleIds you need programmatically &lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$roles&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">az&lt;/span> &lt;span class="n">rest&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-method&lt;/span> &lt;span class="n">GET&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-uri&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/servicePrincipals/&lt;/span>&lt;span class="nv">$graphSp&lt;/span>&lt;span class="s2">?&lt;/span>&lt;span class="se">`$&lt;/span>&lt;span class="s2">select=appRoles&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-query&lt;/span> &lt;span class="s2">&amp;#34;appRoles[?value==&amp;#39;Directory.Read.All&amp;#39; || value==&amp;#39;Group.Read.All&amp;#39;].{value:value,id:id}&amp;#34;&lt;/span> &lt;span class="n">-o&lt;/span> &lt;span class="n">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># assign each role&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$roles&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">ConvertFrom-Json&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">ForEach-Object&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$payload&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">{&lt;/span> &lt;span class="n">principalId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$miSpId&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">resourceId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$graphSp&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">appRoleId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">id&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">ConvertTo-Json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$tmp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">New-TemporaryFile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$payload&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Out-File&lt;/span> &lt;span class="n">-FilePath&lt;/span> &lt;span class="nv">$tmp&lt;/span> &lt;span class="n">-Encoding&lt;/span> &lt;span class="n">utf8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">az&lt;/span> &lt;span class="n">rest&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-method&lt;/span> &lt;span class="n">POST&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-uri&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/servicePrincipals/&lt;/span>&lt;span class="nv">$miSpId&lt;/span>&lt;span class="s2">/appRoleAssignments&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-headers&lt;/span> &lt;span class="s2">&amp;#34;Content-Type=application/json&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-body&lt;/span> &lt;span class="s2">&amp;#34;@&lt;/span>&lt;span class="nv">$tmp&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Out-Null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Remove-Item&lt;/span> &lt;span class="nv">$tmp&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="security-model-what-is-and-is-not-leaving-your-app">Security model; what is and is not leaving your app&lt;/h2>
&lt;ul>
&lt;li>No long-lived credentials in your app. The underlying SP keys and certs are platform-managed; your code never handles them&lt;/li>
&lt;li>Token requests stay local to the host. Calls to IMDS use &lt;code>169.254.169.254&lt;/code> and never leave the host&lt;/li>
&lt;li>Access tokens are bearer tokens. You do receive the access token in your app; protect it like any other bearer token. The win here is that it is short-lived and you do not store a secret. The platform rotates the identity for you&lt;/li>
&lt;/ul>
&lt;h2 id="why-this-matters">Why this matters&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">traditional&lt;/th>
&lt;th style="text-align: left">zero-credential (managed identity)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">secrets in code/config/Key Vault&lt;/td>
&lt;td style="text-align: left">no app-held secrets; platform-issued tokens&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">manual rotation of secrets/certs&lt;/td>
&lt;td style="text-align: left">platform rotates credentials; you request new tokens when needed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">risk of leaks via git/logs&lt;/td>
&lt;td style="text-align: left">no long-lived secrets to leak; token request is host-local&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">secret management infra&lt;/td>
&lt;td style="text-align: left">none for Graph; you manage &lt;strong>permissions&lt;/strong>, not secrets&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">outages from expired secrets&lt;/td>
&lt;td style="text-align: left">short-lived tokens; transparent renewal via the platform&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="grab-and-go-snippets">Grab-and-go snippets&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>IMDS token request&lt;/strong> (HTTP semantics, including &lt;code>expires_in&lt;/code>):
&lt;code>GET http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&amp;amp;resource=https://graph.microsoft.com/&lt;/code> with header &lt;code>Metadata: true&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>App Service/Functions identity endpoint&lt;/strong> (raw HTTP example, required headers):
&lt;code>GET /MSI/token?resource=https://graph.microsoft.com/&amp;amp;api-version=2019-08-01&lt;/code> with header &lt;code>X-IDENTITY-HEADER: &amp;lt;IDENTITY_HEADER&amp;gt;&lt;/code> to the host in &lt;code>IDENTITY_ENDPOINT&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Assign Graph app role to a service principal&lt;/strong> and required permissions for the caller:
&lt;code>POST https://graph.microsoft.com/v1.0/servicePrincipals/{id}/appRoleAssignments&lt;/code> with &lt;code>principalId&lt;/code>, &lt;code>resourceId&lt;/code>, and &lt;code>appRoleId&lt;/code>; caller needs &lt;code>AppRoleAssignment.ReadWrite.All&lt;/code> + &lt;code>Application.Read.All&lt;/code> (or a suitable directory role).&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>And now, please stop using secrets if you don&amp;rsquo;t have to 😇&lt;/p></description></item><item><title>Introducing Mermaid to Dataverse Converter</title><link>https://m365princess.com/blogs/mermaid/</link><pubDate>Wed, 10 Sep 2025 17:33:51 +0000</pubDate><guid>https://m365princess.com/blogs/mermaid/</guid><description>&lt;h2 id="why-diagrams-matter-and-why-they-usually-fail-us">Why diagrams matter (and why they usually fail us)&lt;/h2>
&lt;p>Entity-Relationship Diagrams (ERDs) are the universal shorthand for talking about data models. They show what entities exist, which attributes they carry, and how they connect to each other. In workshops, they are invaluable: they help everyone get on the same page, and they force the team to make decisions about terminology early on. Is it a Customer or an Account? Do we store Address inside the Order or separately? These are the conversations you want to have at the whiteboard.&lt;/p>
&lt;p>The tragedy is that most ERDs end their lives inside a Visio file, a Confluence page, or, if you’re truly unlucky, a PowerPoint deck. They look polished for the kickoff meeting. Six weeks later they’re a lie. The system has moved on, but the diagram is frozen in time. Nobody trusts it anymore. What was supposed to be the map has become a fossil.&lt;/p>
&lt;h2 id="mermaid-diagrams-that-behave-like-code">Mermaid: diagrams that behave like code&lt;/h2>
&lt;p>&lt;a href="https://mermaid.js.org/">Mermaid.js&lt;/a> changes the game because it treats diagrams as text. Instead of dragging rectangles around a canvas, you write a few lines that describe your model. Those lines render into a clean diagram, every time. No one is fiddling with connectors or fighting layout engines; the syntax is simple, readable, and versionable.&lt;/p>
&lt;p>&lt;img alt="mermaid diagram" src="https://m365princess.com/images/mermaid-dataverse-pink.png">&lt;/p>
&lt;p>What you see here isn’t a static sketch. It’s code. You can put it into Git, track changes, open pull requests and more. The diagram becomes part of the software, evolving alongside the rest of the project. That alone is already a big step forward from Visio archaeology.&lt;/p>
&lt;h2 id="from-drawing-to-deployment">From drawing to deployment&lt;/h2>
&lt;p>But I didn’t want to stop at documentation. I built the &lt;a href="https://github.com/luisefreese/mermaid">Mermaid to Dataverse Converter&lt;/a>, an Azure App Service that takes your Mermaid ERD and provisions the schema directly into Microsoft Dataverse.&lt;/p>
&lt;p>&lt;img alt="Mermaid to Dataverse Converter" src="https://m365princess.com/images/mermaid-converter-final.png">&lt;/p>
&lt;p>The diagram that once lived in a deck now becomes the thing that creates your environment. The converter generates publishers and solutions, defines entities with their attributes, provisions one-to-many and many-to-many relationships, respects global choice sets, and even maps to existing Common Data Model entities where possible. What started as a design artifact in a customer meeting ends up as a working Dataverse solution a few minutes later.&lt;/p>
&lt;h3 id="what-about-plan-designer">What about plan designer?&lt;/h3>
&lt;p>If you’ve been following the Power Platform updates, you’ve probably seen &lt;a href="https://learn.microsoft.com/power-apps/maker/plan-designer/plan-designer">Plan Designer&lt;/a>. It’s Microsoft’s AI-driven canvas for solution design. You type in a prompt, sometimes upload an image, and it drafts a whole plan for you: tables, relationships, apps, flows, even a portal. It’s impressive, and for quick ideation it’s a fantastic tool.&lt;/p>
&lt;p>But prompts are not code. They look like they could be versioned, but they aren’t deterministic. The same words might give you a different schema tomorrow. You can check a prompt into Git, but you can’t guarantee the output will be identical next time you run it. That makes deployments unpredictable.&lt;/p>
&lt;p>You can call it a proof of concept or an MVP, and that’s fine if it stays a sketch. The reality, though, is that proofs of concept have a bad habit of sneaking into production. Once users see something that works, they keep using it. Suddenly you’ve got live data sitting on a nondeterministic schema that you can’t repeat or redeploy with confidence.&lt;/p>
&lt;p>&lt;a href="https://github.com/luisefreese/mermaid">Mermaid to Dataverse Converter&lt;/a> takes the opposite path. The file is text. The output is deterministic. The same diagram yields the same schema every time, no surprises. It can be versioned properly, reviewed properly, and deployed properly. That’s the difference between a sketchpad and infrastructure. Plan Designer is great for sparking ideas; &lt;a href="https://github.com/luisefreese/mermaid">Mermaid to Dataverse Converter&lt;/a> is what you reach for when you want something you can trust on a &lt;a href="https://www.m365princess.com/blogs/yolo-deploy-friday/">Friday night rollout&lt;/a>.&lt;/p>
&lt;h3 id="documentation-as-infrastructure">documentation as infrastructure&lt;/h3>
&lt;p>Because Mermaid is plain text, it slides neatly into the practices we already use for code. You commit it, version it, and push it through a pipeline. Every schema change becomes visible in source control, every adjustment is reviewable, and every environment can be provisioned automatically from the same source.&lt;/p>
&lt;p>This is what Infrastructure as Code looks like in the Dataverse world. The ERD is no longer a sketch you present to stakeholders. It’s the blueprint that shapes the environment, always up to date because it has to be. The usual drift between documentation and reality disappears, because they are literally the same thing.&lt;/p>
&lt;h3 id="rage-against-the-docs">Rage against the docs&lt;/h3>
&lt;p>For too long we’ve tolerated diagrams that die in SharePoint folders and slide decks that mislead more than they guide. We’ve accepted documentation as a necessary evil that will always be out of date. That’s nonsense. Documentation should be as alive as the system it describes. It should be part of the build, not a side note.&lt;/p>
&lt;p>Mermaid plus Dataverse finally lets us do that. Define the model, check it in, and let it provision itself. Stop treating diagrams like art projects. Start treating them like infrastructure.&lt;/p>
&lt;h2 id="try-it-out">Try it out&lt;/h2>
&lt;p>The &lt;a href="https://github.com/luisefreese/mermaid">Mermaid to Dataverse Converter&lt;/a> is open source. It&amp;rsquo;s deployed within a few minutes. Grab an ERD, upload it, and run it through the converter. Watch your documentation turn into a Dataverse solution. The next time someone asks for “the latest version of the diagram,” you can point to GitHub, and to the running system, and know they are the same thing.&lt;/p>
&lt;p>Would love to get your feedback! 💖&lt;/p></description></item><item><title>It’s OK to be seen trying</title><link>https://m365princess.com/blogs/seen-trying/</link><pubDate>Thu, 31 Jul 2025 10:45:00 +0000</pubDate><guid>https://m365princess.com/blogs/seen-trying/</guid><description>&lt;h1 id="its-ok-to-be-seen-trying">It’s OK to be seen trying&lt;/h1>
&lt;p>Somewhere along the way, we started believing that you’re only allowed to speak &lt;em>after&lt;/em> you’ve figured everything out. That you can only post once the product is perfect. That you can only ask a question if it’s smart enough, and that you can only contribute once you have “value”: measurable, monetizable, LinkedIn-optimizable value.&lt;/p>
&lt;p>And until then? Stay quiet 🤫. Watch. Lurk politely in the back row of the internet.&lt;/p>
&lt;p>My suggestion: Let’s kill that idea.&lt;/p>
&lt;h2 id="perfectionism-kills-participation">Perfectionism kills participation&lt;/h2>
&lt;p>It’s not that people have nothing to say. It’s that they’re afraid of saying it wrong.&lt;/p>
&lt;p>In many rooms, especially technical ones, there’s this quiet rule: if you don’t already know, don’t speak. And if you do speak, you better be right. Flawlessly, citation-included, TED-Talk-ready right.&lt;/p>
&lt;p>The result? Smart, curious people stay silent. Teams lose out on diverse thinking. Communities become echo chambers for the already-confident.&lt;/p>
&lt;h2 id="building-in-public-is-a-power-move-even-when-its-messy">Building in public is a power move (even when it’s messy)&lt;/h2>
&lt;p>When you share half-baked ideas, drafts, ugly prototypes, something magical happens. You signal that it’s &lt;em>safe&lt;/em> to learn here. That trying counts. That progress matters more than polish. You also make space for feedback, collaboration, and the kind of honesty you can’t fake in a keynote. This doesn’t mean performative “look at me failing” posts. It means showing the process. Being real. Building &lt;em>during&lt;/em>, not just after.&lt;/p>
&lt;h2 id="yes-the-reply-guys-will-show-up">Yes, the reply guys will show up&lt;/h2>
&lt;p>The “Actually…” brigade is always ready. The guy correcting your grammar. The one telling you that your take “lacks nuance”. The person who misses the point just to feel smarter. They don’t matter.&lt;/p>
&lt;p>If you’re building in public, you &lt;em>will&lt;/em> be corrected. But you’ll also connect with the people who get it, the ones who are walking the same messy path, not standing on the sidelines with a red pen.&lt;/p>
&lt;h2 id="your-voice-matters-even-if-it-shakes">Your voice matters, even if it shakes&lt;/h2>
&lt;p>You don’t need to be an expert to contribute. You don’t need to have it all figured out to post something useful. Sometimes, the most helpful thing you can share is the thing you just learned five minutes ago. It’s fresh. It’s real. It’s not “thought leadership”. but it’s thought &lt;em>in motion&lt;/em>.&lt;/p>
&lt;p>Beginners make the best guides for other beginners. They still remember what it felt like not to know.&lt;/p>
&lt;h2 id="teaching-and-learning-are-not-separate-states">Teaching and learning are not separate states&lt;/h2>
&lt;p>You don’t “arrive” at knowing and then start giving back. It’s a loop. You figure something out. You share it. Someone else teaches you the next piece. Rinse, repeat. The healthiest communities are built by people who are open mid-process. Not the ones holding court, but the ones pulling others up as they climb.&lt;/p>
&lt;h2 id="trying-is-enough-trying-visibly-is-brave">Trying is enough. trying visibly is brave.&lt;/h2>
&lt;blockquote>
&lt;p>“Sometimes I work 40 hours in one day.&lt;br>
Sometimes I need a week to find 4.”&lt;/p>
&lt;/blockquote>
&lt;p>Let go of the myth that it has to look good.&lt;br>
It just has to be &lt;em>honest&lt;/em>. Progress beats polish. And showing up, even with a shaky voice and half a plan, is always better than staying silent. So write the post. Publish the prototype. Ask the question. Be seen trying.&lt;/p>
&lt;p>Because someone out there is waiting for permission to do the same.&lt;/p></description></item><item><title>Stuck in pilot - Part 1: The illusion of progress</title><link>https://m365princess.com/blogs/pilot1/</link><pubDate>Wed, 30 Jul 2025 09:20:51 +0000</pubDate><guid>https://m365princess.com/blogs/pilot1/</guid><description>&lt;blockquote>
&lt;p>“We just need to test the AI. We’ll figure out the data later.”&lt;/p>
&lt;/blockquote>
&lt;p>That sentence has quietly killed more AI pilots than any model failure ever could. Most AI pilots don’t fail because the tech didn’t work. They fail because no one did the groundwork. No clean data. No integration path. No clear problem. No business owner. Just a well-intentioned scramble to “try AI” and show something in a demo, preferably fast. And then everyone wonders why it didn’t go anywhere.&lt;/p>
&lt;h2 id="the-myth-of-the-fast-pilot">The myth of the fast pilot&lt;/h2>
&lt;p>Pilots are often sold as a quick win. Low commitment, high visibility. A chance to show progress without the red tape of production work. But if you skip the prep: if you start before you’ve validated the problem, the data, and the path to value, then you’re not testing AI. You’re testing how far a half-baked prototype can limp before someone shelves it with a polite “let’s revisit this later”. Big sigh!&lt;/p>
&lt;blockquote>
&lt;p>You think you’re being agile. You’re actually being avoidant.&lt;/p>
&lt;/blockquote>
&lt;h2 id="whats-almost-always-missing">What’s almost always missing&lt;/h2>
&lt;p>Let’s get specific. Here are four foundational gaps that quietly ruin most AI pilots before they even start, and unfortunately, I see all of them when my customers show me their pilots.&lt;/p>
&lt;h3 id="1-clean-reliable-data">1. Clean, reliable data&lt;/h3>
&lt;p>Everyone assumes the data is “somewhere”. No one checks if it’s usable, consistent, labeled, or accessible. So when the model performs poorly, people blame the algorithm instead of the dataset. Garbage in, garbage model. We should know by now, that this sentence correctly says: &lt;em>garbage in - garbage out&lt;/em>.&lt;/p>
&lt;h3 id="2-an-integration-plan">2. An integration plan&lt;/h3>
&lt;p>The pilot lives in a beautiful little bubble. It doesn’t connect to the CRM. It doesn’t update any real records. It doesn’t push anything to production systems. So even if it works, it can’t scale, because no one thought about how it would fit into the real-world process.&lt;/p>
&lt;h3 id="3-a-clear-problem-statement">3. A clear problem statement&lt;/h3>
&lt;p>You get goals like “reduce time spent” or “optimize the workflow” or “improve customer experience”. Vague enough to be exciting. But totally unhelpful when you try to define success. What, exactly, are we fixing? How do we measure this?&lt;/p>
&lt;h3 id="4-business-ownership">4. Business ownership&lt;/h3>
&lt;p>When a pilot is purely IT- or innovation-driven, it often lacks a business owner who actually cares about the outcome. No one to champion it. No one to fight for its rollout. No one whose KPIs depend on it. So when priorities shift, the pilot just… &lt;em>dies in corporate&lt;/em>.&lt;/p>
&lt;h2 id="every-pilot-needs-a-contract-even-if-its-just-a-slide">Every pilot needs a contract (even if it’s just a slide)&lt;/h2>
&lt;p>Before you build anything, you should be able to answer five basic questions:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>What problem are we solving? Use plain language. No jargon. No “digital transformation” fluff&lt;/p>
&lt;/li>
&lt;li>
&lt;p>How bad is it right now? What’s the baseline? What’s the pain? What are the symptoms?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>What data are we using? Where does it live? Who owns it? How clean is it? Can we access it?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>How will we test it? What’s in scope? What’s off limits? How will we simulate usage?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>What does success look like? Define the win condition now, not retroactively after the pilot’s already derailed&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>This doesn’t have to be a 10-page deck. But it has to exist, &lt;strong>before&lt;/strong> the pilot starts.&lt;/p>
&lt;h2 id="skipping-the-foundation-isnt-agileits-wishful-thinking">Skipping the foundation isn’t agile—it’s wishful thinking&lt;/h2>
&lt;p>You can’t move fast if you don’t know what you’re building, or for whom. And no amount of generative flair or AI hype will rescue a pilot that’s missing the basics. If your pilot is slow, buggy, or underwhelming, it may not be the tech. It may be the homework you skipped to feel fast.&lt;/p>
&lt;h2 id="closing-thought">Closing thought&lt;/h2>
&lt;blockquote>
&lt;p>You can’t build a second floor when the foundation is still guesswork.&lt;/p>
&lt;/blockquote>
&lt;p>If your pilot’s in trouble, don’t blame the model or the team. Look backward. Because chances are, the cracks were already there before you wrote a single line of code (or clicked yourself through a portal).&lt;/p>
&lt;hr>
&lt;h2 id="more-parts-of-this-series">More parts of this series:&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/pilot0/">Stuck in pilot - Part 0: The comfort of the sandbox&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/pilot1/">Stuck in pilot - Part 1: No foundations, no future&lt;/a> [📍 You are here!]&lt;/li>
&lt;li>Stuck in pilot - Part 2: Fear of success - to be published soon™️&lt;/li>
&lt;li>Stuck in pilot - Part 3: your chatbot isn’t useful - to be published soon™️&lt;/li>
&lt;li>Stuck in pilot - Part 4: from stuck to scaled - to be published soon™️&lt;/li>
&lt;/ul></description></item><item><title>The MVP trap: AI lets you do more, but often worse</title><link>https://m365princess.com/blogs/ai-mvp-trap/</link><pubDate>Wed, 30 Jul 2025 06:00:00 +0000</pubDate><guid>https://m365princess.com/blogs/ai-mvp-trap/</guid><description>&lt;p>There’s a narrative floating around right now: With AI, one person can do the work of a whole team.&lt;/p>
&lt;ul>
&lt;li>Code? Done.&lt;/li>
&lt;li>Design? Prompted.&lt;/li>
&lt;li>Copy? Generated.&lt;/li>
&lt;li>Voice-over, editing, visuals? All stitched together in record time.&lt;/li>
&lt;/ul>
&lt;p>And at first glance, that sounds like progress. AI makes it easier than ever to ship something: quickly, independently, and with just enough polish to post. But it’s also creating a very specific kind of trap.&lt;/p>
&lt;h2 id="the-mvp-pyramid-no-one-wants-to-talk-about">The MVP pyramid no one wants to talk about&lt;/h2>
&lt;p>There’s an image I keep coming back to whenever people start throwing around the word “MVP”.&lt;/p>
&lt;p>&lt;img alt="MVP pyramid: wrong vs right" src="https://m365princess.com/images/mvp-right-wrong.png">&lt;/p>
&lt;p>The left side is what many AI-powered projects are starting to look like: Stacked layers. Functionality without usability. Code without context. Stuff that technically works, but feels empty.&lt;/p>
&lt;p>The right side? That’s how it should work. A small, testable, &lt;em>coherent&lt;/em> slice. Something people can actually use. Something with value, not just features. And right now, AI is making the left-hand path faster, cheaper, and way too tempting.&lt;/p>
&lt;h2 id="shipping-isnt-the-same-as-succeeding">Shipping isn’t the same as succeeding&lt;/h2>
&lt;p>Just because something runs doesn’t mean it works. Just because it’s publishable doesn’t mean it’s worth sharing. And just because AI lets you do everything yourself doesn’t mean you should. We&amp;rsquo;re now surrounded by content and tools that exist purely because they &lt;em>can&lt;/em>, not because they solve a problem well, or even make sense.&lt;/p>
&lt;p>Voice-overs that miss the tone.&lt;br>
Copy that reads like a marketing intern on autopilot (not Copilot)
Interfaces that look decent in a screenshot but fall apart under real use&lt;/p>
&lt;blockquote>
&lt;p>The result? Lots of output. Very little value.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="the-we-dont-need-juniors-myth">The “we don’t need juniors” myth&lt;/h2>
&lt;p>Here’s where it gets dangerous. Some folks are already saying&lt;/p>
&lt;ul>
&lt;li>“If AI can write the code, we don’t need junior devs.”&lt;/li>
&lt;li>“If ChatGPT can write posts, who needs marketing people?”&lt;/li>
&lt;li>“If design is a prompt away, why bother with actual designers?”&lt;/li>
&lt;/ul>
&lt;p>But juniors don’t just write code. They ask the awkward questions. They bring fresh eyes to assumptions. They grow into the people who later build the foundations you depend on. Removing them from the system doesn’t save time; it starves your future talent pipeline.&lt;/p>
&lt;p>Same with marketers, designers, QA, tech writers: anyone who adds clarity, nuance, and experience to what you&amp;rsquo;re building. AI doesn’t replace them. It just replaces your excuses for skipping them.&lt;/p>
&lt;h2 id="ai-lowers-the-cost-of-mediocrity">AI lowers the cost of mediocrity&lt;/h2>
&lt;p>When things were expensive to build, we were careful by default. We tested. Reviewed. Debated. Improved. Now? We generate, skim, and ship. Because it&amp;rsquo;s cheap. Because it&amp;rsquo;s fast. Because it looks good &lt;em>enough&lt;/em>.&lt;/p>
&lt;blockquote>
&lt;p>But doing more isn’t the same as doing better.&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-are-we-actually-building">What are we actually building?&lt;/h2>
&lt;p>AI is here. It’s useful. It’s powerful. But it doesn’t know what matters to your users. It doesn’t care about the edge cases. It won’t ask if your output is accessible, maintainable, or even remotely worth doing. That’s still on you. On your team. On your taste. So the question isn’t “how much can I build now?”, but&lt;/p>
&lt;blockquote>
&lt;p>Am I delivering a meaningful slice of value, or just another bottom layer nobody needs?&lt;/p>
&lt;/blockquote>
&lt;p>Because in the end, tools don’t make good products. People do.&lt;/p></description></item><item><title>The productivity graph is broken and we’re pretending it’s fine</title><link>https://m365princess.com/blogs/productivity-broken/</link><pubDate>Tue, 29 Jul 2025 09:21:04 +0000</pubDate><guid>https://m365princess.com/blogs/productivity-broken/</guid><description>&lt;p>You know that neat little curve in project planning tools that suggests you’ll produce value at a steady pace from Monday to Friday? It’s a lie. Not a malicious lie. Just a completely detached-from-reality one.&lt;/p>
&lt;h2 id="the-myth-of-the-8-hour-brain">The myth of the 8-hour brain&lt;/h2>
&lt;p>Most jobs still pretend that productivity is something that can be sliced neatly into eight equal hours a day. But if you’ve ever done any kind of deep, focused, creative, or strategic work, you already know: it doesn’t work like that.&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>“Sometimes I work 40 hours in one day. Sometimes I need a week to find 4.”&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;p>That quote wasn’t meant to be profound. I just said it out loud once, and people nodded like I’d confessed something they weren’t allowed to say themselves.&lt;/p>
&lt;h2 id="the-1440-reality">the 1–4–40 reality&lt;/h2>
&lt;p>Here’s what it actually looks like for a lot of us:&lt;/p>
&lt;ul>
&lt;li>On some days, you get &lt;strong>1 hour&lt;/strong> of real output, and spend the rest trapped in inboxes, meetings, or brain fog&lt;/li>
&lt;li>Some days, you hit a rhythm and produce &lt;strong>4 hours&lt;/strong> of high-value work.&lt;/li>
&lt;li>Once in a blue moon, you get &lt;strong>40 hours of clarity&lt;/strong> in a single caffeine-fueled sprint and wonder if you’ve unlocked a new dimension&lt;/li>
&lt;/ul>
&lt;p>The problem isn’t the variation. The problem is pretending the variation doesn’t exist, and building performance expectations around a model that’s never been true.&lt;/p>
&lt;h2 id="its-not-about-discipline">It&amp;rsquo;s not about discipline&lt;/h2>
&lt;p>It’s easy to assume this inconsistency means you’re undisciplined. That you need better routines. A stricter schedule. A fancier Pomodoro timer. But what if it’s not about discipline? What if knowledge work is simply unpredictable, because focus, creativity, and problem-solving are not linear resources? You don’t summon insight on command. You don’t plan your most productive hour, it sneaks up on you at 6:43 a.m. in the shower or at 9:19 p.m. when you should be unwinding.&lt;/p>
&lt;p>I found this cartoon a little too relatable :-)&lt;/p>
&lt;p>&lt;img alt="dev productivity by Monkeyuser.com" src="https://m365princess.com/images/dev-productivity.jpg">&lt;/p>
&lt;h2 id="the-cal-newport-effect">The Cal Newport effect&lt;/h2>
&lt;p>One of the best comments I got on this topic was about &lt;em>Deep Work&lt;/em> by Cal Newport: “Real productivity doesn’t mean being busy all day. It means doing focused, meaningful work without distractions.” Exactly 🫳🎤⬇️. And how many hours of that do we realistically get in a typical day? Two, maybe three—if we’re lucky, rested, and no one scheduled a team sync at 9:00 a.m.&lt;/p>
&lt;h2 id="so-how-should-we-think-about-productivity">So how should we think about productivity?&lt;/h2>
&lt;p>Start with this: &lt;strong>value ≠ hours.&lt;/strong> Your job isn’t to fill time. It’s to deliver outcomes, solve problems, make good decisions, and occasionally do something brilliant. And most of that happens in peaks, not in steady state. So instead of fighting the 1–4–40 reality, try designing around it:&lt;/p>
&lt;ul>
&lt;li>Guard your deep work hours like gold - I for example have a maximum of 2 hours of meetings per day - rest is &lt;em>real&lt;/em> work&lt;/li>
&lt;li>Not every hour will be productive, and that’s okay. There is this thing of &lt;em>productive procrastination&lt;/em> where you subconsciously go through all the steps that you aren&amp;rsquo;t ready for, yet. And when the time comes, you processed it so thoroughly, that you exactly know what to do. And then you spread your wings and fly.&lt;/li>
&lt;li>Measure by impact, clarity, and progress, your clock is not a good way to measure productivity.&lt;/li>
&lt;/ul>
&lt;h2 id="lets-stop-pretending">Let’s stop pretending&lt;/h2>
&lt;p>The graph is broken. Our brains don’t work on conveyor belts. And yet, we keep designing work environments, performance metrics, and schedules as if they do.You’re not lazy. You’re not broken. You’re just working in a system that wasn’t built for how people actually think. Maybe it’s time to fix the graph instead of forcing ourselves to fit it.&lt;/p></description></item><item><title>Stuck in pilot - Part 0: The comfort of the sandbox</title><link>https://m365princess.com/blogs/pilot0/</link><pubDate>Sat, 26 Jul 2025 09:20:51 +0000</pubDate><guid>https://m365princess.com/blogs/pilot0/</guid><description>&lt;blockquote>
&lt;p>“Our AI chatbot is live- in a test environment, since &amp;hellip; uhm&amp;hellip; 18 months.”&lt;/p>
&lt;/blockquote>
&lt;p>Sounds familiar?&lt;/p>
&lt;p>If your organization has a hallway legend about a “pilot” project that never saw daylight, you’re not alone. Across industries, AI pilots have become the business equivalent of Schrödinger’s cat: both alive and dead, depending on who’s presenting the PowerPoint. They give the illusion of innovation without requiring anyone to actually commit to change.&lt;/p>
&lt;p>Let’s talk about why pilots feel safe, why that’s a trap, and why &lt;em>not&lt;/em> piloting isn’t the answer either.&lt;/p>
&lt;h2 id="pilots-are-easy-to-love">Pilots are easy to love&lt;/h2>
&lt;p>There’s something comforting about a pilot. It’s non-threatening. Limited in scope. Budget-contained. And most importantly: No one gets fired if it doesn’t work. Pilots are where good ideas go to hide from accountability.&lt;/p>
&lt;p>In a sandbox, you don’t have to think about integrating with your CRM, fixing bad data, or training customer support teams. You can just build a chatbot, or drop an AI summarizer into a Power Automate flow, and feel like you’re part of the future. And hey, if it looks cool enough, maybe someone will even share it in the All Hands meeting. Yay!&lt;/p>
&lt;blockquote>
&lt;p>Pilots without a path to production are organizational theater.&lt;/p>
&lt;/blockquote>
&lt;h2 id="performative-innovation">Performative innovation&lt;/h2>
&lt;p>I call it &lt;em>pilot theater&lt;/em>, the corporate cousin of innovation theater. It’s performative and looks like progress, but it’s just a loop of demo–hype–pause–repeat.&lt;/p>
&lt;p>You spend six months “validating feasibility”, only to realize no one planned for ownership, integration, or ongoing support. So the pilot gets parked. Or worse, someone suggests starting a new pilot. One that’s even more exciting! (And just as doomed.)&lt;/p>
&lt;p>In the meantime, the business still has the same inefficiencies, and same bottlenecks. But now, with the added bonus of AI fatigue.&lt;/p>
&lt;p>&lt;img alt="cartoon on copilot" src="https://m365princess.com/images/copilot-2-days.png">&lt;/p>
&lt;h2 id="the-infamous-use-case-list-wont-save-you">The infamous use case list won’t save you&lt;/h2>
&lt;p>A common symptom of pilot theater is the infamous “AI use case list”. You gather 20, 30, maybe 50 ideas from across the company. Some are genuinely good. Others? Not so much.&lt;/p>
&lt;p>But the problem isn’t the ideas, but the &lt;strong>lack of clarity about what to do with them&lt;/strong>.&lt;/p>
&lt;p>I’ve written about this in detail &lt;a href="https://www.m365princess.com/blogs/usecases/">here&lt;/a>, but in short: Use case lists give the illusion of progress. They help you say, “look at how much we could do!” without answering the more important question: “what are we actually ready for?”&lt;/p>
&lt;p>What’s missing isn’t imagination, but prioritization, ownership, and feasibility.&lt;/p>
&lt;h2 id="skipping-pilots-is-worse">Skipping pilots is worse&lt;/h2>
&lt;p>Let’s be clear: &lt;strong>not piloting&lt;/strong> is not the answer either. Nobody should roll out AI to all 18,000 employees at once. That’s not bold; that’s reckless. Pilots are essential to reduce risk, test assumptions, and validate business value in a controlled environment.&lt;/p>
&lt;p>But a pilot should be the start of a journey, not a loop.&lt;/p>
&lt;p>A solid pilot begins with a hypothesis: a &lt;strong>testable&lt;/strong>, grounded assumption about how AI could improve a specific business process. Maybe you believe that automating invoice routing will cut manual handling time by 50%. Or that an AI-powered summarizer will reduce response time in your support center by 15%. That hypothesis must be linked to something tangible, something you can actually measure.&lt;/p>
&lt;p>Which brings us to metrics. If your pilot has no business metrics, no baseline data, and no plan for what happens if it works… congratulations, you’re not piloting. You’re procrastinating.&lt;/p>
&lt;h2 id="pilots-need-an-ending">Pilots need an ending&lt;/h2>
&lt;p>One of the reasons pilots get stuck is that no one agreed on how they’d &lt;em>end&lt;/em>. You need &lt;strong>exit criteria&lt;/strong>: clear, objective statements that define what “successful”, “inconclusive”, or “not worth continuing” looks like. These should be defined before the pilot starts, not when someone asks for a status update six months in.&lt;/p>
&lt;p>For example:&lt;br>
&lt;em>“We’ll consider the pilot successful if the AI reduces invoice processing time by 30%, with an error rate under 2%, and if the three-person accounting team finds it usable without additional training.”&lt;/em>&lt;/p>
&lt;p>Not only does this force clarity early on, it gives everyone a shared target. And more importantly, it avoids the trap of “it’s not bad… let’s just keep it running in test.”&lt;/p>
&lt;p>Of course, exit criteria only work if they’re connected to real business metrics. And I don’t mean “number of chatbot clicks” or “hours spent coding”. I mean measurable outcomes:&lt;/p>
&lt;ul>
&lt;li>How much time was saved?&lt;/li>
&lt;li>Was manual effort reduced?&lt;/li>
&lt;li>Did we see fewer errors?&lt;/li>
&lt;li>Was customer satisfaction improved?&lt;/li>
&lt;li>Did operational throughput increase?&lt;/li>
&lt;/ul>
&lt;p>Start by capturing the &lt;strong>baseline&lt;/strong>: how long does this process take today? What are users frustrated about? What’s the current failure rate?&lt;/p>
&lt;p>Then, define what change would make the pilot worth scaling. Measure the delta. Track results. And make a decision. All of this should be part of your &lt;strong>pilot planning&lt;/strong>. Not a side note, not a retrospective, but a section right at the top of your one-pager&lt;/p>
&lt;ul>
&lt;li>What are we testing?&lt;/li>
&lt;li>What does success look like?&lt;/li>
&lt;li>What will we do if we get it?&lt;/li>
&lt;/ul>
&lt;p>Because a good pilot starts with a hypothesis, includes a measurable outcome, and ends with a decision: &lt;strong>scale, pivot, or stop&lt;/strong>.&lt;/p>
&lt;h2 id="signs-youre-stuck">Signs you’re stuck&lt;/h2>
&lt;p>If any of these apply, you’re probably stuck:&lt;/p>
&lt;ul>
&lt;li>Your pilot is older than a year&lt;/li>
&lt;li>There’s no rollout plan, just another “phase”&lt;/li>
&lt;li>The project is owned by IT, but not the business&lt;/li>
&lt;li>The same demo has been shown to three leadership teams&lt;/li>
&lt;li>No one can tell you what success looks like&lt;/li>
&lt;/ul>
&lt;h2 id="pilots-should-make-people-uncomfortable">Pilots should make people uncomfortable&lt;/h2>
&lt;p>Good pilots challenge something. A process. A decision. A mindset. They surface the real blockers: data quality, siloed teams, broken incentives, so that the organization can deal with them &lt;em>before&lt;/em> a full rollout. If your AI pilot made everyone feel safe, it probably didn’t do much.&lt;/p>
&lt;h2 id="lets-end-with-this">Let’s end with this&lt;/h2>
&lt;blockquote>
&lt;p>“If your AI pilot was never designed to leave the sandbox, it wasn’t a pilot, it was a safe place to pretend you’re changing.”&lt;/p>
&lt;/blockquote>
&lt;h2 id="whats-next-in-this-series">What’s next in this series&lt;/h2>
&lt;p>This post is part of the &lt;strong>“Stuck in Pilot”&lt;/strong> blog series. Coming up next:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Part 1: no foundations, no future&lt;/strong>&lt;br>
&lt;em>What your AI pilot was missing before it even started&lt;/em> - to be published soon™️&lt;/li>
&lt;li>&lt;strong>Part 2: fear of success&lt;/strong>&lt;br>
&lt;em>Why good pilots still don’t go live&lt;/em> - to be published soon™️&lt;/li>
&lt;li>&lt;strong>Part 3: your chatbot isn’t useful&lt;/strong>&lt;br>
&lt;em>When pilots fail because no one asked if the solution matters&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 4: from stuck to scaled&lt;/strong> - to be published soon™️
&lt;em>A roadmap for turning a successful pilot into real business value&lt;/em> - to be published soon™️&lt;/li>
&lt;/ul></description></item><item><title>Round Robin assignments in Power Automate</title><link>https://m365princess.com/blogs/roundrobin/</link><pubDate>Fri, 25 Jul 2025 09:21:04 +0000</pubDate><guid>https://m365princess.com/blogs/roundrobin/</guid><description>&lt;p>When distributing tasks or tickets across a team, fairness matters, and so does flexibility. With Power Automate, we can implement a &lt;strong>round robin&lt;/strong> pattern that always picks the next person in line, and wraps around once we hit the last.&lt;/p>
&lt;p>I got asked by a customer on how to achieve this - so it might be helpful to you as well.&lt;/p>
&lt;h2 id="what-were-solving">What we&amp;rsquo;re solving&lt;/h2>
&lt;p>Let’s say your team handles incoming forms—support requests, process suggestions, or tickets. You want those distributed evenly across your team, but also automatically.&lt;/p>
&lt;p>The round robin logic makes sure that in a team 3 people:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Request #&lt;/th>
&lt;th style="text-align: left">Assigned to&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">1&lt;/td>
&lt;td style="text-align: left">Person 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">2&lt;/td>
&lt;td style="text-align: left">Person 2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">3&lt;/td>
&lt;td style="text-align: left">Person 3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">4&lt;/td>
&lt;td style="text-align: left">Person 1 again&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>… and so on. No skipping, no duplication, no manual effort.&lt;/p>
&lt;h2 id="setup">Setup&lt;/h2>
&lt;h3 id="sharepoint-list-assignmentindex">SharePoint list: AssignmentIndex&lt;/h3>
&lt;p>Create a simple list called &lt;code>AssignmentIndex&lt;/code>. It only needs one row and one column (besides the Title):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Title&lt;/th>
&lt;th style="text-align: left">LastIndex&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">Assignment&lt;/td>
&lt;td style="text-align: left">0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>&lt;code>LastIndex&lt;/code> must be a &lt;strong>number&lt;/strong> column&lt;/li>
&lt;li>This acts as memory across runs, telling us who got the last task&lt;/li>
&lt;/ul>
&lt;h3 id="microsoft-forms-form">Microsoft Forms form&lt;/h3>
&lt;p>Set up a basic form with questions like &lt;code>Title&lt;/code>, &lt;code>Description&lt;/code> or similar. We will use this to trigger an upcoming support request.&lt;/p>
&lt;h2 id="flow-overview">Flow overview&lt;/h2>
&lt;p>&lt;img alt="flow overview" src="https://m365princess.com/images/flow-overview-rr.png">&lt;/p>
&lt;p>Let’s break this down into steps:&lt;/p>
&lt;h3 id="microsoft-forms-trigger">Microsoft Forms trigger&lt;/h3>
&lt;p>Use the trigger &lt;strong>When a new response is submitted&lt;/strong>&lt;/p>
&lt;h3 id="get-form-details">Get Form Details&lt;/h3>
&lt;p>Use the actions: &lt;strong>Get response details&lt;/strong>, this gives us the data we want to assign: title, description, urgency, etc.&lt;/p>
&lt;h3 id="initialize-memberlist">Initialize MemberList&lt;/h3>
&lt;ul>
&lt;li>variable of type &lt;strong>Array&lt;/strong>&lt;/li>
&lt;li>leave value empty&lt;/li>
&lt;/ul>
&lt;h3 id="get-sharepoint-index">Get SharePoint Index&lt;/h3>
&lt;p>Use &lt;strong>Get items&lt;/strong> to pull the one row from the &lt;code>AssignmentIndex&lt;/code> list. Use a filter condition &lt;code>Title eq 'Assignment'&lt;/code>.&lt;/p>
&lt;h3 id="initialize-lastindex">Initialize LastIndex&lt;/h3>
&lt;p>Now initialize a variable of type &lt;strong>Float&lt;/strong>: &lt;code>body('Get_items_-_AssignmentIndex')? ['value'][0]?['LastIndex']&lt;/code> - this lets us store the value of the &lt;strong>LastIndex&lt;/strong> column.&lt;/p>
&lt;h3 id="get-microsoft-teams-members">Get Microsoft Teams members&lt;/h3>
&lt;p>Use the action &lt;strong>List members&lt;/strong> from Microsoft Teams connector to list the members of your Team. Select a team and channel from the dropdown. The output will be an array of team members (emails, names, etc.).&lt;/p>
&lt;h4 id="get-the-email-addresses">Get the email addresses&lt;/h4>
&lt;p>Add an &lt;strong>Apply to each&lt;/strong> loop with the &lt;strong>input&lt;/strong>: &lt;code>body('List_members')?['value']&lt;/code>. Inside of the loop, add an &lt;strong>Append to array variable&lt;/strong> - Choose the &lt;code>MemberList&lt;/code> from the dropdown and put this into the value as an expression: &lt;code>items('Apply_To_Each')?['email']&lt;/code>&lt;/p>
&lt;p>This will loop through the array of objects we get from the Teams action, extract only the email addresses of the team members and add them to our array variable.&lt;/p>
&lt;h3 id="calculate-the-new-index">Calculate the new index&lt;/h3>
&lt;p>We now want to move one step/person forward in the team list. Initialize another variable &lt;code>NewIndex&lt;/code> of type &lt;strong>Float&lt;/strong> and use an expression:&lt;/p>
&lt;p>&lt;code>mod(add(variables('LastIndex'), 1), length(variables('MemberList')))&lt;/code>&lt;/p>
&lt;p>This is where the magic happens. The &lt;code>mod()&lt;/code> (Modulo) function will return the remainder of a division (dividend : divisor). In our case, it will give us the remainder of the division of &lt;code>LastIndex&lt;/code> + &lt;code>1&lt;/code> and the amount of items we got from the &lt;code>Memberlist&lt;/code> variable (we calculate this with the &lt;code>length&lt;/code> function).&lt;/p>
&lt;h3 id="what-does-mod-do-here">What does &lt;code>mod()&lt;/code> do here?&lt;/h3>
&lt;p>Let’s make that clear with a table:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">LastIndex&lt;/th>
&lt;th style="text-align: left">Team size&lt;/th>
&lt;th style="text-align: left">NewIndex calculation&lt;/th>
&lt;th style="text-align: left">Result&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">0&lt;/td>
&lt;td style="text-align: left">3&lt;/td>
&lt;td style="text-align: left">(0 + 1) mod 3 = 1&lt;/td>
&lt;td style="text-align: left">1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">1&lt;/td>
&lt;td style="text-align: left">3&lt;/td>
&lt;td style="text-align: left">(1 + 1) mod 3 = 2&lt;/td>
&lt;td style="text-align: left">2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">2&lt;/td>
&lt;td style="text-align: left">3&lt;/td>
&lt;td style="text-align: left">(2 + 1) mod 3 = 0&lt;/td>
&lt;td style="text-align: left">0&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;code>mod()&lt;/code> function ensures we &lt;strong>loop back&lt;/strong> to the beginning after reaching the end of the list.&lt;/p>
&lt;p>Now let&amp;rsquo;s finally find the right person to assign the new task to. We already know the &lt;code>NewIndex&lt;/code>, all is left for us now is to get the &lt;code>NewIndex&lt;/code>th (sic!) item from our &lt;code>MembersList&lt;/code>: Add a &lt;strong>Compose&lt;/strong> action and call it &lt;strong>Assignee&lt;/strong>. Use this expression: &lt;code>variables('MemberList')[int(variables('NewIndex'))]&lt;/code>, which will then return the email address, and due to our round-robin logic it will always be the next person in the list. You might have noticed, that we make an integer out of our float variable - this is needed because the index needs to be an integer (as an index can&amp;rsquo;t be 9 3/4 or similar).&lt;/p>
&lt;h2 id="assign-the-task">Assign the task&lt;/h2>
&lt;p>You can use this pattern for different use cases, for example&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Assigned To&lt;/strong> fields (Planner, SharePoint, Approvals)&lt;/li>
&lt;li>Emails&lt;/li>
&lt;li>Logging&lt;/li>
&lt;/ul>
&lt;p>Do as you please, your &lt;strong>Assignee&lt;/strong> action is now always the correct person.&lt;/p>
&lt;h2 id="update-the-sharepoint-index">Update the SharePoint index&lt;/h2>
&lt;p>Finally, don’t forget to update the &lt;code>LastIndex&lt;/code> field in SharePoint List so we know who got the last assignment and the list stores the correct value for the next flow run.&lt;/p>
&lt;p>Use &lt;strong>Update item&lt;/strong> with:&lt;/p>
&lt;ul>
&lt;li>ID = dynamic content &lt;code>ID&lt;/code> from our &lt;strong>Get items&lt;/strong> action&lt;/li>
&lt;li>LastIndex = &lt;code>variables('NewIndex')&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Done! 👌&lt;/p>
&lt;p>Looking for more blog posts on Power Automate? I got you: &lt;a href="https://www.m365princess.com/categories/power-automate/">https://www.m365princess.com/categories/power-automate/&lt;/a>&lt;/p></description></item><item><title>Copilot Studio: Part 5 - From tool to capability – making Copilot Studio strategic</title><link>https://m365princess.com/blogs/copilot5/</link><pubDate>Fri, 25 Jul 2025 06:15:26 +0000</pubDate><guid>https://m365princess.com/blogs/copilot5/</guid><description>&lt;p>If you made it this far in tis series, you’ve seen the messy middle: the risks, the lifecycle debt, the cost of inaction. Now let’s zoom out.&lt;/p>
&lt;p>Copilot Studio isn’t just another way to automate tasks, but a design surface for how your organization thinks, acts, and responds. But only if you treat it like a capability, not a tool.&lt;/p>
&lt;h2 id="the-real-value-isnt-in-building-faster-but-in-thinking-better">The real value isn’t in building faster but in thinking better&lt;/h2>
&lt;p>Most organizations ask the wrong first question: &lt;em>“What use cases can we build with Copilot Studio?”&lt;/em>&lt;/p>
&lt;p>They should be asking: &lt;em>“What kinds of agents should we be able to build, reliably, at scale?”&lt;/em>&lt;/p>
&lt;p>Because building one agent isn’t hard. Building twenty that share knowledge, escalate consistently, reuse logic, and don’t rot after Q1? That’s capability. You don’t need a library of bots. You need an ecosystem.&lt;/p>
&lt;h2 id="one-per-team-is-not-a-strategy">“One per team” is not a strategy&lt;/h2>
&lt;p>If your roadmap is “every department gets a chatbot”, congratulations, you’ve reinvented the silo.
Now every agent has its own personality, logic, and tone. None of them are connected. All of them are partial.&lt;/p>
&lt;blockquote>
&lt;p>Capabilities are horizontal.&lt;br>
They support multiple processes, flex across domains, and evolve with the org.&lt;/p>
&lt;/blockquote>
&lt;p>Instead of one bot per team, ask:&lt;/p>
&lt;ul>
&lt;li>Which core tasks show up across teams?&lt;/li>
&lt;li>Which decisions are low-stakes but high-volume?&lt;/li>
&lt;li>Where do people get blocked, not because the work is hard, but because it’s inconsistent?&lt;/li>
&lt;/ul>
&lt;p>Build for those. Build once, use many times. And please stop making FAQ-Bots.&lt;/p>
&lt;h2 id="composition-over-control">Composition over control&lt;/h2>
&lt;p>Most agent deployments start centralized. One team. One environment. One “AI initiative”. &lt;a href="https://www.m365princess.com/blogs/pilot0/">That works for a pilot&lt;/a>. But it doesn’t scale. Because real-world needs shift fast. Autonomy matters. Context matters. So instead of controlling every agent from the center, design for composition&lt;/p>
&lt;ul>
&lt;li>Shared knowledge sources&lt;/li>
&lt;li>Reusable skills&lt;/li>
&lt;li>Composable response blocks&lt;/li>
&lt;li>Plug-and-play escalation flows&lt;/li>
&lt;/ul>
&lt;p>Let teams build what they need, but give them building blocks that don’t reinvent the basics.&lt;/p>
&lt;h2 id="capability-maps--use-case-catalogs">Capability maps &amp;gt; use case catalogs&lt;/h2>
&lt;p>A use case catalog tells you what you’ve built. A capability map tells you what you &lt;em>could&lt;/em> build, where it fits, how it integrates, and what it depends on. It includes:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Agent types&lt;/strong> (retrieval, task, autonomous)&lt;/li>
&lt;li>&lt;strong>Triggers&lt;/strong> (user-initiated, system signals, cross-agent)&lt;/li>
&lt;li>&lt;strong>Behaviors&lt;/strong> (respond, decide, act, escalate)&lt;/li>
&lt;li>&lt;strong>Ownership&lt;/strong> (who governs, who maintains, who measures success)&lt;/li>
&lt;li>&lt;strong>Boundaries&lt;/strong> (what this agent should &lt;em>not&lt;/em> do)&lt;/li>
&lt;/ul>
&lt;p>It’s not documentation (although docs are still necessary). It’s design intent. It helps you scale without duplicating effort or creating invisible risk.&lt;/p>
&lt;h2 id="workforce-design-not-tech-deployment">Workforce design, not tech deployment&lt;/h2>
&lt;p>Every agent you deploy takes over a task your people used to own. That’s not just automation, but workforce shift. Treat it that way and ask&lt;/p>
&lt;ul>
&lt;li>What kind of decisions are we delegating?&lt;/li>
&lt;li>What kind of work do we &lt;em>want&lt;/em> to automate or augment?&lt;/li>
&lt;li>Where should humans stay in the loop?&lt;/li>
&lt;li>Where should the loop disappear entirely?&lt;/li>
&lt;/ul>
&lt;p>If you don’t ask these questions, Copilot Studio becomes another dashboard, something that demos well, but changes nothing. (If you feel that your org is stuck at that level&amp;hellip; lets have a chat, I will help you get out of pilot-jail)&lt;/p>
&lt;h2 id="wrap-up-this-wasnt-about-bots">Wrap-up: This wasn’t about bots&lt;/h2>
&lt;p>This whole series? It wasn’t really about Copilot Studio, but about clarity, ownership, intent. Because that’s what agents reflect. Not just what you told them to do, but how clearly, how carefully, and how repeatably you told them.&lt;/p>
&lt;blockquote>
&lt;p>Tools come and go. Capabilities compound.&lt;/p>
&lt;/blockquote>
&lt;p>Build the latter.&lt;/p>
&lt;h2 id="you-can-still-catch-up-on-the-previous-blog-posts-in-this-series">You can still catch up on the previous blog posts in this series&lt;/h2>
&lt;p>&lt;img alt="padme meme" src="https://m365princess.com/images/padme-meme.jpg">&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Part 0&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot0/">Everything is an agent, until it isn&amp;rsquo;t&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot1/">When automation bites back – autonomy ≠ chaos&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot2/">Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot3/">The cost of (in)action – what you’re really paying for with Copilot Studio&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot4/">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 5&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot5">From tool to capability – making Copilot Studio strategic&lt;/a>&lt;/em> [📍 You are here]&lt;/li>
&lt;/ul></description></item><item><title>Copilot Studio: Part 4 - Agents that outlive their creators – governance, risk, and the long tail of AI</title><link>https://m365princess.com/blogs/copilot4/</link><pubDate>Tue, 22 Jul 2025 10:17:17 +0000</pubDate><guid>https://m365princess.com/blogs/copilot4/</guid><description>&lt;h2 id="agents-that-outlive-their-creators--governance-risk-and-the-long-tail-of-ai">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/h2>
&lt;p>In most organizations, apps get decommissioned. Workflows get retired. People move on, and someone eventually archives the mess they left behind. But agents? Agents keep acting, even when their creator is gone. That’s not a hypothetical risk. It’s already happening. Because most Copilot Studio deployments are building agents with no plan for ownership, no lifecycle guardrails, and no way to know who’s responsible once the project changes hands.&lt;/p>
&lt;h2 id="automation-doesnt-age-well">Automation doesn’t age well&lt;/h2>
&lt;p>A flow that fails? It throws an error. Someone notices. An agent that fails? &amp;ldquo;It might just hallucinate&amp;rdquo; a plausible answer. Or skip a trigger. Or quietly misclassify a request. And no one notices, until the wrong thing gets approved. Or never does. Now imagine that agent was built by someone who left six months ago. No documentation. No ownership transfer. No audit trail. No one even knows it still exists.&lt;/p>
&lt;p>&lt;strong>And yet, it’s still acting.&lt;/strong>&lt;/p>
&lt;h2 id="orphaned-logic-is-the-new-shadow-it">Orphaned logic is the new shadow IT&lt;/h2>
&lt;p>Orphaned agents don’t break all at once. They decay. The SharePoint source changes. The connector credentials expire. The business rule is revised, but not in the agent. And because it doesn’t crash, no one flags it. It just keeps delivering wrong-ish results. Enough to pass casual use. Enough to lose trust over time. When agents are treated like temporary hacks, they never get the lifecycle discipline of “real” apps. But they still live in your production environment, attached to your brand, acting in your systems. Shadow IT used to be spreadsheets and rogue access databases. Now it’s retired employees’ bots still issuing approvals.&lt;/p>
&lt;h2 id="intent-isnt-portable">Intent isn’t portable&lt;/h2>
&lt;p>Agents don’t just contain instructions. They contain &lt;a href="https://www.m365princess.com/blogs/assumptions/">assumptions&lt;/a>. What should be escalated?&lt;br>
What counts as “sensitive”? What tone should be used in a customer response? These aren’t just prompts, but judgment calls: made once, encoded, and never reviewed. Until someone disagrees with the outcome and the fallout begins. You can’t inherit an agent without inheriting its design logic. And most of that logic is undocumented.&lt;/p>
&lt;blockquote>
&lt;p>Code you can read. Flows you can inspect.&lt;/p>
&lt;/blockquote>
&lt;p>But an agent’s behavior? That often lives in prompts, embedded logic, and fuzzy mental models. Good luck reverse-engineering intent.&lt;/p>
&lt;h2 id="governance-isnt-cleanup-its-foresight">Governance isn’t cleanup. It’s foresight.&lt;/h2>
&lt;p>Most AI governance today is reactive. There’s an incident. Or a compliance audit. Or a spike in weird behavior. Then everyone scrambles to retro-document the agent, wrap it in monitoring, and assign an owner. It’s backward.&lt;/p>
&lt;p>Governance should begin at the moment of creation:&lt;/p>
&lt;ul>
&lt;li>Who owns this agent?&lt;/li>
&lt;li>What is its purpose?&lt;/li>
&lt;li>How long should it run?&lt;/li>
&lt;li>What should happen when it fails?&lt;/li>
&lt;li>How is success measured&lt;/li>
&lt;li>And who reviews that?&lt;/li>
&lt;/ul>
&lt;p>If you can’t answer those questions, you’re not building an agent. You’re publishing a biiig liability.&lt;/p>
&lt;h2 id="the-long-tail-of-ownership">The long tail of ownership&lt;/h2>
&lt;p>Every agent you deploy creates ongoing obligation. It needs:&lt;/p>
&lt;ul>
&lt;li>Knowledge base updates&lt;/li>
&lt;li>Prompt maintenance&lt;/li>
&lt;li>Access review&lt;/li>
&lt;li>Escalation logic refresh&lt;/li>
&lt;li>Retirement planning&lt;/li>
&lt;/ul>
&lt;p>And yet most agents go live with none of that defined. Because the maker is “just experimenting” or the team is “moving fast”. Then the person leaves. Or the project pivots. Or the department gets restructured. And suddenly you’re running production logic that no one can explain. AI doesn’t stop working just because you stopped watching it. That’s what makes it dangerous.&lt;/p>
&lt;h2 id="future-proofing-the-invisible-workforce">Future-proofing the invisible workforce&lt;/h2>
&lt;p>If your organization has a bot running in Teams, that bot is now part of your workforce. It performs tasks. Interfaces with customers. Retrieves and summarizes knowledge. Makes decisions. So treat it like a team member:&lt;/p>
&lt;ul>
&lt;li>Give it a clear role&lt;/li>
&lt;li>Document its function&lt;/li>
&lt;li>Assign an owner&lt;/li>
&lt;li>Review its performance&lt;/li>
&lt;li>Offboard it when it’s done&lt;/li>
&lt;/ul>
&lt;p>Because whether you track them or not, your agents are working. And if no one’s in charge, they’re still making decisions, with your name on them.&lt;/p>
&lt;h2 id="coming-up-next">Coming up next&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Part 0&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot0/">Everything is an agent, until it isn&amp;rsquo;t&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot1/">When automation bites back – autonomy ≠ chaos&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot2/">Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot3/">The cost of (in)action – what you’re really paying for with Copilot Studio&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot4/">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/a>&lt;/em> [📍 You are here]&lt;/li>
&lt;li>&lt;strong>Part 5&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot5">From tool to capability – making Copilot Studio strategic&lt;/a>&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>Org chart is cheap, show me the relationships</title><link>https://m365princess.com/blogs/org/</link><pubDate>Tue, 22 Jul 2025 08:31:49 +0000</pubDate><guid>https://m365princess.com/blogs/org/</guid><description>&lt;h3 id="tldr">tl;dr&lt;/h3>
&lt;p>AI can read every policy, spec, and wiki page in your company; it even knows the formal org chart. But it doesn’t &lt;em>feel&lt;/em> the informal paths: the backchannels, the trust networks, the unspoken approvals. Institutional knowledge lives in the spaces between people. When you drop an AI agent into an organization and expect it to &amp;ldquo;just know&amp;rdquo;, it plays the wrong game. Map the relationships first; then let AI augment the humans who already run them.&lt;/p>
&lt;h2 id="the-illusion-of-instant-expertise">The illusion of instant expertise&lt;/h2>
&lt;p>There’s something deeply appealing about the idea that AI could replace the messiness of human memory. If we could just ingest all the project notes, the documented processes, the internal wikis, maybe even throw in the Slack/Teams logs for good measure, surely we could replicate what the experts in the building know. (I will happily leave out the problem that usually, knowledge is rarely (well) documented, that processes are unclear/broken, and that people fight political battles in hierarchal structures; some to thrive, some to just survive this quarter)&lt;/p>
&lt;p>The problem is: We’re conflating access to documentation with access to understanding. Reading what happened is not the same as knowing how it happened, or why. And it definitely doesn’t mean you can replicate it. The fantasy is that institutional knowledge is just poorly indexed content. But most of it was never written down in the first place. It lives in offhand comments, hallway deals, late-night decisions, subtle workarounds, and emotional memory, the kind that says, “Yeah, that diagram says A, but trust me, talk to Maria first.”&lt;/p>
&lt;p>AI can scan the documentation, but it won’t sense the tension in a planning call. It can retrieve the slide deck, but it doesn’t know that slide was a decoy for a much harder conversation that never made it into writing. It can suggest a process, but it won’t understand the invisible compromises that hold it together.&lt;/p>
&lt;h2 id="the-real-org-chart">The real org chart&lt;/h2>
&lt;p>&lt;img alt="the real org chart" src="https://m365princess.com/images/org-chart.png">&lt;/p>
&lt;p>A few years ago, a cartoonish diagram started circulating in HR departments. It showed a smiling org chart turned sideways by arrows of resentment, secret crushes, backchannel deals, and long-forgotten favors. People laughed; some a little too hard. It stuck because it captured something everyone knew but no one ever documented: that real power, real trust, and real influence don’t follow reporting lines - and they are never written down.&lt;/p>
&lt;p>Every company has someone who technically reports to someone else, but in practice, calls the shots. Every dev team has a person who isn’t the architect but whose opinion the architect won’t ship without. Every meeting has the person whose silence speaks louder than the project sponsor’s cheerleading.&lt;/p>
&lt;p>Sometimes it&amp;rsquo;s the executive assistant who knows exactly when to schedule a meeting so it gets the right attention. Sometimes it&amp;rsquo;s the long-tenured employee who can de-escalate a cross-team conflict with a single sentence. These aren’t exceptions; they’re infrastructure. The kind no system-of-record captures. When someone says, “Let me check with them first”, you’re seeing the real org chart at work. And AI, no matter how much data you feed it, doesn’t &lt;em>feel&lt;/em> those dynamics.&lt;/p>
&lt;h2 id="why-ai-stumbles">Why ai stumbles&lt;/h2>
&lt;p>AI struggles in knowledge work because it assumes the structure is the truth. It assumes that if you feed it the correct inputs, it will produce the correct answers. But knowledge work is full of context, ambiguity, and shifting expectations.&lt;/p>
&lt;p>Let me give you an example: Language models score probabilities; humans score trust. When someone gives you a nuanced answer to a politically charged question, you’re not just hearing the words. You’re reading between the lines, tracking facial expressions, body language, tone. You’re also remembering how they handled a tough decision two years ago. AI misses all of this.&lt;/p>
&lt;p>Even if you give it access to all the right content (remember, that&amp;rsquo;s a whole different challenge!), you’re working with static context. An org&amp;rsquo;s knowledge graph changes faster than its documents. Today’s team lead becomes tomorrow’s problem child. The person who used to run things steps back. Someone else quietly steps up. Your AI won’t keep up with the gossip, and it definitely won’t sense that a document is technically accurate but politically obsolete.&lt;/p>
&lt;p>There’s also the Conway problem. &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway’s Law&lt;/a> tells us that systems mirror the communication structures of the teams that build them. If you don’t know who actually talks to whom, your automation will lock in the wrong assumptions. AI solutions built on misread maps only deepen the disconnect.&lt;/p>
&lt;p>And finally, AI is blind to the informal networks. The people who connect teams, translate between departments, or just know who to talk to, they rarely show up in org charts. These are your bridge people. They have no formal mandate but hold immense influence. If you don’t account for them, your AI will repeatedly miss the mark.&lt;/p>
&lt;h2 id="what-we-actually-need">What we actually need&lt;/h2>
&lt;p>We need to stop trying to replace knowledge workers with AI and instead design tools that help them be more effective. That starts with respecting how knowledge actually flows in an organization. Don’t build systems that expect to learn everything from one big data ingestion. Most of what matters isn’t written down, and what is written often lacks the nuance to be interpreted safely without context. Instead, build opt-in systems that let people gradually contribute and curate the knowledge they hold. Systems that understand the value of context, timing, and consent.&lt;/p>
&lt;p>Instead of mapping roles, map relationships. Knowing that Alex is the &amp;ldquo;Product Owner&amp;rdquo; doesn’t tell you much. Knowing that Alex always defers to Jamie on architectural questions, despite having the final say on paper, tells you a lot. Track who people actually turn to for help, advice, or unblocking. The connectors. The mentors. The quiet experts. These are the nodes an AI assistant needs to understand if it&amp;rsquo;s going to provide value without stepping on toes.&lt;/p>
&lt;p>And above all&lt;/p>
&lt;blockquote>
&lt;p>Stop framing AI as a cost-cutting tool. Frame it as a knowledge amplifier.&lt;/p>
&lt;/blockquote>
&lt;p>AI is something that helps humans find the right person faster, spot inconsistencies earlier, or prep better for meetings. Let people stay in charge. Just give them better tools.&lt;/p>
&lt;h2 id="making-it-real">Making it real&lt;/h2>
&lt;p>So how do we build AI that respects the real org chart? Start with relationship mapping. Not in a creepy surveillance way, but in the spirit of transparency and collaboration. You don’t need to trace every coffee chat. But there’s a big difference between &amp;ldquo;the dev team&amp;rdquo; and &amp;ldquo;the three people in the dev team everyone trusts with hard questions&amp;rdquo;.&lt;/p>
&lt;p>Find those people. Talk to them. Ask not what they do on paper, but how they get things done in practice. They’ll tell you where the bottlenecks really are, who smooths things over when teams fight, and where decisions get quietly reversed before they hit the roadmap.&lt;/p>
&lt;p>Then, build your AI around those realities. If someone always edits the architecture document after it’s approved, make sure the AI knows not to recommend it without checking with them. If two people interpret the same policy differently, your assistant shouldn’t pick one; it should flag the conflict and suggest a conversation.&lt;/p>
&lt;p>And don’t measure success by usage alone. Measure trust. When people rely on an AI suggestion, do they feel it helped? Do they come back? Or do they start ignoring it because it keeps missing the point? Usage can be a vanity metric. Trust is what determines whether AI becomes a partner or a nuisance.&lt;/p>
&lt;h2 id="closing-thought">Closing thought&lt;/h2>
&lt;p>Knowledge work is deeply human. It’s made of trust, shortcuts, conflict, and compromise. If your AI doesn’t know who people trust, who they avoid, who they quietly run things by before making a call, it doesn’t know enough. The real org chart isn’t in the HR system; it’s in the relationships. Start there.&lt;/p>
&lt;h2 id="ps">PS&lt;/h2>
&lt;p>If you are still on the &amp;ldquo;hey Copilot whats our travel policy&amp;rdquo; level, lets talk to get you out of that chatbot-jail. AI can do so much more.&lt;/p></description></item><item><title>Copilot Studio: Part 3 - The cost of (in)action – what you’re really paying for with Copilot Studio</title><link>https://m365princess.com/blogs/copilot3/</link><pubDate>Mon, 21 Jul 2025 10:01:06 +0000</pubDate><guid>https://m365princess.com/blogs/copilot3/</guid><description>&lt;p>Some organizations break things by moving too fast. Most break things by doing nothing at all. Inaction is comfortable. It looks responsible. Safe. &amp;ldquo;We&amp;rsquo;re just waiting until the platform matures.&amp;rdquo; But the bill is still running, just not where you&amp;rsquo;re looking.&lt;/p>
&lt;blockquote>
&lt;p>Copilot Studio isn’t expensive because of licenses.&lt;/p>
&lt;/blockquote>
&lt;p>It’s expensive because of what happens when you treat it like a toy, or when you ignore it altogether.&lt;/p>
&lt;h2 id="the-quiet-cost-of-doing-it-wrong">The quiet cost of doing it wrong&lt;/h2>
&lt;p>You don’t need a catastrophic failure to lose money with Copilot Studio. You just need friction.&lt;/p>
&lt;p>You don’t need a dramatic failure to lose money with Copilot Studio. Most of the time, it’s friction that wears you down. Maybe the agent was built in the default environment and published without a pipeline, so every update overwrites something; accidentally wiping out working logic. Maybe no one tested the prompts properly, so users keep escalating the same issue the bot was supposed to resolve. Or maybe the agent keeps quoting a SharePoint list that hasn’t been updated in six months, and now someone has to patch it manually every week to avoid embarrassment. Over time, teams start building their own versions of the same chatbot because no one wants to deal with the old one, and suddenly you&amp;rsquo;re supporting five slightly different agents that all sort of work, but none of them are governed. Eventually, users stop relying on them at all. No one complains; they just go back to asking colleagues for help. The usage metrics stay flat, but the trust has disappeared. And sometimes, the agent misses something critical, an escalation that never happens, a ticket that never gets filed. Nothing breaks immediately. It just doesn’t work when it matters. And now you’re firefighting a silent failure that started months ago.&lt;/p>
&lt;p>None of this makes a headline. It just eats your time. Quietly. Relentlessly.&lt;/p>
&lt;h2 id="the-invisible-price-of-doing-nothing">The invisible price of doing nothing&lt;/h2>
&lt;p>Then there’s the cost of inaction&lt;/p>
&lt;p>Every time you delay deploying agents because “we’re not ready”, here’s what happens:&lt;/p>
&lt;ul>
&lt;li>Teams fall back to &lt;em>inbox ops&lt;/em>. Decisions happen in chat. Institutional memory erodes.&lt;/li>
&lt;li>Knowledge stays locked in wikis no one reads. The same questions get answered by five different people.&lt;/li>
&lt;li>Talent builds workarounds in Excel, again.&lt;/li>
&lt;li>Innovation effort migrates to unmanaged tools. That cool use case? They built it with ChatGPT on their phone.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Action is expensive, but inaction costs a fortune. ~Shane Parrish&lt;/p>
&lt;/blockquote>
&lt;h2 id="ai-isnt-free-but-the-real-costs-arent-where-you-think">AI isn’t free, but the real costs aren’t where you think&lt;/h2>
&lt;p>Organizations love to obsess over license pricing. “Is it worth paying for Copilot Studio?” is just a variant of &amp;ldquo;Should we allow premium connectors?&amp;rdquo;&lt;/p>
&lt;p>Valid questions, but wrong focus. The cost isn’t just in the platform. It’s in the architecture and culture that wrap around it:&lt;/p>
&lt;ul>
&lt;li>Bad prompts are cheap. Bad decisions from bad prompts are not.&lt;/li>
&lt;li>Loose environments don’t break until they do, publicly.&lt;/li>
&lt;li>Slow agents trained on incomplete data don’t help, they damage trust.&lt;/li>
&lt;li>&amp;ldquo;We’ll just let the team try it&amp;rdquo; becomes &amp;ldquo;we&amp;rsquo;re now supporting six shadow agents with no owner.&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Copilot Studio scales whatever you feed it. If that’s poor process design, you’ve scaled confusion. If it’s quality? You’ve scaled leverage. Either way, you’re paying for it.&lt;/p>
&lt;h2 id="technical-debt-now-with-conversational-ui">Technical debt, now with conversational UI&lt;/h2>
&lt;p>Every Copilot Studio agent you build becomes a long-term relationship. That means:&lt;/p>
&lt;ul>
&lt;li>Maintenance&lt;/li>
&lt;li>Governance drift&lt;/li>
&lt;li>Training new owners&lt;/li>
&lt;li>Retiring old logic without breaking existing workflows&lt;/li>
&lt;/ul>
&lt;p>Ignore this, and you’re not saving time but postponing effort into future outages and context loss.&lt;/p>
&lt;p>&lt;img alt="doing nothing" src="https://m365princess.com/images/nothing.png">&lt;/p>
&lt;h2 id="cost-isnt-a-number-but-behavior">Cost isn’t a number, but behavior&lt;/h2>
&lt;p>The real expense isn’t what you spend. It’s what your team &lt;em>learns is acceptable&lt;/em>. Agents with vague logic and brittle actions teach people that automation is unreliable. Agents that get quietly deprecated teach people that automation is disposable. You build capability through repetition, reuse, and reliability. If every agent is a one-off, every failure resets trust.&lt;/p>
&lt;h2 id="this-is-what-scale-looks-like">This is what scale looks like&lt;/h2>
&lt;blockquote>
&lt;p>The question isn’t “should we use Copilot Studio?”&lt;br>
The question is: “what does it cost us to do nothing well?”&lt;/p>
&lt;/blockquote>
&lt;p>Because when you don’t act, you pay in:&lt;/p>
&lt;ul>
&lt;li>Missed signals&lt;/li>
&lt;li>Fragmented knowledge&lt;/li>
&lt;li>Inconsistent responses&lt;/li>
&lt;li>Developer frustration&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Not all tech debt comes from overbuilding, a lot comes from under-deciding.&lt;/p>
&lt;/blockquote>
&lt;p>🌵 I&amp;rsquo;m curious: did you ever take the time or effort to calculate that? Or do you work in a company where that doesn&amp;rsquo;t matter, because &lt;em>we&amp;rsquo;ve always done it like this&lt;/em>? Lemme know.&lt;/p>
&lt;h2 id="coming-up-next">Coming up next&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Part 0&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot0/">Everything is an agent, until it isn&amp;rsquo;t&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot1/">When automation bites back – autonomy ≠ chaos&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot2/">Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot3/">The cost of (in)action – what you’re really paying for with Copilot Studio&lt;/a>&lt;/em> [📍 You are here]&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot4/">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 5&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot5">From tool to capability – making Copilot Studio strategic&lt;/a>&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>Smart buttons for incident tracking in SharePoint</title><link>https://m365princess.com/blogs/sp-buttons/</link><pubDate>Wed, 16 Jul 2025 07:25:44 +0000</pubDate><guid>https://m365princess.com/blogs/sp-buttons/</guid><description>&lt;p>Incident tracking in SharePoint lists often involves too many clicks. Users need to open each item, change a value, wait for the value to be saved, and hope they didn&amp;rsquo;t miss anything. With JSON column formatting and Power Automate, this process can be streamlined using inline action buttons.&lt;/p>
&lt;p>&lt;img alt="SharePoint List with buttons" src="https://m365princess.com/images/sp-buttons.png">&lt;/p>
&lt;h2 id="use-case">Use case&lt;/h2>
&lt;p>A SharePoint list tracks incidents with fields for title, priority (low, medium, high), and status (new, in progress, resolved). To improve efficiency, two buttons are added directly in the list view:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Escalate&lt;/strong>: triggers a Power Automate flow to notify a manager&lt;/li>
&lt;li>&lt;strong>Resolve&lt;/strong>: triggers a flow to mark the item as resolved&lt;/li>
&lt;/ul>
&lt;p>Both buttons are conditionally enabled: &lt;strong>Escalate&lt;/strong> is only active when Priority = high, &lt;strong>Resolve&lt;/strong> is only active when Status ≠ new&lt;/p>
&lt;h2 id="sharepoint-list-setup">SharePoint List setup&lt;/h2>
&lt;p>Create a list with the following columns:&lt;/p>
&lt;ul>
&lt;li>Title (single line of text) (it&amp;rsquo;s already there)&lt;/li>
&lt;li>Priority (choice)&lt;/li>
&lt;li>Status (choice)&lt;/li>
&lt;li>Actions (single line of text; used for formatting only)&lt;/li>
&lt;/ul>
&lt;h2 id="power-automate-flow-setup">Power Automate flow setup&lt;/h2>
&lt;p>Create two flows in Power Automate:&lt;/p>
&lt;ul>
&lt;li>Escalate flow: Use &amp;ldquo;For a selected item”, then notify the people you like this to receive or start any other protocol that you might already have in place&lt;/li>
&lt;li>Resolve flow: Same trigger, update the &lt;strong>Status&lt;/strong> column to be &lt;code>resolved&lt;/code> and maybe send some message to the person who raised the incident&lt;/li>
&lt;/ul>
&lt;p>Copy the Flow IDs from their URLs.&lt;/p>
&lt;p>&lt;img alt="Copy IDs via Export feature" src="https://m365princess.com/images/pa-export.png">&lt;/p>
&lt;h2 id="apply-json-formatting">Apply JSON formatting&lt;/h2>
&lt;p>Go to the &lt;strong>Actions&lt;/strong> column → &lt;strong>Column settings&lt;/strong> → &lt;strong>Format this column&lt;/strong>. Paste the JSON below (replace the Flow IDs):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;$schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;flex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;gap&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;10px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;justify-content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;button&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Escalate&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Priority] == &amp;#39;high&amp;#39;, &amp;#39;#287b7d&amp;#39;, &amp;#39;#e0e0e0&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Priority] == &amp;#39;high&amp;#39;, &amp;#39;white&amp;#39;, &amp;#39;#888888&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2px solid transparent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;6px 12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border-radius&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;cursor&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Priority] == &amp;#39;high&amp;#39;, &amp;#39;pointer&amp;#39;, &amp;#39;not-allowed&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;600&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;box-sizing&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;border-box&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customRowAction&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;executeFlow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;actionParams&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Priority] == &amp;#39;high&amp;#39;, &amp;#39;{\&amp;#34;id\&amp;#34;: \&amp;#34;&amp;lt;FLOWID_ESCALATE&amp;gt;\&amp;#34;}&amp;#39;, &amp;#39;&amp;#39;)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;button&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Resolve&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Status] == &amp;#39;new&amp;#39;, &amp;#39;#f0f0f0&amp;#39;, &amp;#39;transparent&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Status] == &amp;#39;new&amp;#39;, &amp;#39;#aaaaaa&amp;#39;, &amp;#39;#287b7d&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2px solid #287b7d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;6px 12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border-radius&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;cursor&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Status] == &amp;#39;new&amp;#39;, &amp;#39;not-allowed&amp;#39;, &amp;#39;pointer&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;500&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;box-sizing&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;border-box&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customRowAction&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;executeFlow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;actionParams&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Status] != &amp;#39;new&amp;#39;, &amp;#39;{\&amp;#34;id\&amp;#34;: \&amp;#34;&amp;lt;FLOWID_RESOLVE&amp;gt;\&amp;#34;}&amp;#39;, &amp;#39;&amp;#39;)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="how-it-works">How it works&lt;/h2>
&lt;p>The root element is a div that acts as a container for the buttons, and we apply flexbox styling to align buttons horizontally with spacing between them. All buttons are nested inside the children array. We add conditional styling and call a Power Automate flow when the button gets selected.&lt;/p>
&lt;h2 id="outcome">Outcome&lt;/h2>
&lt;p>Users can take action directly in the list view without opening the item. You can find this soon as a sample in the &lt;a href="https://github.com/pnp/List-Formatting/tree/master/column-samples/generic-action-buttons">PnP Listformatting repo&lt;/a> - PR got approved ✨&lt;/p></description></item><item><title>Assumption is the mother of all fuck-ups</title><link>https://m365princess.com/blogs/assumptions/</link><pubDate>Wed, 16 Jul 2025 05:25:44 +0000</pubDate><guid>https://m365princess.com/blogs/assumptions/</guid><description>&lt;h2 id="the-comforting-danger-of-we-assumed">The comforting danger of “we assumed&amp;hellip;”&lt;/h2>
&lt;p>You’ve heard the saying: &lt;a href="https://www.imdb.com/title/tt0114781/quotes/">assumption is the mother of all fuck-ups&lt;/a>. It&amp;rsquo;s crude, yes, but also painfully accurate. Trust me, been there.&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;We assumed each customer would have one account&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;We assumed the Excel template wouldn’t change&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;We assumed we would only need one language&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>These statements don’t come from bad developers. They come from teams under pressure, moving fast, or trying to make progress without full information. It feels efficient to move forward based on a shared understanding, or at least a silent one. But that’s the trap:&lt;/p>
&lt;blockquote>
&lt;p>Assumptions feel like alignment. They&amp;rsquo;re not. They&amp;rsquo;re just unspoken bets.&lt;/p>
&lt;/blockquote>
&lt;h2 id="how-assumptions-show-up-in-software">How assumptions show up in software&lt;/h2>
&lt;p>Assumptions are easy to spot in hindsight. They&amp;rsquo;re harder to catch in the moment because they’re embedded in our tools, our conversations, and our diagrams. You’ll find them:&lt;/p>
&lt;ul>
&lt;li>in requirements that gloss over edge cases&lt;/li>
&lt;li>in data models designed for the &amp;ldquo;happy path&amp;rdquo;&lt;/li>
&lt;li>in architecture diagrams that skip error handling&lt;/li>
&lt;li>in user stories with no alternate flows&lt;/li>
&lt;li>in test plans that only test what we expect to happen&lt;/li>
&lt;/ul>
&lt;p>They hide behind &amp;ldquo;default settings&amp;rdquo;, &amp;ldquo;temporary workarounds&amp;rdquo;, and &amp;ldquo;this should be fine.&amp;rdquo;&lt;/p>
&lt;p>&lt;img alt="assumption cross stich" src="https://m365princess.com/images/assumption.png">&lt;/p>
&lt;h2 id="real-world-fallout">Real-world fallout&lt;/h2>
&lt;p>Some examples I&amp;rsquo;ve seen in real projects:&lt;/p>
&lt;ul>
&lt;li>A warehouse app that assumed every product would have a barcode. Then a shipment arrived from a new supplier—with no barcodes. The team had to manually override 300 entries.&lt;/li>
&lt;li>A SharePoint site that assumed everyone would use the default language. Then came a merger with a French team, and suddenly no one could find the documents they needed.&lt;/li>
&lt;li>A Power Platform solution that assumed the requester of a task would always be the one to approve it. It worked beautifully—until vacation season hit and tasks piled up with no fallback path.&lt;/li>
&lt;/ul>
&lt;p>None of these were technical failures. They were assumption failures.&lt;/p>
&lt;p>We assume to move faster. It helps us act when things are uncertain, unclear, or politically tricky. Some assumptions are born out of survival—others out of status, fear, or comfort. And the ones we never say out loud? Those are the most dangerous. They&amp;rsquo;re not just technical; they&amp;rsquo;re cultural, emotional, or territorial. That’s why in complex orgs, the riskiest assumptions are often the ones no one is allowed to question.&lt;/p>
&lt;h2 id="the-most-dangerous-kinds-of-assumptions">The most dangerous kinds of assumptions&lt;/h2>
&lt;p>Let’s call them out. Here are some of the worst offenders:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">&lt;strong>Assumption Type&lt;/strong>&lt;/th>
&lt;th style="text-align: left">&lt;strong>Example&lt;/strong>&lt;/th>
&lt;th style="text-align: left">&lt;strong>What really happened&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>User behavior&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“Users will always finish the form in one go.”&lt;/td>
&lt;td style="text-align: left">They got a Teams call halfway through and closed the tab&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Data shape&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“Every entry will have a unique ID.”&lt;/td>
&lt;td style="text-align: left">Except for the ones copied from a legacy system, those were all blank&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Organizational clarity&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“Everyone understands what ‘urgent’ means.”&lt;/td>
&lt;td style="text-align: left">Turns out, it means “maybe next week” to one team and “drop everything now” to another&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Tool stability&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“That Excel template won&amp;rsquo;t change structure.”&lt;/td>
&lt;td style="text-align: left">Someone added three columns and changed the headers. Quietly. On a Friday&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Language/culture&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“Date formats? Everyone uses dd/mm/yyyy, right?”&lt;/td>
&lt;td style="text-align: left">Until the US team joined and booked a meeting for the wrong day. Twice&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Connectivity&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“The app will always have network access.”&lt;/td>
&lt;td style="text-align: left">Except in the warehouse. Or on the train. Or when VPN dies&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Ownership&lt;/strong>&lt;/td>
&lt;td style="text-align: left">“That’s the API team’s problem, not ours.”&lt;/td>
&lt;td style="text-align: left">The API team said the same about you. Nothing got fixed&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These feel safe, until one breaks, and it’s your team scrambling at 2 a.m.&lt;/p>
&lt;h2 id="how-to-catch-them-before-they-catch-you">How to catch them before they catch you&lt;/h2>
&lt;p>Assumptions aren’t inherently bad. You need them to move forward. But they should be explicit, testable, and ideally temporary. Here are a few ways to surface them early:&lt;/p>
&lt;ul>
&lt;li>Say them out loud. Literally start a meeting with: “Let’s list our assumptions.”&lt;/li>
&lt;li>Challenge default values. “Why is that the default? What if it&amp;rsquo;s wrong?”&lt;/li>
&lt;li>Use assumption mapping. A whiteboard or Miro board with three columns:
&lt;ul>
&lt;li>What we think is true&lt;/li>
&lt;li>What we’ve confirmed&lt;/li>
&lt;li>What we need to find out&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Add assumptions to your backlog. Track them like risks or tech debt.&lt;/li>
&lt;li>Involve people outside your bubble. Someone from sales, support, or compliance will spot gaps you missed.&lt;/li>
&lt;/ul>
&lt;p>And a favorite: ask the team, “What would really surprise us later?” The answers usually surface the biggest hidden assumptions.&lt;/p>
&lt;p>Spotting assumptions is the first step. But what do we do with them? That’s where validation comes in.&lt;/p>
&lt;h2 id="how-to-validate-assumptions-without-killing-momentum">How to validate assumptions without killing momentum&lt;/h2>
&lt;p>It’s one thing to spot an assumption; it’s another to do something useful with it. The goal isn’t to eliminate all risk, but to expose the shaky ground &lt;em>before&lt;/em> it becomes a crater. Here’s how we turn assumptions into validated decisions inside an agile setup:&lt;/p>
&lt;h3 id="1-prototype-early-and-for-the-right-reasons">1. Prototype early, and for the right reasons&lt;/h3>
&lt;p>Use quick spikes or throwaway prototypes specifically to test assumptions. Not to build a solution, but to answer a question:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Will this work with our existing system?&lt;/em>&lt;/li>
&lt;li>&lt;em>Does the user actually understand this flow?&lt;/em>&lt;/li>
&lt;li>&lt;em>Can we really get that data from that source?&lt;/em>&lt;/li>
&lt;/ul>
&lt;h3 id="2-make-assumptions-visible-in-your-backlog">2. Make assumptions visible in your backlog&lt;/h3>
&lt;p>Log them like you would bugs or tech debt. Use tags like &lt;code>@assumption&lt;/code>, or write stories like:&lt;br>
&lt;em>As a team, we need to confirm that customers can link multiple accounts to their profile.&lt;/em>&lt;/p>
&lt;p>This keeps the risk visible and gives you a chance to prioritize actual validation work.&lt;/p>
&lt;h3 id="3-include-assumption-checks-in-definition-of-ready">3. Include assumption checks in definition of ready&lt;/h3>
&lt;p>Before a story goes into a sprint, ask:&lt;/p>
&lt;ul>
&lt;li>What are we assuming here?&lt;/li>
&lt;li>Can we test or demo this assumption?&lt;/li>
&lt;li>What would we do if it&amp;rsquo;s wrong?&lt;/li>
&lt;/ul>
&lt;p>This prevents hidden dependencies from sneaking into sprint commitments.&lt;/p>
&lt;h3 id="4-demo-failures-not-just-successes">4. Demo failures, not just successes&lt;/h3>
&lt;p>In sprint reviews, don’t just show what worked. Show what you &lt;em>tested and ruled out&lt;/em>. It builds trust and makes it safe to admit uncertainty early.&lt;/p>
&lt;h2 id="closing-thought">Closing thought&lt;/h2>
&lt;p>Software fails quietly when we act on things we hope are true. It fails loudly when we act on things we assume are true. The fix isn’t endless documentation or 500-question checklists. It’s building a habit of naming our guesses and replacing them with knowledge when it counts.&lt;/p>
&lt;p>Make it safe to ask “what are we assuming here?” in every room. It’s not a sign of indecision; it’s a sign you want this thing to actually work.&lt;/p></description></item><item><title>Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check</title><link>https://m365princess.com/blogs/copilot2/</link><pubDate>Tue, 15 Jul 2025 06:53:41 +0000</pubDate><guid>https://m365princess.com/blogs/copilot2/</guid><description>&lt;p>You’ve built a Copilot Studio agent. It works in the demo. It talks to systems. You click “Publish” and it goes live. Except&amp;hellip; nothing about this is live in the way that matters. No pipelines. No environments. No version control. No audit trail. No rollback.&lt;/p>
&lt;blockquote>
&lt;p>If that’s your lifecycle, you haven’t shipped an agent. You’ve just made a sandbox louder.&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-publish-actually-means">What “Publish” actually means&lt;/h2>
&lt;p>Clicking &lt;strong>Publish&lt;/strong> in Copilot Studio updates the current version of your agent to all connected channels:Teams, Microsoft 365 Copilot Chat, web, mobile, Azure Bot Service. It’s a channel push, not a deployment pipeline. That distinction matters. If you’re working in your default environment and using Publish as your release mechanism, you’re bypassing everything that makes software resilient. Your logic goes live immediately. There’s no formal test phase. No way to rollback. And probably no documentation of what changed since the last release.&lt;/p>
&lt;h2 id="why-environments-matter">Why environments matter&lt;/h2>
&lt;p>Copilot Studio is part of the Power Platform stack. That means it inherits all the good, bad, and critical enterprise realities that come with it: environment isolation, solution packaging, data loss prevention (DLP), tenant-wide governance controls.&lt;/p>
&lt;p>The default environment isn’t meant for production. Microsoft explicitly recommends moving agents to dedicated environments. In the default, every user is an environment maker by default; there’s no permission isolation and no connector guardrails unless you enforce them yourself.&lt;/p>
&lt;p>Real deployments need a real environment strategy. That typically means separating development, testing, and production into distinct environments, each with its own access control, data boundaries, and connector rules. Microsoft calls this a &amp;ldquo;governance zone&amp;rdquo; model: Safe Innovation, Collaboration, and Enterprise zones. And yes, your agents should be living in those, not all lumped into one chaotic playground.&lt;/p>
&lt;h2 id="solutions-and-pipelines-are-not-optional">Solutions and pipelines are not optional&lt;/h2>
&lt;p>Agents in Copilot Studio are solution-aware Dataverse components. That means if you’re not using solutions, you’re not doing lifecycle management; you’re editing in production 😱.&lt;/p>
&lt;p>Once your agent is in a solution, you can export and import it across environments. You can also integrate it into Power Platform pipelines. So yes, ALM is very much real and supported.&lt;/p>
&lt;p>This unlocks proper versioning, change tracking, rollback support, and environment-specific configuration. The stuff that makes the difference between “This works” and “This won’t break everything tomorrow.”&lt;/p>
&lt;h2 id="agent-types-are-not-all-created-equal">Agent types are not all created equal&lt;/h2>
&lt;p>There’s a world of difference between a retrieval agent that fetches answers from SharePoint and an autonomous agent that acts on real-time signals without being asked.&lt;/p>
&lt;p>Copilot Studio supports three official agent types: retrieval, task, and autonomous. Retrieval agents are the least risky; they work off structured knowledge and user prompts. Task agents are a step up: They use flows or APIs to take action. Autonomous agents operate on triggers, make decisions, and act independently.&lt;/p>
&lt;p>&lt;img alt="spectrum of agents, source: Microsoft" src="https://m365princess.com/images/agents.png">&lt;/p>
&lt;p>Each category ramps up the governance requirements. Retrieval agents need decent content hygiene. Task agents need guarded connector access and flow versioning. Autonomous agents need escalation logic, fallback paths, and real-time monitoring.&lt;/p>
&lt;p>If you treat them all the same, especially in terms of deployment and testing, you’re walking into operational debt with your eyes closed.&lt;/p>
&lt;h2 id="publishing-to-a-channel-doesnt-mean-you-deployed-anything">Publishing to a channel doesn’t mean you deployed anything&lt;/h2>
&lt;p>Just because your agent is live in Teams or shows up in Microsoft 365 Copilot Chat doesn’t mean you’ve deployed it properly.&lt;/p>
&lt;blockquote>
&lt;p>Channels are UI. Environments are infrastructure.&lt;/p>
&lt;/blockquote>
&lt;p>If your agent wasn’t authored, tested, and promoted through a structured pipeline, it’s not production-grade. It’s just a lucky preview.&lt;/p>
&lt;p>💡Read more about &lt;a href="https://damianbrady.com.au/2018/02/01/friends-dont-let-friends-right-click-publish/">Friends don&amp;rsquo;t let friends right-click publish&lt;/a>.&lt;/p>
&lt;h2 id="governance-isnt-a-checklist-its-a-living-contract">Governance isn’t a checklist, it’s a living contract&lt;/h2>
&lt;p>Copilot Studio ships with strong governance capabilities. You can restrict who can share agents, limit connectors with DLP, apply sensitivity labels to protect data, and monitor usage with audit logs.&lt;/p>
&lt;p>Admins can control which environments agents can be built in, which connectors are allowed per environment, and how agents are exposed to users, especially inside Microsoft 365 Copilot.&lt;/p>
&lt;p>Most teams don’t configure these. So agents run on good intentions and shared credentials, not policy. And the moment that agent fails or acts on outdated data, or misroutes a workflow, everyone starts asking who signed off.&lt;/p>
&lt;h2 id="beyond-low-code-integrating-with-ai-foundry-and-copilot">Beyond low-code: Integrating with AI Foundry and Copilot&lt;/h2>
&lt;p>Copilot Studio isn’t just for citizen developers anymore. You can bring in Azure AI Foundry models, register custom skills, and create orchestration flows across multiple agents. But these advanced capabilities assume you’ve already nailed the basics: environments, solutions, pipelines, versioning, audit.&lt;/p>
&lt;blockquote>
&lt;p>If you haven’t? Don’t scale your chaos. Scale your discipline.&lt;/p>
&lt;/blockquote>
&lt;h2 id="bottom-line">Bottom line&lt;/h2>
&lt;p>Copilot Studio gives you the ability to create digital agents that listen, respond, and act. But with that power comes a non-negotiable requirement: ALM. Clicking “Publish” is not a deployment strategy. It’s a risk multiplier.&lt;br>
Your agent is code. Treat it like code. Because sooner or later, your users (or worse, your auditors) will expect it to behave like a real system.&lt;/p>
&lt;h2 id="coming-up-next">Coming up next&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Part 0&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot0/">Everything is an agent, until it isn&amp;rsquo;t&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot1/">When automation bites back – autonomy ≠ chaos&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot2/">Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check&lt;/a>&lt;/em> [📍 You are here]&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot3/">The cost of (in)action – what you’re really paying for with Copilot Studio&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot4/">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 5&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot5">From tool to capability – making Copilot Studio strategic&lt;/a>&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>'I’ll refactor this later': The lie that ships with every sprint</title><link>https://m365princess.com/blogs/refactor/</link><pubDate>Sun, 13 Jul 2025 15:25:44 +0000</pubDate><guid>https://m365princess.com/blogs/refactor/</guid><description>&lt;p>There’s a moment in nearly every development task where you hit a small decision:&lt;/p>
&lt;ul>
&lt;li>Do I clean this up now?&lt;/li>
&lt;li>Or do I just get it working and promise myself I’ll come back to it? 🤞🏻&lt;/li>
&lt;/ul>
&lt;p>The second option always feels easier. More efficient. Responsible, even: &amp;ldquo;I’m not skipping it forever, just for now.&amp;rdquo;&lt;/p>
&lt;p>But I&amp;rsquo;ll let you in on a secret that you won&amp;rsquo;t like: “I’ll refactor this later” is just “temporary” in business casual.&lt;/p>
&lt;p>&lt;img alt="work chronicles cartoon on refactoring" src="https://m365princess.com/images/wc-refactor-later.jpg">&lt;/p>
&lt;h2 id="what-we-really-mean-when-we-say-it">What we really mean when we say it&lt;/h2>
&lt;p>We don’t say it because we’re lazy. We say it because we’re under pressure. Deadlines are tight. Test cases are half-written. Someone’s waiting for this component. The PR already has 27 comments.&lt;/p>
&lt;p>So we choose to make the thing work, and tell ourselves we’ll tidy it up when there’s time. But time never comes back around. And the next time this function gets touched, the person working on it has no idea why the logic is split and there is a TODO comment referencing an old Jira ticket.&lt;/p>
&lt;blockquote>
&lt;p>Refactoring later isn’t a decision; it’s a delay with interest.&lt;/p>
&lt;/blockquote>
&lt;h2 id="the-emotional-debt-of-unfinished-cleanup">The emotional debt of unfinished cleanup&lt;/h2>
&lt;p>Unlike bugs or broken tests, this kind of debt doesn’t throw alarms. It just slows us down, silently. Every bit of unclear logic, every reused variable name, every shortcut we meant to fix: it all adds to the mental friction of working in that codebase. You stop trusting functions. You start working around things. You duplicate instead of reusing. And slowly, delivery speed drops.&lt;/p>
&lt;p>We don’t notice the cost until it’s too late; until someone says the words we all dread:&lt;/p>
&lt;blockquote>
&lt;p>“Should we just rewrite this?”&lt;/p>
&lt;/blockquote>
&lt;p>The decision to delay cleanup turns into a silent tax that compounds over time. It becomes harder to add features. Harder to test. Harder to onboard new teammates. We build tools to work around complexity we created ourselves. And worst of all? The code starts to feel hostile. You avoid touching certain files. You write messages that start with “sorry for this question…” You start dreading the next release.&lt;/p>
&lt;h2 id="when-later-doesnt-come">When later doesn’t come&lt;/h2>
&lt;p>Yes, it rarely does. Even when we create tickets for cleanup, they sit in backlog purgatory. Technical debt doesn’t scream. Features do (or your PM). And unless you have strong engineering culture, proactive refactoring doesn’t happen by default.&lt;/p>
&lt;ul>
&lt;li>Teams change&lt;/li>
&lt;li>Context gets lost&lt;/li>
&lt;li>Priorities shift&lt;/li>
&lt;/ul>
&lt;p>The person who wrote the code may never come back to it. And if they do, it’ll be six projects later, with no memory of what they meant to refactor. In practice, “later” becomes a black hole. Anything sent there is gone for good unless a heroic effort drags it back into the light.&lt;/p>
&lt;h2 id="the-jira-ticket-myth">The Jira ticket myth&lt;/h2>
&lt;p>Sometimes, to feel better, we create a “Refactor XYZ later” issue in Jira. At best, this is documentation of intent. At worst, it’s a digital guilt trap that no one will open again. If your definition of done requires you to create a follow-up ticket for every refactoring you skip, ask yourself: &lt;em>How often do those tickets actually get picked up?&lt;/em>&lt;/p>
&lt;p>Do they live in a “Tech Debt” column that’s never part of sprint planning? Are they invisible to the product owner? Are they always at the bottom of every grooming session? If the answer is “almost never”, then the ticket isn’t a fix. It’s a way to &lt;em>feel like&lt;/em> you’ve done the responsible thing, without actually doing it.&lt;/p>
&lt;p>Refactor tickets are only useful if:&lt;/p>
&lt;ul>
&lt;li>They’re prioritized like real work&lt;/li>
&lt;li>They’re short-lived&lt;/li>
&lt;li>They have enough context to be actionable later&lt;/li>
&lt;/ul>
&lt;p>If not, they’re just the long-form version of “I’ll refactor this later”. Worse, they give teams a false sense of closure. The real work hasn’t been done. But it’s been moved. Abstracted away. And eventually forgotten.&lt;/p>
&lt;h2 id="real-cleanup-isnt-heroic-its-incremental">Real cleanup isn&amp;rsquo;t heroic; it’s incremental&lt;/h2>
&lt;p>“I’ll refactor this later” is seductive because it feels pragmatic. But truly sustainable teams do the opposite: they refactor &lt;em>as they go&lt;/em>, in small, low-risk steps.&lt;/p>
&lt;p>That doesn’t mean polishing everything. It means for example:&lt;/p>
&lt;ul>
&lt;li>Renaming variables, functions, or classes so they express intent more clearly&lt;/li>
&lt;li>Extracting logic into smaller functions to make code easier to read and reuse&lt;/li>
&lt;li>Inlining unnecessary abstractions when they add no value&lt;/li>
&lt;li>Eliminating duplication&lt;/li>
&lt;li>Removing dead code and collapsing over-engineered conditionals&lt;/li>
&lt;li>Writing a small test before making a change so that the safety net exists—even if retrofitted&lt;/li>
&lt;/ul>
&lt;p>You don’t need a big refactor week. You need a culture where small, continuous improvements are normal. A culture that rewards care, not just output. And this culture doesn’t come from tools or templates, but from how teams talk about quality. It comes from leaders making space for cleanup. From reviews that notice and encourage thoughtful structure.&lt;/p>
&lt;h2 id="shipping-clean-ish-code">Shipping clean-ish code&lt;/h2>
&lt;p>Clean code isn’t a goal; it’s a habit. And habits don’t form around vague future promises. They form in the moment you &lt;em>almost&lt;/em> say “later”… and then fix it anyway. It’s not about perfection. It’s about reducing friction. It’s about building code you can come back to, not with fear, but with confidence.&lt;/p>
&lt;blockquote>
&lt;p>You don’t need to refactor everything. You just need to stop lying to yourself.&lt;/p>
&lt;/blockquote>
&lt;p>Leave it better than you found it. And don&amp;rsquo;t let “later” mean not &lt;em>never&lt;/em>.&lt;/p></description></item><item><title>There are no temporary fixes (just permanent shortcuts)</title><link>https://m365princess.com/blogs/temporary/</link><pubDate>Mon, 07 Jul 2025 15:25:44 +0000</pubDate><guid>https://m365princess.com/blogs/temporary/</guid><description>&lt;p>A while back, I wrote about &lt;a href="https://www.m365princess.com/blogs/tiny/">&lt;em>A hill to die on: there are no tiny changes&lt;/em>&lt;/a>, just changes we underestimate.
If you missed it, give it a read. But there’s a related myth we need to dismantle: &lt;em>“It’s just a temporary fix.”&lt;/em>&lt;/p>
&lt;p>Ah yes, the sacred chant of backlog triage. We all know how it goes:&lt;/p>
&lt;blockquote>
&lt;p>“This is just to get us unblocked.”&lt;/p>
&lt;p>“Let’s be pragmatic; we’ll clean it up later.”&lt;/p>
&lt;p>“We’ll write a proper solution next sprint.”&lt;/p>
&lt;/blockquote>
&lt;p>Spoiler: we won’t.&lt;/p>
&lt;h2 id="when-did-temporary-start-meaning-indefinite">When did temporary start meaning indefinite?&lt;/h2>
&lt;p>Once a fix goes live, it’s not temporary; it’s a &lt;em>default&lt;/em>. It becomes part of how the system works, how people rely on it, how your future self has to navigate it. That workaround? It’s now someone’s expectation. That duct-taped dependency? It’s in production, baby. Every “just for now” decision is a long-term liability waiting for an excuse to dig in its heels.&lt;/p>
&lt;h2 id="we-ship-intent-not-just-code">We ship intent, not just code&lt;/h2>
&lt;p>Think about it. That quick-and-dirty fix ships with no expiration date, no conditions, no built-in cleanup plan. And nobody, not the code, not your CI pipeline, not the product owner, is going to magically remember to “circle back”. So it stays. And much like I argued in &lt;em>tiny changes&lt;/em>, even the smallest-seeming fix can have wide ripple effects: Unexpected bugs. Fragile dependencies. Future blockers. All because we told ourselves this wasn’t the &lt;em>real&lt;/em> solution. But it is. It’s live. It counts.&lt;/p>
&lt;h2 id="the-real-problem-we-treat-code-like-a-whiteboard">The real problem: we treat code like a whiteboard&lt;/h2>
&lt;p>We think we can scribble something quick and erase it later. But software doesn’t work like that. Once it’s deployed, it’s not just code:it’s behavior. It has inertia. It gets integrated into other things. The longer it sits there, the harder it is to untangle. So we end up building systems that are a patchwork of “for now” glued together with hope and good intentions.&lt;/p>
&lt;h2 id="what-we-should-say">What we &lt;em>should&lt;/em> say&lt;/h2>
&lt;p>Instead of calling it a temporary fix, be honest about what you’re doing:&lt;/p>
&lt;ul>
&lt;li>“This is a compromise with risk”&lt;/li>
&lt;li>“We’re introducing technical debt deliberately”&lt;/li>
&lt;li>“We’re choosing speed over sustainability”&lt;/li>
&lt;/ul>
&lt;p>Then, if you go ahead with it:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Write it down like a bug&lt;/em> Don’t hide it behind a TODO; give it an owner and a deadline.&lt;/li>
&lt;li>&lt;em>Make the cost visible.&lt;/em> Add it to the roadmap as real work, not invisible hope.&lt;/li>
&lt;li>&lt;em>Flag it in review.&lt;/em> Code reviewers should treat “we’ll fix this later” as a blocker unless there’s a plan.&lt;/li>
&lt;/ul>
&lt;p>Let’s stop pretending our shortcuts don’t have long-term consequences. If we keep saying &lt;em>“we’ll fix it later”&lt;/em> without specifying when and how, we’re not just lying to others, we’re lying to ourselves. You can’t measure what you won’t acknowledge. You can’t clean up what you never wrote down. And you sure as hell can’t call something “temporary” if there’s no plan to remove it.&lt;/p>
&lt;p>So the next time you feel the urge to sneak in a fix and promise yourself it’s only for now, just remember:&lt;/p>
&lt;blockquote>
&lt;p>There are no temporary fixes. Only permanent shortcuts you haven’t regretted yet.&lt;/p>
&lt;/blockquote></description></item><item><title>Introducing QR Watchface: Your wrist’s new best friend</title><link>https://m365princess.com/blogs/qr/</link><pubDate>Sat, 05 Jul 2025 08:14:38 +0000</pubDate><guid>https://m365princess.com/blogs/qr/</guid><description>&lt;p>Ever wanted to share your LinkedIn profile straight from your Apple Watch, without fumbling for your phone? I’ve built something that makes it simple: &lt;a href="https://qrwatchface.netlify.app/">QR Watchface&lt;/a>, a sleek web app that turns any URL into a perfectly sized, scannable QR code for your watch face.&lt;/p>
&lt;p>&lt;img alt="QR Watchface screenshot" src="https://m365princess.com/images/qrwatchface-screenshot.png">&lt;/p>
&lt;h2 id="why-qr-watchface">Why QR Watchface?&lt;/h2>
&lt;p>Imagine this: You&amp;rsquo;re at a conference, or maybe a coffee meet-up, and you want to share your LinkedIn profile, but pulling out your phone and opening the app just isn’t fast enough. With QR Watchface, you get an instant, one-click solution. It generates a clean QR code, adds extra margin for visibility around your watch’s clock, and lets you download a polished image optimized for your Apple Watch face.&lt;/p>
&lt;p>And the best part? You can personalize it: Match the event’s color scheme or use your favorite colors. You get a QR code that looks good and works well.&lt;/p>
&lt;h2 id="heres-the-magic-in-a-nutshell">Here’s the magic in a nutshell&lt;/h2>
&lt;ul>
&lt;li>Choose your QR code and background colors, see the updates live, and get a quick warning if the contrast isn’t up to accessibility standards.&lt;/li>
&lt;li>Rounded corners, gradients, and a minimal pill-shaped download button. It’s sleek, polished, and feels right at home on iOS.&lt;/li>
&lt;li>The app exports a 4:5 aspect ratio PNG with your QR code at 70% scale, positioned perfectly at the bottom so your clock doesn’t interfere.&lt;/li>
&lt;li>Full support for dark mode and built-in contrast checks to meet WCAG standards, ensuring your QR code is always easy to scan, day or night.&lt;/li>
&lt;/ul>
&lt;h2 id="how-it-works">How it works&lt;/h2>
&lt;p>The app checks the &lt;a href="https://www.w3.org/WAI/WCAG22/quickref/?versions=2.1&amp;showtechniques=1411#contrast-minimum">WCAG&lt;/a> 2.1 contrast ratio in the background, so if your color choices fall below the minimum 4.5:1 contrast ratio, you’ll get a warning. Once you&amp;rsquo;re satisfied, hit &lt;strong>Download&lt;/strong> and get a crisp, watch-ready PNG.&lt;/p>
&lt;h2 id="accessibility-matters-and-it-makes-scanning-easier">Accessibility matters (and it makes scanning easier!)&lt;/h2>
&lt;p>Accessibility isn’t just about making sure everyone can read your QR code, it’s about ensuring a better scanning experience for everyone. By enforcing high-contrast colors, not only do we make sure it&amp;rsquo;s easy to read for people with visual impairments, but we also make scanning faster and more reliable for anyone. Low-contrast codes can lead to failed scans, but with clear, high-contrast combinations, your code will be read in one go—no more awkward “Can you try again?” moments.&lt;/p>
&lt;h2 id="give-qr-watchface-a-try">Give QR Watchface a try!&lt;/h2>
&lt;p>Let me know what you think! I&amp;rsquo;d love to hear your feedback: &lt;a href="https://qrwatchface.netlify.app/">https://qrwatchface.netlify.app/&lt;/a>&lt;/p></description></item><item><title>What Tinder🔥 taught me about bad Dataverse design</title><link>https://m365princess.com/blogs/tinder/</link><pubDate>Sat, 05 Jul 2025 08:14:38 +0000</pubDate><guid>https://m365princess.com/blogs/tinder/</guid><description>&lt;p>It started with a fish. Or more accurately, &lt;strong>a fish pic guy&lt;/strong>. You know the one: holding up a trout like it’s a personality. And when I saw Sara Lagerquist compare him to &lt;em>local choices in Dataverse&lt;/em>, I couldn’t unsee it. (&lt;a href="https://www.linkedin.com/posts/saralagerquist_powerplatform-plandesigner-dataverse-activity-7346816502412500993-1z-D?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAACfSBc8BQlFCS3cfPGN48UDKVDsC25R5xLM">She really did.&lt;/a>)&lt;/p>
&lt;p>Suddenly, the entire dating app experience started looking like a metaphor for Power Platform architecture. The red flags, the weird flexes, the mysterious disappearances, it’s all there. So here it is: my lovingly curated list of &lt;em>toxic Tinder traits and their Power Platform equivalents&lt;/em>. And most importantly: what we &lt;em>really really&lt;/em> want instead 💅.&lt;/p>
&lt;h2 id="-just-here-for-fun">🚩 &amp;ldquo;Just here for fun&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version:&lt;/strong> No intention of building anything serious.&lt;/p>
&lt;p>&lt;strong>Dataverse version:&lt;/strong> Apps are built in personal environments and shared informally, often with no use of solutions at all.&lt;/p>
&lt;p>&lt;strong>Why it’s bad:&lt;/strong>
Personal environments are tied to an individual user. If that user’s license changes or they leave the organization, apps and flows can become inaccessible or break. There is no backup mechanism, no structured deployment, and no visibility for admins.&lt;/p>
&lt;p>&lt;strong>What we really really want:&lt;/strong>
Use &lt;em>Managed solutions&lt;/em> in real environments, supported by &lt;em>application lifecycle management&lt;/em>. Adopt a Dev → Test → Prod structure using solution export/import, pipelines, and service accounts for ownership continuity.&lt;/p>
&lt;h2 id="-59-because-apparently-that-matters">🚩 &amp;ldquo;5'9 because apparently that matters&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version:&lt;/strong> Insecure flexing&lt;br>
&lt;strong>Dataverse version:&lt;/strong> Spends hours fine-tuning view layouts: adjusting column widths, alignment, and font size, while ignoring form usability or data model design.&lt;/p>
&lt;p>&lt;strong>Why it’s bad:&lt;/strong>
Aesthetic tweaks at view level don’t improve the user experience if forms are cluttered, poorly grouped, or not optimized for mobile. This leads to low adoption, frustrated users, and avoidable support tickets. Prioritizing cosmetics over structure signals a lack of UX maturity.&lt;/p>
&lt;p>&lt;strong>What we really really want:&lt;/strong>
Design for usability. Use &lt;em>tabbed forms, card sections, responsive layouts&lt;/em>, and define view columns based on meaningful business fields — not visual symmetry. Test across devices and screen sizes.&lt;/p>
&lt;h2 id="-sapiosexual">🚩 &amp;ldquo;Sapiosexual&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version:&lt;/strong> Claims to love intelligence, but mostly just wants to sound smart&lt;br>
&lt;strong>Dataverse version:&lt;/strong> Mentions GPT, AI Builder, vector embeddings, or Copilot Studio without having clean data, a clear prompt strategy, or a licensing model that supports usage.&lt;/p>
&lt;p>&lt;strong>Why it’s bad:&lt;/strong>
Deploying generative AI on top of unstructured or incomplete data (e.g., notes columns, plaintext metadata, inconsistent tags) results in unreliable output. Using OpenAI connectors without cost control leads to unpredictable billing. Overengineering AI features can also distract from core user needs.&lt;/p>
&lt;p>&lt;strong>What we really really want:&lt;/strong>
Use AI &lt;em>intentionally&lt;/em>. Invest in data preparation (structured columns, correct datatypes, standardized tags), and choose Copilot Studio or Azure AI integrations only when they serve a real business goal. Document model dependencies and cost implications.&lt;/p>
&lt;h2 id="-van-life-guy">🚩 &amp;ldquo;Van life guy&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version:&lt;/strong> Free spirit with zero stability&lt;/p>
&lt;p>&lt;strong>Dataverse version:&lt;/strong> Uses trial environments or developer environments for apps with live business users or production data.&lt;/p>
&lt;p>&lt;strong>Why it’s bad:&lt;/strong>
Trial environments are temporary! Flows using premium connectors may break when the environment expires. Admins cannot enforce policies or guarantee data recovery. Trial environments also do not support secure ALM or production SLAs.&lt;/p>
&lt;p>&lt;strong>What we really really want:&lt;/strong>
Use &lt;em>sandbox and production environments&lt;/em> with environment-level governance. Apply &lt;em>DLP policies&lt;/em>, define &lt;em>security roles&lt;/em>, and ensure &lt;em>retention policies&lt;/em> are in place. Trials are for demos and experiments, not business-critical workflows.&lt;/p>
&lt;h2 id="-crypto-bro">🚩 &amp;ldquo;Crypto bro&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version&lt;/strong>: Obsessed with hype, overconfident, and always promising exponential returns.&lt;/p>
&lt;p>&lt;strong>Dataverse version&lt;/strong>: Frequently rebuilds the solution architecture based on trends, switching from Canvas to model-driven, then to Power Pages, without completing any of them or defining a long-term data model.&lt;/p>
&lt;p>&lt;strong>Why it’s bad&lt;/strong>:
Changing technologies mid-project creates confusion, duplicate work, and inconsistent user experience. It also disrupts security planning, licensing, and support handover. Teams lose trust in the platform and developers when nothing is stable for more than a sprint.&lt;/p>
&lt;p>&lt;strong>What we really really want&lt;/strong>:
Define a stable architecture. Choose between &lt;em>Canvas, Model-driven, or Power Pages&lt;/em> based on use case, user roles, and licensing; and commit to the model. Use &lt;em>technical design documents&lt;/em> and &lt;em>decision logs&lt;/em> to guide change intentionally, not impulsively.&lt;/p>
&lt;h2 id="-lets-go-on-an-adventure">🚩 &amp;ldquo;Let’s go on an adventure&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version:&lt;/strong> Spontaneous. Dangerously so.&lt;/p>
&lt;p>&lt;strong>Dataverse version:&lt;/strong> Builds large Power Automate flows with 30+ actions, nested conditionals, and multiple connectors — with no naming conventions, no error handling, and no retry logic.&lt;/p>
&lt;p>&lt;strong>Why it’s bad:&lt;/strong>
When a flow fails, it’s difficult to troubleshoot without clear naming or scope grouping. Missing error handling leads to data loss or incomplete processes. Lack of logging makes audit trails impossible. Complex flows are harder to maintain and scale.&lt;/p>
&lt;p>&lt;strong>What we really really want:&lt;/strong>
Use &lt;em>scopes&lt;/em> to group logic, apply &lt;em>Configure Run After&lt;/em> for error handling, and &lt;em>compose&lt;/em> actions to isolate reusable logic. Document flows with comments and consistent naming. Build for readability and monitoring. Practice makes progress.&lt;/p>
&lt;h2 id="-guy-who-ghosted">🚩 &amp;ldquo;Guy who ghosted&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version:&lt;/strong> Promises the world; then vanishes&lt;/p>
&lt;p>&lt;strong>Dataverse version:&lt;/strong> Delivers a working app in an unmanaged solution and disappears; leaving behind undocumented business rules, JavaScript, and custom plugins with no source control.&lt;/p>
&lt;p>&lt;strong>Why it’s bad:&lt;/strong>
Unmanaged solutions in production environments are mutable. Changes won&amp;rsquo;t be tracked, and deploying updates can overwrite important customizations. With no documentation or ALM pipeline, maintenance becomes guesswork.&lt;/p>
&lt;p>&lt;strong>What we really really want:&lt;/strong>
Use &lt;em>managed solutions&lt;/em> in production and commit solution versions to source control. Document everything. Use &lt;em>pipelines&lt;/em> or at least manual export/import with release notes.&lt;/p>
&lt;h2 id="-my-ex-was-crazy">🚩 &amp;ldquo;My ex was crazy&amp;rdquo;&lt;/h2>
&lt;p>&lt;strong>Tinder version&lt;/strong>: Never their fault. Always someone else’s.
&lt;strong>Dataverse version&lt;/strong>: Blames Microsoft for everything: Slow forms, broken flows, weird errors; when it’s really their own unsupported JavaScript, sloppy plugin code, or broken logic.&lt;/p>
&lt;p>&lt;strong>Why it’s bad&lt;/strong>:
You’ve seen it: a form throws an error, and the first reaction is “Dataverse is buggy”. But dig a little deeper, and there’s a JavaScript function that silently fails. Or a flow that references a deleted column. None of it logged. None of it tested. Instead of fixing the issue, they escalate to IT, file support tickets, and rant in Teams, Reddit, and LinkedIn. Meanwhile, the real problem is still buried in unmanaged code.&lt;/p>
&lt;p>&lt;strong>What we really really want&lt;/strong>:
Take ownership. Write code that fails loudly and clearly. Use try/catch, trace logs, and show meaningful error messages to makers. If you add complexity, document it. Don’t blame Microsoft for problems you created; especially if you&amp;rsquo;re the only one who can fix them.&lt;/p>
&lt;h2 id="final-thoughts-tell-me-what-you-want">Final thoughts: Tell me what you want&lt;/h2>
&lt;p>We’ve all made red-flag decisions in Power Platform. Maybe you built that flow at midnight. Maybe you skipped governance “just this once”. Maybe you were the fish pic guy. But we can do better.&lt;/p>
&lt;p>So next time you design an app, a flow, or a solution:&lt;/p>
&lt;blockquote>
&lt;p>Be someone your future team would swipe right on.&lt;/p>
&lt;/blockquote>
&lt;p>Be someone who leaves behind &lt;em>clarity, not cleanup&lt;/em>.&lt;/p></description></item><item><title>Stop pretending your text is bold: why fake formatting breaks accessibility</title><link>https://m365princess.com/blogs/bold/</link><pubDate>Fri, 04 Jul 2025 18:29:45 +0000</pubDate><guid>https://m365princess.com/blogs/bold/</guid><description>&lt;p>We need to talk about something that keeps showing up in LinkedIn posts, company updates, and personal branding pep talks: fancy fake bold text. You’ve seen it. You’ve maybe even used it.&lt;/p>
&lt;p>𝗟𝗶𝗸𝗲 𝘁𝗵𝗶𝘀, or 𝘁𝗵𝗶𝘀, or even ⓉⒽⒾⓈ.&lt;/p>
&lt;p>💡 &lt;em>For everyone using a screenreader: There is a line that uses unicode characters to display something that resembles bold letters. Screenreaders can&amp;rsquo;t interpret these characters.&lt;/em>&lt;/p>
&lt;p>💡 &lt;em>For everyone not using a screenreader: Try CTRL + Shift + U in your browser, ot should enable the Read aloud feature for some aha effect&lt;/em>&lt;/p>
&lt;p>It looks cool, sure. It grabs attention. But it’s also a nightmare for screen readers, completely unsearchable, and let’s be honest: it’s a trick. A formatting hack. And the people using it usually know exactly what they’re doing.&lt;/p>
&lt;h2 id="whats-the-trick">What’s the trick?&lt;/h2>
&lt;p>LinkedIn doesn’t support bold text, italic, or even basic markdown formatting. So people turn to Unicode font generators: little tools that replace your normal letters with characters from obscure parts of the Unicode table that look bold, italic, or fancy. And yes, it makes the post stand out visually in the feed. That’s the whole point. But it comes at a cost.&lt;/p>
&lt;blockquote>
&lt;p>It’s not formatting — it’s a lie.&lt;/p>
&lt;/blockquote>
&lt;p>When you type 𝐁𝐨𝐥𝐝, you haven’t added formatting. You’ve swapped normal, readable letters for mathematical symbols. To a screen reader, that’s not “bold”. That’s:&lt;/p>
&lt;p>“Mathematical Bold Capital B” Or worse: nothing at all. People who rely on assistive tech won’t hear your clever headline. They won’t get your message. Because your entire paragraph just vanished into something they can’t read or understand. And it gets worse…&lt;/p>
&lt;h2 id="and-no-its-not-searchable-either">And no, it’s not searchable either&lt;/h2>
&lt;p>This trick also breaks basic functionality like search and indexing. If you write 𝐩𝐫𝐨𝐝𝐮𝐜𝐭𝐢𝐯𝐢𝐭𝐲 with fake bold letters, nobody searching for “productivity” will find it. It won’t show up in: LinkedIn search, sometime snot even in browser text search (CTRL+F), SEO indexing and translation tools. It looks like content. But it&amp;rsquo;s actually just noise. So yes, it&amp;rsquo;s a trick. And using it to game the feed while leaving part of your audience behind? That’s not clever. That’s unethical. Screen readers don’t see your branding tricks — they parse your intent. If it only works for sighted users, it doesn&amp;rsquo;t really work. Using formatting hacks to game the feed is tempting, especially when real formatting tools aren’t available. But building attention on the back of accessibility gaps is a dirty move.&lt;/p>
&lt;p>Your content should be visible, readable, searchable, understandable. For everyone. If it’s only beautiful to people who can see and ignore the technical glitches, you’ve missed the point.&lt;/p>
&lt;h2 id="the-real-flex-writing-that-works-for-everyone">The real flex? Writing that works for everyone&lt;/h2>
&lt;p>Using fake formatting to stand out is like putting neon stickers on a car that doesn&amp;rsquo;t run. It looks good parked. It fails the second someone tries to interact with it. Real professionals know how to write clearly, structure simply, and communicate accessibly. So before you paste in that shiny Unicode headline …Ask yourself:&lt;/p>
&lt;blockquote>
&lt;p>Am I being clever, or just unreadable?&lt;/p>
&lt;/blockquote>
&lt;p>Spoiler: the best posts are both stylish and searchable.&lt;/p></description></item><item><title>Copilot Studio: Part 1 – when automation bites back – autonomy ≠ chaos</title><link>https://m365princess.com/blogs/copilot1/</link><pubDate>Wed, 02 Jul 2025 18:29:45 +0000</pubDate><guid>https://m365princess.com/blogs/copilot1/</guid><description>&lt;p>Autonomy scares people. Not because they’re against AI. They’re against losing control, and rightly so. Most organizations aren’t afraid of what their Copilot Studio agent might do. They’re afraid of what it might do &lt;em>without asking&lt;/em>. And yet, that’s the whole point of agents. If you need a human in the loop for every decision, you didn’t build an agent. You built a clunky wizard. Autonomy, done well, is not chaos. Autonomy, done well, is clarity. But clarity takes work. And most deployments skip that part.&lt;/p>
&lt;p>[Sidenote: I hope you know this character:]&lt;/p>
&lt;p>&lt;img alt="secret character I won&amp;rsquo;t reveal here, but hey, you can out yourself on LinkedIN :-)" src="https://m365princess.com/images/lego-secret-blue.png">&lt;/p>
&lt;h2 id="autonomy-is-not-a-vibe">Autonomy is not a vibe&lt;/h2>
&lt;p>There’s a misconception that autonomy is a checkbox or a personality trait. As if some agents are &lt;em>independent&lt;/em> and others are &lt;em>obedient&lt;/em>. But autonomy isn’t about tone. It’s about timing, trust, and triggers. An autonomous agent doesn’t ask before it acts. It listens for signals. It builds a plan. It executes. Ideally, it also knows when to escalate. Not because you told it to in a script, but because it &lt;em>understood the stakes&lt;/em>. That’s not &lt;em>advanced AI&lt;/em>. That’s just responsible system design. Never heard about that? Might be a good idea to talk :-)&lt;/p>
&lt;h2 id="agents-dont-hallucinate-people-hallucinate-them">Agents don’t hallucinate: people hallucinate them&lt;/h2>
&lt;p>Here’s the uncomfortable truth: most agent hallucinations aren’t the model’s fault. They’re design failures. Someone told the agent it could retrieve knowledge and take action, but never clarified when to do which, or how to resolve conflicts between instructions and context.&lt;/p>
&lt;blockquote>
&lt;p>The mess isn’t in the model. The mess is in the instructions we shipped and forgot to version.&lt;/p>
&lt;/blockquote>
&lt;p>[💡 So in case you haven&amp;rsquo;t talked to me in a while: everything is code and should be in source control for versioning and traceability.]&lt;/p>
&lt;p>This is where autonomy becomes dangerous: not because the agent is too powerful, but because the environment around it is too fuzzy. No escalation logic. No fallback plans. No logging that anyone actually checks. It’s not that the agent acts alone. It’s that no one takes responsibility when it does.&lt;/p>
&lt;h2 id="autonomy-needs-a-job-description">Autonomy needs a job description&lt;/h2>
&lt;p>Want autonomy that doesn’t backfire? Treat your agents like new hires. They need&lt;/p>
&lt;ul>
&lt;li>Clear goals&lt;/li>
&lt;li>Explicit limits&lt;/li>
&lt;li>A decision framework&lt;/li>
&lt;li>Supervision that kicks in only when needed&lt;/li>
&lt;/ul>
&lt;p>In fact, the best mental model might be onboarding a junior colleague. You teach them how to handle 80% of cases. They ask for help on edge cases. Eventually, they escalate less because they’ve learned more. The difference? Your agents won’t magically learn unless you build for that too.&lt;/p>
&lt;h2 id="lets-start-with-retrieval-is-how-you-stay-stuck">&amp;ldquo;Let’s start with retrieval&amp;rdquo; is how you stay stuck&lt;/h2>
&lt;p>Organizations love to &amp;ldquo;start simple.&amp;rdquo; Let’s build a retrieval agent. Let’s just do FAQs. Let’s just surface policy links. That’s fine—if the goal is to stall. Because retrieval agents don’t scale business value. They reduce helpdesk noise, maybe. But they don’t change how work gets done. The moment you want the agent to take action (submit a request, assign a case, file a report) you’ve stepped into task or autonomous territory. And if your architecture, data model, and governance aren’t ready? You’re back to waiting for a human to fix it.&lt;/p>
&lt;h2 id="build-trust-into-the-agent-not-around-it">Build trust into the agent, not around it&lt;/h2>
&lt;p>We don’t need to wrap every agent in disclaimers and safety rails. We need to build trust &lt;em>into&lt;/em> the agent’s behavior. That means:&lt;/p>
&lt;ul>
&lt;li>Explainability: show users how the agent reached a conclusion&lt;/li>
&lt;li>Escalation: hand over control gracefully when the agent isn’t confident&lt;/li>
&lt;li>Containment: don’t let one bad action cascade across systems&lt;/li>
&lt;li>Auditability: store not just what the agent did, but &lt;em>why&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>Autonomy isn’t the enemy. It’s the maturity test. And right now, too many orgs are failing it.&lt;/p>
&lt;h2 id="coming-up-next">Coming up next&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Part 0&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot0/">Everything is an agent, until it isn&amp;rsquo;t&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot1/">When automation bites back – autonomy ≠ chaos&lt;/a>&lt;/em> [📍 You are here]&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot2/">Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot3/">The cost of (in)action – what you’re really paying for with Copilot Studio&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot4/">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 5&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot5">From tool to capability – making Copilot Studio strategic&lt;/a>&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>Copilot Studio: Part 0 – everything is an agent, until it isn't</title><link>https://m365princess.com/blogs/copilot0/</link><pubDate>Sun, 29 Jun 2025 04:35:21 +0000</pubDate><guid>https://m365princess.com/blogs/copilot0/</guid><description>&lt;p>We’ve entered the age of automation. But most organizations are still treating it like the age of macros. A little generative garnish, a few nicely structured prompts, and suddenly every flow is called an agent. It sounds impressive until you try to scale it. Then the cracks start to show. Copilot Studio is not just another way to automate tasks. It’s an entry point into something much more ambitious, and much more dangerous: systems that &lt;em>act&lt;/em>, not because a user clicks a button, but because they’ve been instructed to think, plan, and respond to context. Before we can talk about building anything meaningful, we have to stop calling everything an agent.&lt;/p>
&lt;h2 id="the-assistant-illusion">The assistant illusion&lt;/h2>
&lt;p>There’s been a narrative shift in AI; from tooling to teaming. Instead of apps and workflows, we now talk about assistants and agents. But that language obscures a fundamental difference.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Microsoft 365 Copilot&lt;/strong> is an assistant. It augments the user&lt;/li>
&lt;li>&lt;strong>Copilot Studio&lt;/strong> is where you build agents: systems that can reason and act&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>If M365 Copilot is here to help you work, Copilot Studio is here to replace part of that work.&lt;/p>
&lt;/blockquote>
&lt;p>This isn’t about putting a friendlier face on automation but about changing the nature of responsibility. Because once you ship an agent, you’re no longer guiding the outcome. You’re defining intent, and trusting the system to interpret it.&lt;/p>
&lt;p>&lt;strong>Microsoft 365 Copilot&lt;/strong> lives inside documents, chats, and emails. You prompt it. It helps. It never acts without you.&lt;/p>
&lt;p>&lt;strong>Copilot Studio agents&lt;/strong>, however, are defined by behaviors. They can&lt;/p>
&lt;ul>
&lt;li>Act on external systems (e.g., submit a return, open a support case)&lt;/li>
&lt;li>Make decisions based on knowledge and instructions&lt;/li>
&lt;li>Initiate workflows triggered by events, not just user prompts&lt;/li>
&lt;/ul>
&lt;p>When done right, this enables a new class of applications. When done badly, it introduces subtle, scalable chaos. And if you read on LinkedInm, you could get the impression, that everyone is succeeding in &lt;em>agentic AI&lt;/em>. This reminds me a bit about a &lt;a href="https://www.azquotes.com/quote/661939">quote by Dan Ariely&lt;/a>, who said &amp;ldquo;Big Data is like teenage sex: everyone talks about it, nobody really knows how to do it, everyone thinks everyone else is doing it, so everyone claims they are doing it.&amp;rdquo; - Replace &amp;ldquo;big data&amp;rdquo; with &amp;ldquo;AI&amp;rdquo; 💅&lt;/p>
&lt;h2 id="if-everything-is-an-agent-nothing-is">If everything is an agent, nothing is&lt;/h2>
&lt;p>The overuse of the word “agent” is a problem. A chatbot that answers FAQs is not an agent. A scripted topic with static buttons is not an agent. A flow that posts a Teams message on Mondays is not an agent. These things are useful, but they don’t think, plan, or act autonomously. (In a more rigid sense, also &lt;a href="https://www.m365princess.com/blogs/devops-ai-reason/">agents can&amp;rsquo;t reason&lt;/a>, that&amp;rsquo;s just us being helpless describing what AI does without using anthropomorphozing language)&lt;/p>
&lt;blockquote>
&lt;p>Just because it uses AI doesn’t mean it’s intelligent. And just because it runs without code doesn’t mean it should run without oversight.&lt;/p>
&lt;/blockquote>
&lt;p>The Copilot Studio architecture &lt;em>allows&lt;/em> for real agents. But it doesn’t enforce discipline. That’s your job. And most orgs aren’t ready; not because they lack the tools, but because they haven’t built the foundations.&lt;/p>
&lt;h2 id="automation-is-not-strategy">Automation is not strategy&lt;/h2>
&lt;p>We’ve seen this play out before. The first wave of Power Platform was hailed as the democratization of development. What we got, mostly, was an explosion of disconnected apps and flows, each automating a tiny corner of a bigger mess. Now, with AI in the mix, the risk is even greater. Because these aren’t just workflows. They are delegated decisions.&lt;/p>
&lt;blockquote>
&lt;p>The moment your system starts acting on your behalf, you’re responsible for what it &lt;em>thinks&lt;/em> you wanted.&lt;/p>
&lt;/blockquote>
&lt;p>That’s a governance problem. It’s a testing problem. But most of all, it’s a design problem. You’re not just designing logic anymore. You’re shaping behavior. And behavior, once deployed, is sticky. It creates expectations. It teaches your users how to interpret system actions. If those actions are inconsistent, opaque, or worse: wrong, then you’ve just shipped dysfunction at scale.&lt;/p>
&lt;h2 id="what-does-your-org-actually-need">What does your org &lt;em>actually&lt;/em> need?&lt;/h2>
&lt;p>Here’s the unfortunately inconvenient truth: most organizations don’t need agents yet. They need clarity. They need to fix the ten-year-old knowledge base article that their chatbot keeps quoting. They need to stop reinventing the same FAQ bot for every department. They need governance before autonomy. But here&amp;rsquo;s what else is true: every organization will eventually need agents. The value is undeniable. The potential to offload repetitive work, to respond to signals in real time, to build proactive services: that’s the future. But you won’t get there by accident. You’ll get there by design.&lt;/p>
&lt;h2 id="build-or-be-built">Build or be built&lt;/h2>
&lt;p>The reason so many Copilot Studio projects don&amp;rsquo;t deliver the value that was promised in very pretty slides isn’t because of a technical gap. It’s because most organizations are still trying to run agent work inside assistant-era structures. Teams want agents that behave autonomously, but only within processes that are undocumented, inconsistently owned, and resistant to change. That’s not agent design. That’s wishful thinking 🤞🤞.&lt;/p>
&lt;blockquote>
&lt;p>You don’t scale agents by writing better prompts. You scale them by fixing your operating model&lt;/p>
&lt;/blockquote>
&lt;p>Ask yourself:&lt;/p>
&lt;ul>
&lt;li>Who’s responsible when an agent gives a wrong answer?&lt;/li>
&lt;li>Who owns the lifecycle of that agent across environments?&lt;/li>
&lt;li>How will it be monitored, retrained, versioned, retired?&lt;/li>
&lt;/ul>
&lt;p>If your org can’t answer these questions, you’re not building software. You’re building risk.&lt;/p>
&lt;h2 id="this-is-where-we-start">This is where we start&lt;/h2>
&lt;p>This blog series isn&amp;rsquo;t a walkthrough. It’s not a Microsoft-endorsed cheerleading campaign. It’s a practical, sometimes uncomfortable look at what Copilot Studio really demands: from your systems, your people, and your leadership. If you’re here to do more than build bots: If you want to architect capability, not just features. you’re in the right place.&lt;/p>
&lt;h2 id="this-post-is-part-of-a-blog-series-on-copilot-studio">This post is part of a blog series on Copilot Studio&lt;/h2>
&lt;p>Coming soon:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Part 0&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot0/">Everything is an agent, until it isn&amp;rsquo;t&lt;/a>&lt;/em> [📍 You are here]&lt;/li>
&lt;li>&lt;strong>Part 1&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot1/">When automation bites back – autonomy ≠ chaos&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 2&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot2/">Copilot Studio: Part 2 – Copilot Studio agents: the ALM reality check&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 3&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot3/">The cost of (in)action – what you’re really paying for with Copilot Studio&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 4&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot4/">Agents that outlive their creators – governance, risk, and the long tail of AI&lt;/a>&lt;/em>&lt;/li>
&lt;li>&lt;strong>Part 5&lt;/strong>: &lt;em>&lt;a href="https://www.m365princess.com/blogs/copilot5">From tool to capability – making Copilot Studio strategic&lt;/a>&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>Software development is a decathlon (and low-code only gives you running shoes for two events)</title><link>https://m365princess.com/blogs/decathlon/</link><pubDate>Tue, 24 Jun 2025 08:35:21 +0000</pubDate><guid>https://m365princess.com/blogs/decathlon/</guid><description>&lt;h2 id="software-development-is-a-decathlon-and-low-code-only-gives-you-running-shoes-for-two-events">Software development is a decathlon (and low-code only gives you running shoes for two events)&lt;/h2>
&lt;p>Building apps with Power Apps &lt;em>feels&lt;/em> easy. Drag some controls onto a canvas, connect to a SharePoint List, throw in a few formulas — ta-da! A working prototype.&lt;/p>
&lt;p>Except… that’s not the race. That’s the warm-up.&lt;/p>
&lt;p>Shipping real apps (you know, the ones that solve real problems, scale beyond one user, and don’t fall apart next month) is more like a decathlon. You’re not just doing one thing well; you’re juggling ten disciplines at once. And low-code helps with maybe two or three.&lt;/p>
&lt;h2 id="the-real-decathlon-of-software-delivery">The real decathlon of software delivery&lt;/h2>
&lt;p>Here’s what it &lt;em>actually&lt;/em> takes:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Requirement engineering&lt;/strong>
Translating vague requests like “can you make it more user-friendly?” into testable, buildable, traceable features&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Problem solving &amp;amp; edge case hunting&lt;/strong>
Understanding the business logic &lt;em>behind&lt;/em> the button, not just placing the button&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Logical thinking &amp;amp; pattern recognition&lt;/strong>
Building once, reusing often; not repeating the same formula across 27 controls&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Syntax fluency&lt;/strong>
Whether it’s Power Fx, JavaScript, or a flow expression; your logic still has to make sense, and perform&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Data modeling&lt;/strong>
Designing structures that can scale, not storing everything in one flat SharePoint list with 200 columns&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>UI/UX &amp;amp; accessibility&lt;/strong>
Not just “looks fine on my screen”, but intuitive, inclusive, and mobile-friendly&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Testing &amp;amp; quality assurance&lt;/strong>
Writing clear test cases, automating where possible, and validating edge scenarios, not just hoping&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Documentation &amp;amp; supportability&lt;/strong>
If no one else can understand, update, or fix your app six months from now, it’s not a product; it’s a liability&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Integration &amp;amp; maintainability&lt;/strong>
Connecting systems in a way that survives password changes, API limits, and platform updates&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Lifecycle management &amp;amp; accountability&lt;/strong>
Versioning, auditing, knowing who owns what, and not deploying straight to production from someone’s trial environment&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="drowning child meme" src="https://m365princess.com/images/drown.png">&lt;/p>
&lt;h2 id="what-power-apps-made-easier">What Power Apps made easier&lt;/h2>
&lt;p>To be fair, low-code &lt;strong>did&lt;/strong> remove some friction:&lt;/p>
&lt;ul>
&lt;li>You can sketch out a UI fast&lt;/li>
&lt;li>You can connect to data without writing custom code&lt;/li>
&lt;li>You can build logic with Power Fx instead of full-stack frameworks&lt;/li>
&lt;/ul>
&lt;p>These are real advantages, especially for business users and prototyping. But let’s not kid ourselves. (at least not when nobody from MS is listening)&lt;/p>
&lt;h3 id="whats-still-hard-and-still-matters">What’s still hard (and still matters)&lt;/h3>
&lt;ul>
&lt;li>You still have to do &lt;strong>requirement engineering&lt;/strong>, even if the stakeholder is standing next to you.&lt;/li>
&lt;li>You still need &lt;strong>testing and documentation&lt;/strong>, especially when multiple people build on the same solution.&lt;/li>
&lt;li>You still need to care about &lt;strong>supportability&lt;/strong>, &lt;strong>ownership&lt;/strong>, and &lt;strong>accountability&lt;/strong>, because &amp;ldquo;the person who built it left the company&amp;rdquo; is not a great helpdesk answer.&lt;/li>
&lt;/ul>
&lt;p>And all those apps? They need to be &lt;strong>maintainable&lt;/strong> and &lt;strong>reusable&lt;/strong>, not a pile of one-offs duct-taped together in someone’s personal environment.&lt;/p>
&lt;blockquote>
&lt;p>Low-code doesn’t remove complexity. It just delays it.&lt;/p>
&lt;/blockquote>
&lt;p>And unless you’ve trained for the full decathlon, it’ll hit you in production.&lt;/p>
&lt;h2 id="fusion-teams-win-races">Fusion teams win races&lt;/h2>
&lt;p>Power Apps shines when it’s part of a &lt;strong>fusion team&lt;/strong> setup: business experts, pro devs, testers, IT, ops, all pulling in the same direction. It’s not about replacing developers. It’s about speeding up delivery without compromising on the fundamentals: clarity, quality, accountability. Because building fast isn’t the point. Building well, fast: that’s the win.&lt;/p>
&lt;h2 id="final-lap-know-what-race-youre-in">Final lap: Know what race you’re in&lt;/h2>
&lt;p>Low-code is powerful. But it’s not a shortcut to quality. If you only train for the “fun” parts — the visual design, the first-click wow moment — you’ll be in trouble by lap three. The real work is the rest of the decathlon: Requirements. Testing. Ownership. Reusability.
The things that make software &lt;em>work&lt;/em> long after the demo ends. So yes; lace up those low-code running shoes. But don’t skip leg day. You’re still in the full race.&lt;/p></description></item><item><title>Test Less. Test Better: The Modern Testing Stack</title><link>https://m365princess.com/blogs/test3/</link><pubDate>Tue, 17 Jun 2025 07:01:35 +0000</pubDate><guid>https://m365princess.com/blogs/test3/</guid><description>&lt;blockquote>
&lt;p>Fewer tests. Faster feedback.&lt;/p>
&lt;/blockquote>
&lt;p>Testing is broken; not because we don’t do it, but because we waste time doing it wrong. We obsess over unit tests that don’t matter. We write coverage reports that lie. We mock everything until our app is disconnected from reality. Then we go live and wonder why things break. Again.&lt;/p>
&lt;h2 id="the-test-pyramid-wasnt-meant-to-be-worshipped">The test pyramid wasn’t meant to be worshipped&lt;/h2>
&lt;p>The old test pyramid said: write lots of unit tests, fewer integration tests, and just a sprinkle of UI tests. That made sense when systems were simple. When most logic lived in the same codebase. When APIs were internal and your frontend talked to your backend directly.&lt;/p>
&lt;p>&lt;img alt="classic test pyramid" src="https://m365princess.com/images/lego-pyramid.png">&lt;/p>
&lt;p>But those days are gone. (At least for me.)&lt;/p>
&lt;p>Today, your app talks to other apps. It uses third-party services, runs across distributed infrastructure, and depends on config, roles, and runtime behavior that no unit test will ever see. And yet, most teams are still clinging to pyramids. They write thousands of unit tests for functions no one cares about and skip the tests that would have caught the real issues.&lt;/p>
&lt;blockquote>
&lt;p>Test behavior, not branches.&lt;/p>
&lt;/blockquote>
&lt;p>So let’s flip it: test what users do, not how your code is organized. Or even better: realize, that there is no strict test hierarchy&lt;/p>
&lt;p>&lt;img alt="Lego honeycombs" src="https://m365princess.com/images/lego-honeycombs.png">&lt;/p>
&lt;h2 id="behavior-beats-coverage">Behavior beats coverage&lt;/h2>
&lt;p>Your users don’t care whether you’ve tested 95% of your lines of code. They care whether their invoice gets generated. Whether they can sign in with their corporate account. Whether that file upload still works when the internet drops for five seconds.&lt;/p>
&lt;p>This is why behavior-driven testing matters. Tools like Playwright and Cypress let you simulate real user actions, not just internal logic. Instead of testing, &lt;em>Does this function return true?&lt;/em> you’re testing, &lt;em>Can a manager with restricted access approve a report when the backend service is slow?&lt;/em> These are tests that reflect production. They exercise critical paths. They surface regressions that matter. And they do so in a language everyone (from Dev to QA to Product) can understand.&lt;/p>
&lt;p>Both Playwright and Cypress are CI-native. They integrate seamlessly with GitHub Actions and Azure DevOps. You can run tests across browsers, inject user roles, and simulate flakiness, all automatically, on every PR. Behavior-first testing isn’t slower. It’s smarter. Because when it fails, you know something real is broken.&lt;/p>
&lt;h2 id="mocks-hide-the-truth-contracts-reveal-it">Mocks hide the truth. Contracts reveal it.&lt;/h2>
&lt;p>Most test environments are fake. We mock the API, stub the database, simulate users, and then celebrate that everything passes. Until production doesn’t. Why? Because mocks don’t tell you when something changed upstream. They don&amp;rsquo;t alert you when a provider you depend on adds a required field or renames a response property. The mock stays the same. But reality moved on.&lt;/p>
&lt;blockquote>
&lt;p>Contract testing fixes that.&lt;/p>
&lt;/blockquote>
&lt;p>Tools like &lt;a href="https://docs.pact.io/">Pact&lt;/a> and &lt;a href="https://pactflow.io/bi-directional-contract-testing/">PactFlow&lt;/a> don’t just mock, they verify. They let consumers define the behavior they expect from a provider: &lt;em>If I send this request, I expect that response&lt;/em>. And then they check that the provider still honors that agreement. This isn’t the same as validating against an OpenAPI spec. Swagger-based tools are useful for structural checks, but contract tests go deeper. They verify &lt;em>real&lt;/em> behavior across &lt;em>real&lt;/em> services. You stop wondering whether the integration still works. You know it does.&lt;/p>
&lt;p>&lt;img alt="qa-meme" src="https://m365princess.com/images/qa-meme.png">&lt;/p>
&lt;h2 id="accessibility-is-not-a-nice-to-have">Accessibility is not a nice-to-have&lt;/h2>
&lt;p>Most teams still treat accessibility like something they’ll get around to. Maybe someone runs a Lighthouse scan. Maybe someone files a bug. Maybe someone sues you.&lt;/p>
&lt;blockquote>
&lt;p>Accessibility isn’t just a checklist, it’s part of quality.&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>If a screen reader can’t read your modal, that’s a broken UI&lt;/li>
&lt;li>If keyboard users can’t navigate your app, that’s a critical bug&lt;/li>
&lt;li>If your contrast ratio fails standards, it’s a compliance risk&lt;/li>
&lt;/ul>
&lt;p>You can use &lt;a href="https://www.deque.com/axe/">axe&lt;/a> to test accessibility. Add this to your &lt;a href="https://playwright.dev/docs/accessibility-testing">Playwright&lt;/a> or &lt;a href="https://docs.cypress.io/app/guides/accessibility-testing">Cypress&lt;/a> flows. Automate them in your GitHub Action. There is no valid reason to defer this anymore.&lt;/p>
&lt;blockquote>
&lt;p>If your software is supposed to be used by everyone, it needs to be tested for everyone.&lt;/p>
&lt;/blockquote>
&lt;h2 id="test-how-it-breaks-not-just-that-it-works">Test how it breaks, not just that it works&lt;/h2>
&lt;p>Here’s what most tests don’t do: simulate failure. We check the happy path. We test with perfect data. We assume the network is stable, the auth token is valid, and the dependency responds in 200ms. But real systems break in boring, predictable ways:&lt;/p>
&lt;ul>
&lt;li>A token expires&lt;/li>
&lt;li>A permission is missing&lt;/li>
&lt;li>An API returns malformed JSON&lt;/li>
&lt;li>A service times out&lt;/li>
&lt;/ul>
&lt;p>These aren’t edge cases. They’re the default failure modes. Tools like &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/overview">Microsoft Dev Proxy&lt;/a> let you simulate bad responses and inject latency or broken payloads into your app in dev. Start injecting &lt;a href="https://learn.microsoft.com/en-us/microsoft-cloud/dev/dev-proxy/concepts/what-is-chaos-testing">chaos&lt;/a>. Simulate rate limits. Test bad tokens. Drop headers. Let your app break - on purpose. If you never test failure, you’re not testing production.&lt;/p>
&lt;h2 id="your-test-suite-should-be-fast-or-it-wont-be-used">Your test suite should be fast or it won’t be used&lt;/h2>
&lt;p>A modern test stack runs in CI, runs fast, and provides signal. You should know within minutes whether your change is safe, risky, or broken. The goal isn’t just automation. It’s trust. A fast, focused test suite builds trust in your codebase and confidence in every release. So test testing tools for their performance as well.&lt;/p>
&lt;h2 id="testing-is-a-leadership-problem">Testing is a leadership problem&lt;/h2>
&lt;p>Let’s talk to the business for a moment. If you want to move fast &lt;em>without&lt;/em> breaking things, your test strategy matters. If you want teams to innovate without fear, your feedback loops need to be short and reliable. If you want fewer outages, fewer hot fixes, and fewer Friday rollbacks, then testing has to shift from checkbox to strategy. Real testing isn’t about writing more code but about reducing uncertainty. It’s the difference between shipping with hope 🤞 and shipping with confidence.&lt;/p>
&lt;h2 id="test-smarter-not-harder">Test smarter. Not harder.&lt;/h2>
&lt;p>Coverage is a vanity metric. Confidence is the real one. You don’t need a thousand tests. You need the right ones. Test behaviors, not just functions. Verify contracts, not mocks. Run fast, catch failure, test what actually matters. Because if your test suite doesn’t reflect reality, it’s not protecting you, it’s lying to you. And if you want to move faster, scale smarter, and sleep better, then it’s time to modernize your stack.&lt;/p>
&lt;hr>
&lt;p>This blog post is part of a series on testing software. You can find more parts here:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test0/">Your Testing strategy is broken - Let’s fix it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test1/">The benefits of testing (beyond working apps)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test2/">Highway to the danger zone: No testing of business apps&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test3/">Test Less. Test Better: The Modern Testing Stack&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Highway to the danger zone: No testing of business apps</title><link>https://m365princess.com/blogs/test2/</link><pubDate>Sat, 14 Jun 2025 07:01:35 +0000</pubDate><guid>https://m365princess.com/blogs/test2/</guid><description>&lt;p>We love to pretend that business apps are boring. Predictable. Safe. Low risk. After all, we’re not launching rockets or handling billions of users… we’re just managing leave requests, budgets, logistics, workflows, customer data, and access rights. You know, just the things entire companies run on. But here’s the uncomfortable truth:&lt;/p>
&lt;blockquote>
&lt;p>Business apps are some of the riskiest systems we build&lt;/p>
&lt;/blockquote>
&lt;p>And they’re often the least well-tested. If they get tested at all (and no Kyle, I don&amp;rsquo;t mean you to click through the app that you&amp;rsquo;ve build and notice that &lt;em>it works like a charm&lt;/em>).&lt;/p>
&lt;p>So if you are in a project, where you do in fact test business apps, they’re the most likely to be surrounded by quality theater. Mock data. Manual clicks. &lt;em>Acceptance criteria&lt;/em> that describe buttons, not outcomes. Test suites that proudly show 95% coverage - and miss the fact that login fails for half the users in production.&lt;/p>
&lt;h3 id="the-illusion-of-safety-in-business-apps">The illusion of safety in business apps&lt;/h3>
&lt;p>Enterprise apps are integration-heavy. They pull data from ERPs, sync with CRMs, push records into legacy systems, and serve users with a dozen different roles. They’re built with multiple layers of logic: some in code, some in workflows, some in config, some in &lt;a href="https://www.m365princess.com/blogs/spreadsheet/">Excel sheets no one wants to admit still matter&lt;/a>.&lt;/p>
&lt;p>And we test them with… stubs. And mocks. We spin up test environments with sanitized data and assume it’ll behave the same as production. We run a few UI flows and declare victory. It looks good. Until real users do real things. That’s when a renamed field in SAP breaks a Power Automate flow. Or a SharePoint permission issue locks out an entire department. Or a regulatory field gets left blank because no one ever tested the edge case where &lt;em>null&lt;/em> means &lt;em>non-compliant&lt;/em>.&lt;/p>
&lt;p>&lt;img alt="cartoon about testing" src="https://m365princess.com/images/cartoon-testing.jpeg">&lt;/p>
&lt;h3 id="stubs-mocks-and-hope">Stubs, mocks, and hope&lt;/h3>
&lt;p>Most business app testing is built on mocked dependencies. We fake the response of an API. We stub out the database. We hardcode sample user roles. That’s fine for unit tests. It’s even useful when isolating tricky logic. But in business systems, the logic is rarely the problem. The integration is. You’re testing how your leave request process reacts to &lt;em>5 days off&lt;/em>, but not how it handles a broken HR system, or how it syncs to payroll, or what happens when a manager’s access level was misconfigured six months ago.&lt;/p>
&lt;blockquote>
&lt;p>Mocks don’t test reality. They test assumptions.&lt;/p>
&lt;/blockquote>
&lt;p>And assumptions, as we all know, are the mother of all f*ck-ups.&lt;/p>
&lt;p>&lt;img alt="integration" src="https://m365princess.com/images/no-integration-tests-meme-1.png">&lt;/p>
&lt;h3 id="quality-theater-vs-real-risk">Quality theater vs. real risk&lt;/h3>
&lt;p>Most test coverage in business apps tells you &lt;em>that code was executed&lt;/em>, not that value was delivered. You might have 100 tests on a plugin, but none on the actual sales process. You’ve tested that the invoice PDF renders, but never validated the tax calculation logic that runs when the customer is in a different country. Meanwhile, nobody’s tested accessibility in your custom forms. Or what happens when your dependency times out. Or whether your data policies are enforced when users export records in bulk. But hey — the test coverage is 91%. Green dashboard. All good, right?&lt;/p>
&lt;h3 id="low-code-makes-it-easier-to-build--and-easier-to-forget-what-comes-next">Low-code makes it easier to build — and easier to forget what comes next&lt;/h3>
&lt;p>Low-code platforms like Power Platform let teams move fast. They remove boilerplate. They open the door for non-developers to build real solutions. And they often work beautifully, until they don’t. The problem isn’t the platform. It’s what happens after the prototype. In many low-code environments, testing is either entirely manual or completely absent. Automation is rare. Pipelines are an afterthought. Real-world data is never tested. Why? Because the person who built the app is usually not a professional developer. They’re a domain expert. A problem-solver. A builder. They created something great, because no one else was going to. But now the business depends on it. And suddenly, this scrappy solution is infrastructure.&lt;/p>
&lt;p>Here’s the part nobody likes to say out loud:&lt;/p>
&lt;blockquote>
&lt;p>Making the baby is the fun part.&lt;/p>
&lt;/blockquote>
&lt;p>Raising it: maintaining it, securing it, testing it, governing it, is less glamorous. It’s not exciting anymore. It’s responsibility. And the person who built it? They’ve moved on. Or they’re overwhelmed. Or they were never trained to do long-term maintenance at scale. That’s how you end up with business-critical systems running untested, unmonitored, undocumented, and unowned. We have to stop pretending low-code means low-risk. If it runs your business, it deserves real engineering practices. And real testing.&lt;/p>
&lt;h3 id="so-what-do-you-do">So what do you do?&lt;/h3>
&lt;p>First: acknowledge the danger zone. Business apps may look simple on the surface — but they’re full of integration, compliance, and user-specific behavior that make them fragile.&lt;/p>
&lt;p>Then: test what matters. Start writing tests. But write fewer unit tests; write smarter integration tests. Test using real data patterns, not lorem ipsum. Validate API contracts, user roles, error flows, edge cases, and accessibility. Use tools that can test what actually happens — not just what’s supposed to.&lt;/p>
&lt;p>And finally: shift left. Bring QA into the requirements. Make behavior testable before the code exists. If a business rule can’t be verified, it’s not ready to build.&lt;/p>
&lt;h3 id="conclusion-if-it-looks-boring-look-again">Conclusion: If it looks boring, look again&lt;/h3>
&lt;p>The apps that look least exciting are often the most dangerous. Because we don’t give them the testing rigor they need. Because we assume low complexity where there’s actually high risk. Because we tape quality on at the end and hope it holds. And then we wonder why production keeps breaking after the &amp;ldquo;green&amp;rdquo; deploy.&lt;/p>
&lt;p>It’s time to stop pretending business apps are safe. They’re not. But they can be — if we start testing like they matter.&lt;/p>
&lt;hr>
&lt;p>This blog post is part of a series on testing software. You can find more parts here:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test0/">Your Testing strategy is broken - Let’s fix it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test1/">The benefits of testing (beyond working apps)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test2/">Highway to the danger zone: No testing of business apps&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test3/">Test Less. Test Better: The Modern Testing Stack&lt;/a>)&lt;/li>
&lt;/ul></description></item><item><title>You can’t outsource accountability</title><link>https://m365princess.com/blogs/accountability/</link><pubDate>Wed, 11 Jun 2025 07:01:35 +0000</pubDate><guid>https://m365princess.com/blogs/accountability/</guid><description>&lt;p>There’s something oddly reassuring about blaming &lt;em>the vendor&lt;/em>.&lt;/p>
&lt;ul>
&lt;li>The timeline slipped? Must be their fault.&lt;/li>
&lt;li>The app doesn’t work on tablets? Scope misunderstanding.&lt;/li>
&lt;li>We spent $32 million and still don’t have a working product? Clearly a delivery issue.&lt;/li>
&lt;/ul>
&lt;p>But behind every disaster labeled &lt;em>vendor failure&lt;/em>, there&amp;rsquo;s usually a deeper truth: someone on the client side stopped paying attention. Or was never really involved in the first place. Let me tell you a true story!&lt;/p>
&lt;h2 id="a-multi-million-dollar-website-that-never-launched">A multi-million-dollar website that never launched&lt;/h2>
&lt;p>A major brand in the travel sector decided it was time for a full digital overhaul. New website, mobile apps, content platform, modern user experience — the works. They hired one of the biggest names in global consulting to make it happen. They paid tens of millions over the course of the project. What they got:&lt;/p>
&lt;ul>
&lt;li>A desktop website that didn’t adapt to tablets (no, seriously)&lt;/li>
&lt;li>Code that was so poorly structured it had to be rewritten entirely&lt;/li>
&lt;li>Missed deadlines, broken features, and a system that never went live&lt;/li>
&lt;/ul>
&lt;p>After nearly two years and multiple extensions, they fired the consultancy and brought in someone else to rebuild the whole thing. Was the vendor bad? Honestly, I don&amp;rsquo;t know. But the client wasn’t present. Yes, the consultancy messed up. They missed the mark on critical requirements. They misunderstood scope. They delivered incomplete and, in some places, insecure code. But the client let it happen. No strong internal product ownership. No meaningful validation of deliverables. No feedback loop that kept the work aligned to actual business outcomes. They thought writing a check would buy them transformation. Instead, it bought them frustration.&lt;/p>
&lt;h2 id="what-actually-went-wrong">What actually went wrong&lt;/h2>
&lt;p>Key features were assumed, not defined. The client thought &lt;em>responsive design&lt;/em> obviously included tablets. The vendor delivered desktop and mobile — and then asked for more money to add tablet layouts. Reusability across brands was a goal, not a requirement. The client wanted a single platform for multiple brands and regions. The vendor built something that only worked for one. Technically correct; strategically useless. The front-end code was riddled with issues. Performance, maintainability, and even basic logic were subpar. It didn’t follow the standards of the platform it was supposed to be built on. &lt;a href="https://www.m365princess.com/blogs/test0/">Testing&lt;/a> was superficial. Some code was allegedly commented out to make it pass. The testing process looked good on paper — but failed to catch serious problems. Documentation and handover were incomplete. The client expected an interactive design system. The vendor sent PDFs and a bill for the rest. After terminating the contract, the client had to pay another firm to rebuild almost everything from scratch. Of course, there was a &lt;a href="https://upperedge.com/accenture/5-new-significant-developments-in-the-hertz-vs-accenture-case/">big lawsuit&lt;/a> as well.&lt;/p>
&lt;h2 id="lesson-learned-no-one-will-care-about-the-outcome-as-much-as-you-do">Lesson learned: No one will care about the outcome as much as you do&lt;/h2>
&lt;p>You can hire the biggest name in the business, but if you don’t stay engaged — if no one on your side owns the outcomes, asks the hard questions, and keeps the team honest — you’re not managing a transformation. You’re just spending money and hoping it works. I like to remind people of three things to remember for your next &lt;em>strategic&lt;/em> project:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Vendors build what you tell them. Not what you wish for. If you want extensibility, accessibility, or scalability, write it down. Get it into the contract. Assume nothing.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Outcomes need owners. There must be someone on your side whose job is to understand the vision and the delivery. Not just approve invoices.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Working software beats perfect plans. Progress is not a slide deck. Test everything early. Look at real functionality. If you’re surprised at go-live, you weren’t looking closely enough.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="enter-ai">Enter AI&lt;/h2>
&lt;p>Now why is all of that important to you? Because everywhere, organizations feel the pressure to implement AI, to digitally transform and to finally get all of that right. So organizations commit on buying new tools or explore ways to extend and integrate AI with existing systems. But if no one in your org understands how it’s being built, what it’s trained on, or why it matters, you’re just repeating that same IT failure, only faster and with more hallucinations.&lt;/p>
&lt;h2 id="heres-how-to-not-repeat-the-past">Here’s how to not repeat the past&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>Start with the job, not the model. What work are you trying to improve? Who does it today? What’s painful about it? If you can’t answer that in plain language, you don’t need AI, you need clarity.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Own the decision-making. Don’t let external partners dictate what &lt;em>AI&lt;/em> looks like for your business. Use them to build, not to decide. Your leadership team should define success and call the shots on what’s acceptable, usable, and scalable.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Don’t skip the governance conversation. It’s not just about data privacy. It’s about explainability, auditability, and long-term cost. Who’s updating this thing next quarter? What happens when the model drifts? If you don’t know, you’re not ready to ship.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Build internal muscle, even if you’re not coding. You don’t need a team of PhDs. But you do need product managers, domain experts, and data-literate leaders who can ask smart questions. If every AI decision is outsourced, you&amp;rsquo;re not transforming, you&amp;rsquo;re just renting tech theater.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The very inconvenient truth: the tools are better, but the habits are the same. We have better infrastructure, smarter models, and easier integrations than ever before. But if we approach AI with the same mindset that failed digital projects a decade ago, we’ll get the same result:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>🚨 High spend
🚨 Little value
🚨 Lots of finger-pointing&lt;/p>
&lt;blockquote>
&lt;p>AI is not a shortcut to strategic clarity.&lt;/p>
&lt;/blockquote>
&lt;p>It’s an amplifier. Of whatever culture and structure you already have. AI won’t save you, but ownership might. The lesson from that $32 million disaster wasn’t &lt;em>don’t trust vendors&lt;/em>. It was: Don’t abdicate leadership.&lt;/p>
&lt;p>If you&amp;rsquo;re serious about AI, don&amp;rsquo;t start with the pitch deck. Start with the problem. Start with the users. Start with someone internally who is accountable for every step from vision to deployment. And if you don’t have that person? Congratulations. You’ve just launched a very expensive experiment in disappointment.&lt;/p>
&lt;p>If this hits close to home, let’s talk. Not about the vendor. About the part you can control: How to lead a project that actually delivers.&lt;/p></description></item><item><title>The benefits of testing (beyond working apps)</title><link>https://m365princess.com/blogs/test1/</link><pubDate>Wed, 11 Jun 2025 06:07:13 +0000</pubDate><guid>https://m365princess.com/blogs/test1/</guid><description>&lt;p>Most teams treat testing as a cost. Something you do because you have to — like documentation or status reports. It’s seen as a necessary drag on velocity. A checkbox to tick before the sprint ends. A stage in the pipeline you hope turns green quickly so you can merge and move on. But what if we’ve been seeing it wrong all along?&lt;/p>
&lt;ul>
&lt;li>Good testing isn’t a drag. It’s leverage&lt;/li>
&lt;li>It doesn’t slow you down but lets you move faster, with confidence&lt;/li>
&lt;li>It doesn’t cost velocity but buys clarity, alignment, and trust&lt;/li>
&lt;/ul>
&lt;p>And in high-performing teams, it doesn’t live in isolation; it shapes design, guides development, and protects the entire product lifecycle.&lt;/p>
&lt;p>When testing is done well: thoughtfully, deliberately, and close to the source, it becomes one of the most valuable assets your team can invest in. Not just to prevent problems, but to unlock what’s next.&lt;/p>
&lt;p>&lt;img alt="feature complete?" src="https://m365princess.com/images/feature-complete.png">&lt;/p>
&lt;h3 id="speed-without-fear">Speed without fear&lt;/h3>
&lt;p>When teams don’t trust their tests, they hesitate. Releases get delayed. PRs pile up. Every small change feels risky, because no one knows what it might break. So they slow down — not because the code is hard, but because the safety net is full of holes.&lt;/p>
&lt;p>But when tests are meaningful and trusted, speed returns. A developer makes a change and sees the feedback immediately. A release goes out and everyone sleeps that night. The test suite stops being a formality and starts being a source of confidence. Velocity isn’t just a product of tools or team size, but a product of trust. And trust comes from knowing that when things break, you’ll know — and that when tests pass, it means something.&lt;/p>
&lt;h3 id="better-software-design">Better software design&lt;/h3>
&lt;p>It’s almost impossible to write good tests for bad architecture. The moment you try, it becomes obvious which components are too entangled, which responsibilities are blurred, and where assumptions haven’t been made explicit. Testability is often the first signal of good design — or its absence. Teams that care about test quality naturally start writing better code. Functions become purer. Interfaces become clearer. Complex logic gets isolated, not buried. Dependencies are injected, not hard-coded. It’s not because someone read a book on clean architecture. It’s because good tests require clarity — and clarity, when enforced at the boundary of verification, reshapes everything.&lt;/p>
&lt;blockquote>
&lt;p>You don’t write better software in spite of testing. You write it because of it.&lt;/p>
&lt;/blockquote>
&lt;h3 id="cleaner-onboarding-and-easier-maintenance">Cleaner onboarding and easier maintenance&lt;/h3>
&lt;p>Tests aren’t just for catching bugs. They’re also for catching up. A well-written test suite tells new developers how the system is supposed to behave. It shows what matters, what edge cases exist, and what you’ve already thought through. When someone new joins a team, they often read tests before they read code. Because tests show intent. They give you a safe environment to break something and learn from the fallout. You don’t need to ask the original author of the feature — the test tells the story. And for maintainers, a trusted test suite is what allows refactoring. If you can&amp;rsquo;t touch the code without holding your breath, you&amp;rsquo;re not in control. You&amp;rsquo;re hoping 🤞🤞 . That’s not maintainable software. That’s a house of cards. Good tests change that. They give you a way to change things, even years later, without fear.&lt;/p>
&lt;h3 id="real-alignment-across-dev-qa-and-product">Real alignment across Dev, QA, and Product&lt;/h3>
&lt;p>Many bugs don’t come from bad code. They come from broken communication. A requirement that was misread. A business rule that was unclear. A stakeholder assumption that never made it into the ticket. When testing is part of the requirements process — through shared examples, testable acceptance criteria, or behavior-driven development — those gaps close. The discussion shifts from &lt;em>what are we building?&lt;/em> to &lt;em>how will we know it works?&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>Developers, QA, and product speak the same language: expected behavior.&lt;/p>
&lt;/blockquote>
&lt;p>This doesn’t just reduce bugs, but it reduces handoffs, misalignment, and late-stage rework. It turns the test suite into a contract between everyone — and it turns &lt;em>done&lt;/em> into something objective. No more &lt;em>done (at least for now 🤞)&lt;/em> anymore!&lt;/p>
&lt;h3 id="resilience-under-scrutiny">Resilience under scrutiny&lt;/h3>
&lt;p>In regulated environments — healthcare, finance, energy, government — it’s not enough to say &lt;em>we tested this&lt;/em>. You need to prove it. You need to trace requirements to tests, show who validated what, and demonstrate that the system behaves as expected across every critical workflow. A duct-taped test suite full of mocks won’t do that. But a well-structured, traceable, behavior-first test strategy will. Means: When the heat is on, good tests are your proof, your playbook, and your safety net.&lt;/p>
&lt;h3 id="culture-shift">Culture shift&lt;/h3>
&lt;p>Perhaps the most underrated effect of good testing is what it does to team culture. When tests are meaningful, developers start owning quality. When quality is visible, Product starts caring about edge cases. When releases are reliable, trust builds across the org. Testing becomes less about catching mistakes and more about making progress possible. People stop firefighting and start improving. They stop dreading releases and start focusing on impact. QA is no longer the safety net at the end — they’re the co-designers of resilience from the start.&lt;/p>
&lt;p>And that’s when testing stops being a cost and starts being the reason your team performs like one.&lt;/p>
&lt;blockquote>
&lt;p>Conclusion: testing is leverage&lt;/p>
&lt;/blockquote>
&lt;p>Good testing doesn’t just prevent failure. It enables trust, speed, clarity, and progress. It’s not a tax. It’s a force multiplier. If your team still sees testing as a necessary evil — a checkbox, a hurdle, a phase — maybe it’s time to ask a better question:&lt;/p>
&lt;blockquote>
&lt;p>What would become possible if we actually trusted our tests?&lt;/p>
&lt;/blockquote>
&lt;p>That’s what good testing gives you. Not just fewer bugs. More possibility.&lt;/p>
&lt;hr>
&lt;p>This blog post is part of a series on testing software. You can find more parts here:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test0/">Your Testing strategy is broken - Let’s fix it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test1/">The benefits of testing (beyond working apps)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test2/">Highway to the danger zone: No testing of business apps&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test3/">Test Less. Test Better: The Modern Testing Stack&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Rethinking identity beyond passwords</title><link>https://m365princess.com/blogs/passwords/</link><pubDate>Tue, 10 Jun 2025 08:38:17 +0000</pubDate><guid>https://m365princess.com/blogs/passwords/</guid><description>&lt;blockquote>
&lt;p>Your password isn’t protecting you.&lt;/p>
&lt;/blockquote>
&lt;p>At least not in the way you think. Not anymore. We’ve built a whole mythology around passwords — how long they should be, how often they should change, what characters to include. We treat them like they&amp;rsquo;re the foundation of our digital security.&lt;/p>
&lt;p>They’re not.&lt;/p>
&lt;p>&lt;img alt="xkcd-authz" src="https://m365princess.com/images/xkcd-authz.png">
Source: &lt;a href="https://xkcd.com/1200/">https://xkcd.com/1200/&lt;/a>&lt;/p>
&lt;p>Usually, I don’t run security workshops. Most of the time, I’m there to talk about AI strategy, automation and custom apps with Power Platform and Azure, licensing and governance. But still &lt;em>every single time&lt;/em> someone will eventually ask about passwords. Usually in a side comment. &lt;em>Should we increase password length?&lt;/em> or &lt;em>Is it okay to allow password managers?&lt;/em> or even &lt;em>What is a reasonable password policy? IT forces us to change passwords every 6 weeks!&lt;/em>&lt;/p>
&lt;p>It’s like a reflex. And every time it comes up, I realize just how much confusion and outdated thinking still surrounds this topic. Most of the ideas we still cling to about password security are based on assumptions that stopped being relevant many years ago. We’re still designing policies to protect against brute-force attacks — as if that’s the main threat. But in the real world, that’s the &lt;em>last&lt;/em> thing an attacker tries. Before anyone even considers brute-forcing your password, they’ll try credential stuffing (using leaked passwords from another site). They’ll phish you with a fake login page. They’ll deploy malware to log your keystrokes. They’ll trick you into giving it away — and nine times out of ten, that works.&lt;/p>
&lt;p>That’s the real threat landscape. And none of it is stopped by making your password a few characters longer or swapping &lt;em>a&lt;/em> for &lt;em>@&lt;/em> or - as we collectively all decided, once vendors made it mandatory that at least one character needs to be a special character - adding an &lt;em>!&lt;/em> at the end of our existing password. So no, I don’t really care whether your password is 12 or 16 characters. It’s good practice to avoid reuse and common phrases, sure. But password strength is no longer a meaningful line of defense. If your identity strategy starts and ends with &lt;em>make better passwords&lt;/em>, you&amp;rsquo;re solving the wrong problem.&lt;/p>
&lt;p>What &lt;em>does&lt;/em> work?&lt;/p>
&lt;h3 id="1-mfa">1. MFA&lt;/h3>
&lt;p>Multi-factor authentication changes everything. It stops account compromise &lt;em>even if&lt;/em> the attacker has your password. And the numbers don’t lie — Microsoft’s own data shows that enabling MFA prevents over 99% of identity-based attacks. It’s not just effective; it’s essential. That’s why Microsoft enforced MFA for all accounts. But we can go further. And frankly, we should.&lt;/p>
&lt;h3 id="2-passwordless-authentication">2. Passwordless authentication&lt;/h3>
&lt;p>&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-passwordless">Passwordless authentication&lt;/a> with FIDO2 keys, Windows Hello, authenticator app push approvals, certificate-based logins: these are &lt;a href="https://www.youtube.com/watch?v=gAjR4_CbPpQ">harder, better faster, stronger&lt;/a> and far more secure than any password ever was. They’re phishing-resistant by design. They eliminate the weakest link: the password itself. When someone logs in with a biometric, or a device-bound key, or a hardware token, there’s nothing for an attacker to guess, steal, or reuse. Of course, going passwordless requires planning. You need a phased rollout, support for fallback methods, alignment with your Conditional Access policies. But it’s achievable — and honestly, overdue. The guidance is there. The tooling is mature. And the risk of doing nothing is growing. So the next time passwords come up — and they will — this is the conversation we need to have.&lt;/p>
&lt;p>Not about complexity requirements or expiration intervals. Not about special characters or clever passphrases.&lt;/p>
&lt;p>But about what actually works. What actually stops breaches. What actually reflects the modern threat landscape.&lt;/p>
&lt;p>It’s time to start treating passwords as the liability we’ve failed to let go of.&lt;/p></description></item><item><title>Confidently wrong: Why we misquote Maslow and misunderstand AI</title><link>https://m365princess.com/blogs/maslow/</link><pubDate>Mon, 09 Jun 2025 12:36:27 +0000</pubDate><guid>https://m365princess.com/blogs/maslow/</guid><description>&lt;h2 id="the-pyramid-that-never-was">The pyramid that never was&lt;/h2>
&lt;p>You’ve probably seen Maslow’s hierarchy of needs: a five-level pyramid that starts with food and shelter and ends with self-actualization. It’s tidy. It’s visual. It’s wrong.&lt;/p>
&lt;p>Maslow &lt;strong>never drew a pyramid&lt;/strong>. He described needs as overlapping tendencies, not a strict ladder. You don’t need to &lt;em>finish&lt;/em> belonging before pursuing esteem. You can feel creative even if you’re broke. And different people prioritize needs differently. Maslow himself said the hierarchy isn’t fixed.&lt;/p>
&lt;p>But the pyramid stuck. Why? Because it’s simple, feels logical, and gives people a sense of understanding. Enter: The Dunning-Kruger Effect!&lt;/p>
&lt;h2 id="the-confidence-of-the-underinformed">The confidence of the underinformed&lt;/h2>
&lt;p>The Dunning-Kruger effect is a well-documented cognitive bias where people with low expertise in a subject overestimate their competence, because they don’t know what they don’t know.&lt;/p>
&lt;p>In the case of Maslow, people saw the diagram, heard a catchy explanation in a training once, and started citing it with total confidence. They didn’t realize how much nuance they were missing, because they weren’t aware there was any nuance to begin with.&lt;/p>
&lt;p>This is exactly what’s happening with AI right now, especially in business.&lt;/p>
&lt;h2 id="ai-the-new-pyramid">AI: The new pyramid&lt;/h2>
&lt;p>We’re watching the same pattern unfold in real time. Executives and teams are:&lt;/p>
&lt;ul>
&lt;li>Rolling out &lt;em>AI strategies&lt;/em> based on tech they don’t truly understand&lt;/li>
&lt;li>Making ethical claims with no grounding in data governance or model architecture&lt;/li>
&lt;li>Confusing predictive text generation with reasoning or &lt;em>thinking&lt;/em>&lt;/li>
&lt;li>Assuming one flashy demo equals readiness for full enterprise deployment&lt;/li>
&lt;/ul>
&lt;p>Just like the pyramid, AI appears understandable at a glance. You talk to it; it talks back. It’s impressive. So, people assume they’ve got a handle on it. They feel confident talking about risks, investments, and capabilities, without seeing the missed complexities.&lt;/p>
&lt;p>This is dangerous in business. Not because people are malicious, but because they make high-impact decisions from a place of false certainty.&lt;/p>
&lt;h2 id="surface-level-understanding-leads-to-surface-level-strategies">Surface-level understanding leads to surface-level strategies&lt;/h2>
&lt;p>You wouldn’t base a company’s well-being strategy on a rigid version of Maslow’s pyramid. Yet many companies are building AI initiatives on equally flawed simplifications:&lt;/p>
&lt;ul>
&lt;li>&lt;em>AI will replace this whole department&lt;/em>&lt;/li>
&lt;li>&lt;em>Let’s use Copilot for everything&lt;/em>&lt;/li>
&lt;li>&lt;em>We just need a chatbot to automate the process&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>These are not strategies; they’re assumptions cosplaying as insight.&lt;/p>
&lt;h2 id="the-real-cost-of-overconfidence">The real cost of overconfidence&lt;/h2>
&lt;p>In both the Maslow and AI examples, the cost of misunderstanding isn’t just academic. It leads to:&lt;/p>
&lt;ul>
&lt;li>Poor design decisions&lt;/li>
&lt;li>Wasted investment&lt;/li>
&lt;li>Frustrated employees&lt;/li>
&lt;li>Missed opportunities&lt;/li>
&lt;li>Ethical missteps&lt;/li>
&lt;/ul>
&lt;p>What’s missing isn’t intelligence; it’s depth. And humility. The willingness to say, &lt;em>I don’t fully understand this yet—let’s dig deeper&lt;/em>.&lt;/p>
&lt;h2 id="so-what-can-we-learn">So what can we learn?&lt;/h2>
&lt;ol>
&lt;li>Be skeptical of oversimplified models&lt;/li>
&lt;li>Encourage questions, not just confidence&lt;/li>
&lt;li>Balance excitement with education: Yes, explore AI. But build internal literacy before you commit to major rollouts&lt;/li>
&lt;li>Remember that real expertise sounds cautious. The more someone knows about AI, the more they acknowledge its limits.&lt;/li>
&lt;/ol>
&lt;h3 id="final-thought">Final thought&lt;/h3>
&lt;p>Maslow didn’t give us a pyramid, and AI isn’t magic. In both cases, the danger lies not in the tool or theory, but in our overconfidence about how much we actually understand. So next time you hear someone explain AI with total certainty, pause for a second.&lt;/p>
&lt;p>You might just be listening to a pyramid in the making.&lt;/p></description></item><item><title>BYOM: Using Azure AI Foundry models in Copilot Studio</title><link>https://m365princess.com/blogs/copilot-foundry/</link><pubDate>Sat, 07 Jun 2025 08:51:08 +0000</pubDate><guid>https://m365princess.com/blogs/copilot-foundry/</guid><description>&lt;p>Copilot Studio gives you a fast, secure way to build conversational agents and deploy them into the Microsoft 365 environment without writing code. But what happens when the default models aren&amp;rsquo;t enough? That’s where &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/what-is-azure-ai-foundry">Azure AI Foundry&lt;/a> comes in. Copilot Studio now supports direct integration with models deployed via AI Foundry. That means you can choose exactly the model you need, like a language model with stronger reasoning capabilities, an image analysis model, or something domain-specific, and then connect it directly to your Copilot Studio agent.&lt;/p>
&lt;h2 id="example">Example&lt;/h2>
&lt;p>An IT team is managing support requests across multiple business units. Users often submit screenshots: Error messages, UI glitches, device warnings, but rarely with context. Traditional agents struggle with this format. Text-only prompts don’t help when the issue is visual. Instead of trying to route these through a human queue, we use Azure AI Foundry to deploy a vision model capable of:&lt;/p>
&lt;ul>
&lt;li>Performing OCR on screenshots&lt;/li>
&lt;li>Identifying key UI elements&lt;/li>
&lt;li>Classifying common issues based on screen layouts&lt;/li>
&lt;/ul>
&lt;p>We connect the model to a Copilot agent in Microsoft Teams. Now, users can upload a screenshot, and the agent can recognize a known error and suggest next steps within seconds. No manual ticket triage, no copy-paste from Outlook to ServiceNow.&lt;/p>
&lt;h2 id="how-to-connect-an-ai-foundry-model-to-your-copilot-agent">How to connect an AI Foundry model to your Copilot agent&lt;/h2>
&lt;ol>
&lt;li>Find a model in the huge AI Foundry catalog. You can also fine-tune an existing model&lt;/li>
&lt;li>Deploy your model in Azure AI Foundry&lt;/li>
&lt;li>You will get 3 values, that you need in one of the following&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>A deployment name&lt;/li>
&lt;li>An endpoint URL&lt;/li>
&lt;li>An API key&lt;/li>
&lt;/ul>
&lt;p>Once you have that, navigate to Copilot Studio and open the agent you want to enhance.&lt;/p>
&lt;ul>
&lt;li>Go to &lt;strong>Settings&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>Generative AI&lt;/strong>&lt;/li>
&lt;li>In the &lt;strong>Primary response model&lt;/strong> dropdown, select &lt;strong>Connect a model&lt;/strong> in the &lt;strong>From Azure AI Foundry (preview)&lt;/strong> section&lt;/li>
&lt;/ul>
&lt;p>Once prompted, paste in the:&lt;/p>
&lt;ul>
&lt;li>Target URI (endpoint)&lt;/li>
&lt;li>Deployment name&lt;/li>
&lt;li>API key&lt;/li>
&lt;/ul>
&lt;p>Copilot Studio will verify the connection. Once successful, your agent is now powered by that specific model.&lt;/p>
&lt;p>Boom, done! 🧨&lt;/p>
&lt;p>PS: This is a super new feature. In case you don&amp;rsquo;t see the &lt;strong>From Azure AI Foundry (preview)&lt;/strong> section please be patient, it will roll out soon™️. Also make sure, that in your Power Platform environment, you have the &lt;a href="https://learn.microsoft.com/en-us/power-platform/admin/early-release">&lt;strong>Get new features early&lt;/strong>&lt;/a> switch set to &lt;strong>Yes&lt;/strong>&lt;/p>
&lt;p>&lt;img alt="early access to new features" src="https://m365princess.com/images/early.png">&lt;/p></description></item><item><title>Your Testing strategy is broken - Let’s fix it</title><link>https://m365princess.com/blogs/test0/</link><pubDate>Tue, 03 Jun 2025 14:01:35 +0000</pubDate><guid>https://m365princess.com/blogs/test0/</guid><description>&lt;h2 id="test-coverage-is-not-quality">Test coverage is not quality&lt;/h2>
&lt;p>That’s a hard truth most teams don’t want to hear. But we need to. Because too many development teams are stuck in the wrong mindset: chasing 100% test coverage as if it guarantees safe deployments, stable apps, and happy users. Spoiler alert: It doesn’t.&lt;/p>
&lt;p>In fact, the more you worship coverage metrics, the more likely you are to miss the actual point of testing: to reduce risk and increase confidence in the system you ship. Most test suites in the wild aren&amp;rsquo;t robust safety systems — they&amp;rsquo;re duct-taped scaffolding, held together with mock data, over-simplified edge cases, and a hope that &lt;em>it probably won’t crash again&lt;/em> 🤞🤞. You’ll find unit tests that mock everything except the actual behavior, stubs that assume the happy path, and assertions that test for the existence of a response rather than its correctness. While exact numbers vary, industry observations consistently show that many defects in production stem from logic errors and integration issues — the kind that unit tests alone rarely catch. Reports from quality tooling vendors and case studies in enterprise environments highlight how overly mocked test suites often miss real-world behavior and system contracts. This pattern is common in integration-heavy systems, especially when test suites don&amp;rsquo;t include API contract validation. For example, many teams have reported incidents where upstream API changes—such as renamed fields or altered data types—slipped past their test suites because mocked data hid the real risk. These failures are rarely caught by line coverage metrics and often occur in systems showing high test coverage but lacking true behavioral checks. The coverage looks impressive. The confidence, in practice, is often misplaced.&lt;/p>
&lt;h2 id="the-illusion-of-safety">The illusion of safety&lt;/h2>
&lt;p>Test coverage tells you which lines of code have been executed during testing. That’s it. Not whether they were tested with relevant inputs. Not whether they behaved correctly. Not whether they matter to users. This is how you end up with 92% coverage and still break production.&lt;/p>
&lt;p>These aren&amp;rsquo;t edge cases. They are blind spots: the kind you miss when your &amp;ldquo;&lt;em>quality gate&lt;/em>&amp;rdquo; is a green bar glued on with test doubles and wishful thinking.&lt;/p>
&lt;p>&lt;img alt="qa meme" src="https://m365princess.com/images/qa-dev.png">&lt;/p>
&lt;h2 id="what-coverage-doesnt-tell-you">What coverage doesn’t tell you&lt;/h2>
&lt;p>Coverage can’t tell you if critical business flows actually work. It doesn’t confirm that your APIs behave correctly when the schema changes. It won’t catch performance regressions, accessibility issues, or security gaps. It tells you nothing about whether your tests reflect how real users interact with the system. You can hit every line and still fail when a third-party service returns null. Or break login for users with special characters in their email address. Or violate accessibility standards and expose your organization to lawsuits. If your strategy is built on coverage, you’re measuring activity, not assurance. You&amp;rsquo;re holding your production code together with duct tape and dashboard metrics.&lt;/p>
&lt;p>&lt;img alt="testing meme" src="https://m365princess.com/images/testing-meme.jpg">&lt;/p>
&lt;h2 id="why-business-apps-are-especially-at-risk">Why business apps are especially at risk&lt;/h2>
&lt;p>Enterprise applications: ERPs, CRMs, finance systems, low-code tools amplify the problem. They integrate across systems. They rely on real-world, unpredictable data. They must meet compliance, access control, and auditing requirements. A green coverage report in this world is like saying &lt;em>the blueprint looks fine&lt;/em> while the building has no plumbing. Or heating. Or working doors.&lt;/p>
&lt;h2 id="the-real-cost-of-bad-testing">The real cost of bad testing&lt;/h2>
&lt;p>The cost of getting it wrong is massive. Gartner estimates the average cost of IT downtime at around $5,600 per minute: That’s more than $300,000 per hour. In large enterprises, that number can spike much higher. Amazon once lost an estimated $2.6 million from just 13 minutes of downtime. When it comes to data breaches, &lt;a href="https://www.securityhq.com/reports/cost-of-a-data-breach-report-2023">IBM’s 2023 Cost of a Data Breach Report&lt;/a> puts the global average at $4.45 million.&lt;/p>
&lt;p>These are just the direct financial losses. The indirect costs cut deeper: burned out developers scrambling to patch things that should’ve been caught earlier, users losing trust after yet another &lt;em>oops&lt;/em> moment, and a team culture where people quietly tape things back together instead of fixing the foundation.&lt;/p>
&lt;h2 id="fix-it-at-the-source-requirements">Fix it at the source: requirements&lt;/h2>
&lt;p>If testing happens after coding, it’s already too late. At that point, you&amp;rsquo;re not validating understanding — you&amp;rsquo;re validating assumptions you hope were right. Testing should begin the moment you define the requirement.&lt;/p>
&lt;ul>
&lt;li>What’s the expected behavior?&lt;/li>
&lt;li>What’s the risk if it fails?&lt;/li>
&lt;li>How will we know it works — and how will we prove it?&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Good teams test.
Great teams integrate testing into the requirement process.&lt;/p>
&lt;/blockquote>
&lt;p>Modern engineering teams treat testing as part of requirements engineering. They don’t separate the &lt;em>what&lt;/em> from the &lt;em>how we’ll know it worked&lt;/em>. When a feature is proposed, so is its verification. A requirement without a testable outcome is a red flag 🚩.&lt;/p>
&lt;p>This is where Behavior-Driven Development (BDD) and example mapping change the game. Instead of vague feature specs, teams define shared examples: edge cases, failure modes, and success criteria. A user story becomes a testable scenario: &lt;em>Given a customer has no active subscription, when they log in, then they are redirected to the upgrade screen&lt;/em>. It’s not just about automation, but about alignment. When tests are derived from well-defined requirements:&lt;/p>
&lt;ul>
&lt;li>Developers know what success looks like&lt;/li>
&lt;li>QA doesn’t need to reverse-engineer intent&lt;/li>
&lt;li>Business stakeholders can read and understand the criteria&lt;/li>
&lt;li>Ambiguities are exposed before they become bugs&lt;/li>
&lt;/ul>
&lt;p>This practice shifts the energy from writing tests that merely touch the code, to designing tests that challenge the logic, the integration, the value. It closes the gap between &lt;em>we thought it worked&lt;/em> and &lt;em>we proved it works&lt;/em>.&lt;/p>
&lt;p>It also changes team dynamics. Now, QA isn’t just testing functionality, they’re challenging clarity. Product owners aren’t just writing features, they’re shaping outcomes. Developers aren’t just building to spec, they’re validating intent.&lt;/p>
&lt;p>This is where you stop taping quality on at the end and start designing it in from the beginning.&lt;/p>
&lt;h2 id="what-to-do-instead-of-chasing-100">What to do instead of chasing 100%&lt;/h2>
&lt;p>Shift your strategy. Focus on critical user flows; the parts of the system that matter most in production. Write tests that validate behavior and integration, not trivial implementation details. Treat accessibility and security as testable requirements, not afterthoughts. Use coverage as a signal, not a goal. And involve QA from the start of the process, not the end.&lt;/p>
&lt;h2 id="final-thoughts">Final thoughts&lt;/h2>
&lt;p>Testing isn’t about writing more tests. It’s about writing the right ones. Coverage makes you feel safe. Confidence means you are safe. Because if your test strategy is held together by tape and bubble gum, you can expect it to fall apart the moment anyone leans on it.&lt;/p>
&lt;hr>
&lt;p>This blog post is part of a series on testing software. You can find more parts here:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test0/">Your Testing strategy is broken - Let’s fix it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test1/">The benefits of testing (beyond working apps)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test2/">Highway to the danger zone: No testing of business apps&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/test3/">Test Less. Test Better: The Modern Testing Stack&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Tired of the AI hype machine</title><link>https://m365princess.com/blogs/elephant/</link><pubDate>Mon, 02 Jun 2025 07:12:23 +0000</pubDate><guid>https://m365princess.com/blogs/elephant/</guid><description>&lt;h2 id="the-feed-vs-the-floor">The feed vs. the floor&lt;/h2>
&lt;p>Open LinkedIn and you’ll see the same recycled energy over and over:&lt;/p>
&lt;ul>
&lt;li>&lt;em>AI will reinvent your entire industry!&lt;/em>&lt;/li>
&lt;li>&lt;em>The companies that don’t adapt will die!&lt;/em>&lt;/li>
&lt;li>&lt;em>Here’s how I use AI to 10x my thinking in under 3 minutes!&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>Then, in the comments:&lt;/p>
&lt;ul>
&lt;li>🔥 “Incredible insight!”&lt;/li>
&lt;li>🤝 “Exactly what I’ve been saying!”&lt;/li>
&lt;li>🧠 “Have you tried asking ChatGPT &lt;em>politely&lt;/em>?”&lt;/li>
&lt;/ul>
&lt;p>And so many rocket 🚀🚀🚀 emojis.&lt;/p>
&lt;p>Meanwhile, in actual companies:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Our Copilot licenses are active, but no one knows where to start&lt;/em>&lt;/li>
&lt;li>&lt;em>We built a chatbot, but it just repeats things from our website&lt;/em>&lt;/li>
&lt;li>&lt;em>We don’t trust the output, so we still do it manually anyway&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>This is the real gap:&lt;/p>
&lt;blockquote>
&lt;p>LinkedIn is busy arguing over whether AI understands &amp;lsquo;please&amp;rsquo; and &amp;rsquo;thank you&amp;rsquo; while orgs are still struggling to make AI useful at all.&lt;/p>
&lt;/blockquote>
&lt;p>Most AI implementations right now are stuck at what I call the &lt;strong>chatbot plateau&lt;/strong>. We’re not solving real business problems, we’re giving people a generic chat interface and calling it innovation. Instead of improving how knowledge is structured or decisions are made, we offer employees yet another vague prompt box: &lt;em>Ask me anything!&lt;/em> The result? A confused user asking for the parental leave policy, only to get an outdated file, a broken link, or a non-answer dressed up in polite language. This isn’t transformation; it’s deflection. Slapping a chatbot onto an intranet doesn&amp;rsquo;t fix content chaos, broken search, or unclear ownership. It just buries it one layer deeper. And yet, this is what passes as &lt;em>AI integration&lt;/em> in far too many orgs right now, because it demos well and looks futuristic, even when it delivers nothing. So while the feed celebrates another prompt tip or AI-generated image, the floor is dealing with broken systems and bigger questions.&lt;/p>
&lt;h2 id="the-ai-theatre-is-exhausting">The AI theatre is exhausting&lt;/h2>
&lt;p>You’ve probably seen the elephant. Someone prompts a generative model: &lt;em>Create an image of a group of people with no elephant in it.&lt;/em> What do you get? A lovely picnic scene… with a giant elephant in the middle. Funny? Sure. Insightful? Maybe once. Helpful to anyone trying to automate a broken procurement process? Not even slightly.&lt;/p>
&lt;p>This is the noise:&lt;/p>
&lt;ul>
&lt;li>Prompt hacks, AI-generated carousels, and ethics debates held entirely in abstract&lt;/li>
&lt;li>Thought leaders posting about their &lt;em>AI-first mindset&lt;/em> from the comfort of a slide deck&lt;/li>
&lt;li>Dozens of use cases that are clever, but not valuable&lt;/li>
&lt;/ul>
&lt;p>And in the background, the actual AI problems remain unsolved: Data quality. Governance. Integration. User adoption. Real-world constraints.&lt;/p>
&lt;h2 id="what-working-ai-actually-looks-like">What working AI actually looks like&lt;/h2>
&lt;p>The irony? AI does have real potential. But the projects that succeed don’t start with elephants, ethics debates, or tip-of-the-day posts. They start with grounded questions:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Where is our time being wasted?&lt;/em>&lt;/li>
&lt;li>&lt;em>Which decisions are based on guesswork?&lt;/em>&lt;/li>
&lt;li>&lt;em>Which manual steps could be automated safely and meaningfully?&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>Transformation happens when teams define real problems, connect usable data, and build human-first, feedback-driven workflows and then, only &lt;em>then&lt;/em> add AI where it helps.&lt;/p>
&lt;p>&lt;img alt="legacy chatbot" src="https://m365princess.com/images/legacy-chatbot.jpeg">&lt;/p>
&lt;h2 id="stop-solving-imaginary-problems">Stop solving imaginary problems&lt;/h2>
&lt;p>&lt;a href="https://www.m365princess.com/blogs/ai-please/">Saying “thank you”&lt;/a> to a language model doesn’t affect your ROI. Neither does creating a prompt that includes &lt;em>without an elephant&lt;/em>. Here’s what does:&lt;/p>
&lt;ul>
&lt;li>Building use cases that reduce real friction for real users&lt;/li>
&lt;li>Understanding when automation is helpful—and when it’s just noise&lt;/li>
&lt;li>Getting past the AI-as-magic thinking, and focusing on design, intent, and structure&lt;/li>
&lt;/ul>
&lt;p>I’ve seen far more impactful results from AI when it’s embedded deeply into processes and not just layered on as a chatbot. In past projects, I’ve worked with teams to implement predictive maintenance using sensor data to prevent costly downtime in industrial environments. We’ve used AI-powered document intelligence to extract clauses, renewal dates, and obligations from thousands of contracts. This saved legal teams hours of manual review and triggering automated workflows in Microsoft 365. In finance, I’ve helped build models to detect anomalies in transactional data to flag potential risks long before audits catch them. HR teams I’ve worked with now use AI to identify bias in job descriptions and streamline internal mobility by matching people to roles based on skills, not job titles. I’ve also helped service teams move past basic bots and into intelligent routing, where incoming requests are prioritized and sent to the right team based on AI-driven classification and not just a keyword match.&lt;/p>
&lt;p>None of these required a &lt;em>chat with your data&lt;/em> interface. They required clear goals, usable data, and the right architecture. These projects don’t go viral. But they solve actual problems. That’s the kind of AI transformation I’m here for.&lt;/p>
&lt;h2 id="final-thought">Final thought&lt;/h2>
&lt;p>I believe in the potential of AI. But I also believe most organizations are still early in the journey, despite what LinkedIn would have you believe. So if you’re not chasing the elephant or debating prompt tone, good. You’re probably closer to meaningful AI than most of the &lt;em>visionaries&lt;/em>. Let’s keep doing the work. The real kind.&lt;/p></description></item><item><title>Should I automate it?</title><link>https://m365princess.com/blogs/automate/</link><pubDate>Thu, 22 May 2025 12:11:24 +0000</pubDate><guid>https://m365princess.com/blogs/automate/</guid><description>&lt;p>Somewhere between your fourth copy-pasted email of the day and yet another spreadsheet update, you wonder: should I just automate this? It’s a fair question; and honestly, a tempting one. But context is everything. Before diving in, it’s worth pausing and asking yourself a few grounded questions. Think of this as a guided conversation with your future self (and hopefully someone from governance).&lt;/p>
&lt;h2 id="1-whos-affected-and-how-badly">1. Who’s affected, and how badly?&lt;/h2>
&lt;p>Start with the scope. If this automation is just for your own inbox triage or filing PDFs you get daily, go ahead: low risk, low impact. The worst-case scenario is you create a Frankenstein flow, spend an afternoon debugging it, and walk away with a valuable lesson. But as soon as your automation starts to touch more than just your own workflow, it enters a different league. Automating a small handover between two people might seem harmless, but even simple automations can introduce fragility. What happens if your colleague is on leave? If they change the file name? If your flow breaks, will it quietly fail or silently drop the ball on a customer order? Let’s take it further. Automating a process that supports onboarding, procurement, or customer communication? Now you’re interfering with how others work, and you’re also reshaping accountability. You&amp;rsquo;re influencing SLAs, timelines, and expectations. If you’re working on payroll, compliance reports, or IT provisioning, the potential for damage gets even more real. These aren’t just workflows; they’re risk surfaces. That’s where architecture, stakeholder alignment, and robust testing come in. It’s not about being slow or bureaucratic but about building solutions that can withstand change, edge cases, and shared responsibility. You wouldn’t rewire the office network just because you read a blog post about routers. Don’t do it with processes either.&lt;/p>
&lt;p>In short:&lt;/p>
&lt;blockquote>
&lt;p>Personal automations are great playgrounds.&lt;/p>
&lt;/blockquote>
&lt;p>But the moment others rely on your output, it&amp;rsquo;s no longer just about your efficiency but about shared trust. And shared trust deserves structure.&lt;/p>
&lt;h2 id="2-process-quality-clarity-or-chaos">2. Process quality: clarity or chaos?&lt;/h2>
&lt;p>Let’s assume the intent is noble. Automating repetitive work sounds good in theory, but is the process itself stable and well-understood enough to be automated? A well-documented, consistent, mostly manual process is a strong candidate. Think of steps like &lt;em>check incoming support tickets&lt;/em>, &lt;em>assign them based on category&lt;/em>, or &lt;em>send a weekly summary to stakeholders&lt;/em>.These are clear, repeatable, and generally predictable.&lt;/p>
&lt;p>Now compare that to a process that involves chasing people down via chat, digging up templates from someone’s personal OneDrive, or interpreting vague instructions like &lt;em>do what we did last time&lt;/em>. That’s not a process, it’s folklore. If your workflow includes steps like &lt;em>just ping Sandra, she knows&lt;/em>, or &lt;em>send a friendly reminder&lt;/em>, automation won’t solve the real issue. You’ll just bury a flawed process under layers of technology. And that’s not just opinion. According to a &lt;a href="https://www.mckinsey.com/capabilities/transformation/our-insights/common-pitfalls-in-transformations-a-conversation-with-jon-garcia">McKinsey study&lt;/a>, around 70% of large transformation efforts fail, often due to unclear or shifting processes. Automating something that’s ambiguous doesn’t reduce the problem, it reinforces it. It gives people a false sense of reliability while quietly perpetuating all the same inefficiencies.&lt;/p>
&lt;p>Let me repeat this:&lt;/p>
&lt;blockquote>
&lt;p>Automating junk processes is how you turn temporary inefficiencies into permanent confusion.&lt;/p>
&lt;/blockquote>
&lt;p>Before automating, consider mapping the process end-to-end. Identify who does what, when, with what inputs and expected outcomes. If you can&amp;rsquo;t write that down in a way others understand, automation isn’t the next step, clarity is. (if you need help with this step, hit me up)&lt;/p>
&lt;h2 id="3-data-quality-dream-or-disaster">3. Data quality: dream or disaster?&lt;/h2>
&lt;p>You can&amp;rsquo;t automate what you can&amp;rsquo;t trust. High-quality data (clean, structured, accessible via APIs or databases) is the fuel automation runs on. If your information lives in a reliable system of record, is updated consistently, and communicates in formats that other systems understand, you’re in a strong position. Automating with this kind of data feels like adding a turbocharger to a well-built engine.&lt;/p>
&lt;p>But let’s be honest: many of us aren’t dealing with ideal conditions. If you&amp;rsquo;re &lt;a href="https://www.m365princess.com/blogs/spreadsheet/">scraping Excel files from email attachments&lt;/a> or copy-pasting values from PDFs, you’re already on thin ice. These &lt;em>data sources&lt;/em> often come with missing fields, inconsistent naming, and formatting quirks that break flows without warning.&lt;/p>
&lt;p>A simple rule of thumb: if it takes a human 15 minutes to clean the data every time, automating that cleanup will likely take 15 hours, and it will still break the moment someone renames a column or inserts a rogue emoji into a free-text field. And when things break, they often do so silently, leading to incorrect outputs, corrupted records, or decisions made on flawed inputs. This isn’t just inefficient or expensive, but also dangerous. Bad data at scale is worse than no data at all. You’re not improving a process; you’re just accelerating your mistakes.&lt;/p>
&lt;p>If you want a horror story, look no further than &lt;a href="https://www.m365princess.com/blogs/data/">organizations still using spreadsheets as the backbone of operations&lt;/a>, something we explored in this blog post. These legacy &lt;em>systems&lt;/em> often masquerade as structured data sources but behave like digital sandcastles. So before you start automating, ask: &lt;em>is the data fit for purpose?&lt;/em> If not, clean the foundation first. Otherwise, you&amp;rsquo;re building a robot that trips over the same mess every day.&lt;/p>
&lt;h2 id="4-time-saved-vs-time-spent">4. Time saved vs. time spent&lt;/h2>
&lt;p>This is where that brilliant XKCD chart comes in handy. The premise is simple but powerful: how often do you do the task, how much time does it take, and how long will it take to automate it? This is the math behind whether your automation effort is worth it.&lt;/p>
&lt;p>&lt;img alt="is it worth the time XKCD" src="https://m365princess.com/images/is_it_worth_the_time_2x.png">&lt;/p>
&lt;p>Visual taken from: &lt;a href="https://xkcd.com/1205/">https://xkcd.com/1205/&lt;/a>&lt;/p>
&lt;p>Say you spend five minutes per day manually doing a task. Over five years, that adds up to just over 21 hours. If your automation takes more than two to three full days to build, test, deploy, and occasionally fix, you may never see a return on that investment. Especially if the task is tolerable and unlikely to change. Now, this doesn&amp;rsquo;t mean all &lt;em>small&lt;/em> automations are a waste. If the process is prone to human error, causes frequent context switching, or is a bottleneck for others, the value may go far beyond the raw time saved. Speed is only one axis of improvement, quality, accuracy, and scalability matter too.&lt;/p>
&lt;p>Still, it’s important to be honest about the cost-benefit tradeoff. Many developers (I might be guilty of that as well) enjoy automating things for the intellectual challenge or the elegance of the solution. But sometimes, the smartest move is to keep doing that task manually, especially if you’ll spend more time maintaining the automation than you ever did on the task itself.&lt;/p>
&lt;p>In short: let data and context, not just curiosity, guide the decision.&lt;/p>
&lt;h2 id="5-cost-labor-and-lost-weekends">5. Cost: Labor and lost weekends&lt;/h2>
&lt;p>Automation is often sold as a quick win, an easy way to improve productivity without adding headcount. But the true cost of automation isn’t just the time it takes to build a flow. It’s also the maintenance, the troubleshooting, and the late-night Teams messages when it fails. Now add labor: time spent planning, building, testing, fixing edge cases, explaining it to stakeholders, and documenting the flow. Then sprinkle in the debugging hours (because we all know a 42-step flow with nested conditions never works perfectly on the first try). And then there’s the human bottleneck factor. If you&amp;rsquo;re the only one who understands how the automation works and you&amp;rsquo;re out of office, congratulations, you’ve just made yourself a critical point of failure. There’s a difference between being helpful and being irreplaceable in a way that stresses out your team.&lt;/p>
&lt;p>Before jumping in, ask yourself: &lt;em>do we have the right tooling in place? Is this within our supported platform stack? Who else knows how this works, and are they empowered to help if something breaks?&lt;/em> If you can’t answer those clearly, what you’re building isn’t just automation, it’s a support ticket waiting to happen.&lt;/p>
&lt;h2 id="6-are-you-automating-a-moving-target">6. Are you automating a moving target?&lt;/h2>
&lt;p>Some processes are beautifully stable: They’ve run the same way for years and aren’t likely to change next week. Others are in constant flux: driven by shifting regulations, changing business models, or the latest reorg. If you&amp;rsquo;re working with a process that evolves every quarter, take a step back. Automation only pays off when what you’re automating stays more or less the same. Otherwise, you’ll spend more time updating the automation than the task ever cost in the first place. This is especially common in fast-growing companies or departments undergoing digital transformation.&lt;/p>
&lt;p>Think of it this way: Would you write a user manual for something that changes every few weeks? Probably not. So why commit it to code? That said, if you absolutely need to automate, consider designing for flexibility. Build modular flows with clear entry and exit points. Document them. Include versioning. And make peace with the fact that you might need to revisit and revise regularly, because the process isn’t done evolving just because your flow is live.&lt;/p>
&lt;h2 id="7-ownership-who-owns-this-thing">7. Ownership: Who owns this thing?&lt;/h2>
&lt;p>Ownership might sound like a formality, but it&amp;rsquo;s the backbone of every sustainable automation. When your flow fails at 3:27 am on the last day of the quarter, someone will be asked: &lt;em>Who owns this?&lt;/em>. If the answer is &lt;em>I think I built it during a hackathon&lt;/em>, that’s not just risky, it’s irresponsible. Every business process, no matter how small, should have a named owner. Someone who knows what the flow is supposed to do, who approves changes, and who can say &lt;em>yes&lt;/em> or &lt;em>no&lt;/em> when someone asks to tweak it. Ownership isn&amp;rsquo;t about bureaucracy, but only about clarity. It’s the difference between having a fire drill and a coordinated fire response.&lt;/p>
&lt;p>Without clear ownership, automations slowly decay. A once-useful flow becomes a mystery nobody wants to touch, living somewhere in a forgotten environment, still trying to send weekly reports to a mailbox that no longer exists. And when it fails, the silence lasts until someone’s task goes missing, and fingers start pointing. So before you build, ask: &lt;em>who will own this a month from now? Six months? Will they know it exists? And if they leave, is anyone else ready to take over?&lt;/em>&lt;/p>
&lt;p>If the answer is &lt;em>me, I guess&lt;/em>, and you&amp;rsquo;re not the person accountable for the business process, you might be building more technical debt than value. And no, dear IT-departments: It is not sufficient to just co-own every Power Automate flow owned by a user - you need to make sure that you know what it is doing.&lt;/p>
&lt;h2 id="8-compliance-risk-and-audits">8. Compliance, risk, and audits&lt;/h2>
&lt;p>Even if your automation runs beautifully and always triggers on time, processes the right data, and emails the right people, that doesn’t mean it’s safe. In fact, the more reliable it seems, the more dangerous it can be if it’s quietly doing the wrong thing with the wrong data. If your flow handles personal data, financial transactions, or anything that might touch compliance or regulatory frameworks, you’re no longer in casual territory. You’re in the land of data protection, security policies, and audit readiness. That means your automation must include:&lt;/p>
&lt;ul>
&lt;li>Proper access control&lt;/li>
&lt;li>Audit trails&lt;/li>
&lt;li>Robust error handling&lt;/li>
&lt;li>Clear fallback paths&lt;/li>
&lt;/ul>
&lt;p>These aren’t just &lt;em>nice to haves&lt;/em>, they’re non-negotiables. One missed field, one hardcoded email address, or one forgotten permission level can turn a minor issue into a full-blown incident. A &lt;em>small bug&lt;/em> becomes a &lt;em>data breach&lt;/em> in the time it takes to hit &lt;strong>Send&lt;/strong>.&lt;/p>
&lt;p>And yes, automation absolutely counts as data processing under regulations like GDPR and its international cousins. If you wouldn&amp;rsquo;t be allowed to do something manually without compliance approval, you definitely shouldn&amp;rsquo;t automate it without the same scrutiny. Before deploying, ask: &lt;em>Would this pass an audit? Would I be comfortable explaining it to a privacy officer, or a journalist?&lt;/em> If the answer is &lt;em>no&lt;/em>, pause. It’s easier to build compliance in from the start than to retroactively secure a system that’s already caused harm.&lt;/p>
&lt;h2 id="9-human-touch-vs-human-error">9. Human touch vs. human error&lt;/h2>
&lt;p>Not all manual work is a waste of time. Some tasks genuinely benefit from human involvement, especially those that require context, empathy, or nuanced judgment. Think of reviewing a customer escalation, handling a sensitive HR case, or making an exception in a policy-driven workflow, humans still outperform even the best logic trees. Automating these interactions can unintentionally strip away the very qualities that build trust and engagement. A rejection email sent by a bot, no matter how well phrased, rarely lands the same way as one written by a person who understands the situation. Also AI won&amp;rsquo;t fix that. #justsayin&lt;/p>
&lt;p>That said, automation shines in areas where the rules are clear and the data is structured. Examples are routine approvals with fixed thresholds, data validation between systems, or moving files and values between platforms. These tasks are low-value for humans but high-frequency and error-prone, making them ideal for automation. And sometimes the best path isn’t full automation but augmentation. Let the system do the prep work: gather data, check conditions, suggest actions. Then let a person decide. That hybrid model keeps humans in the loop without overwhelming them with routine work. It’s efficient and thoughtful. So when you evaluate a process, ask not just &lt;em>can I automate this?&lt;/em> but also &lt;em>what part of this benefits from a human touch—and which parts don’t need one at all?&lt;/em> And I know, the lines are blurring with LLMs and we see lots of agents who will handle interaction &lt;em>in our fast paced digital world&lt;/em> 🤡 - but trust me&amp;hellip; some things should just not be executed by AI.&lt;/p>
&lt;h2 id="10-tool-chaos-and-platform-fit">10. Tool chaos and platform fit&lt;/h2>
&lt;p>Technology should make your life easier, but too often, automation becomes another layer on top of a confusing stack. If your organization already uses ten different tools for task tracking, approvals, and reporting, and everyone is using their own combination of them, your automation might not solve the problem. It might just add one more patch to a system that’s already showing stress fractures. Inconsistent tooling isn’t just an IT headache; it creates user confusion, duplicate work, and brittle processes. An automation that integrates two tools nobody else uses isn’t strategic, it’s just more siloed glue. Instead, look for opportunities to consolidate. Build on platforms your organization already supports and standardizes. If your IT team has blessed a core toolset, use it. Not because it’s perfect, but because it’s supported. That means security patches, maintenance windows, backup plans, and someone to call when things break.&lt;/p>
&lt;p>When you align with platform strategy, your automation has a chance to scale. When you don’t, even a brilliant flow might die in obscurity the moment you change teams or someone sunsets one of the systems involved. So ask yourself &lt;em>is this the right place to automate it?&lt;/em> Because sometimes, what looks like a workflow issue is really a tooling problem in disguise.&lt;/p>
&lt;h2 id="11-transparency-and-traceability">11. Transparency and traceability&lt;/h2>
&lt;p>People like to know what&amp;rsquo;s going on, especially when their work depends on it. Manual processes, for all their flaws, often have one major advantage: they’re visible. You see the handoff, you know who’s working on what, and there’s usually a paper trail, however informal. Automation can quietly remove all of that. A task that used to involve an email or a verbal confirmation now disappears into a black box. If it succeeds, great. But if something goes wrong and no one knows why or where it stopped, frustration builds fast. To avoid that, your automation should include visibility by design. Build in notifications that let people know when something’s been submitted or completed. Provide logs or dashboards that track status. Even a simple confirmation email or SharePoint update log can make the difference between trust and suspicion. The first time someone asks &lt;em>what happened to my task?&lt;/em> and nobody knows, you’ll wish you had built in just a few more breadcrumbs.&lt;/p>
&lt;p>Remember, automation should reduce uncertainty, not introduce it.&lt;/p>
&lt;h2 id="12-just-because-you-can-doesnt-mean-you-should">12. Just because you can doesn’t mean you should&lt;/h2>
&lt;p>This is the line that often gets skipped in enthusiastic automation conversations. Tools like Power Automate make it incredibly easy to stitch together workflows, call APIs, and spin up integrations in a matter of minutes. It feels powerful, and it is. But just because you can automate something doesn’t mean it’s wise to do it alone. As soon as your automation affects more than your own work, especially if it reaches into cross-functional workflows, customer-facing systems, or business-critical operations, it stops being a personal time-saver and starts becoming part of your organization’s digital infrastructure.&lt;/p>
&lt;p>That comes with responsibility. You need testing. Documentation. Version control. Monitoring. You need to think like an engineer, even if you’re not one. And if you’re operating in a professional environment, you also need to engage with governance and architecture. Not to slow you down, but to protect the people who depend on the outcome. Rogue automation can create fragile systems, introduce silent errors, or violate compliance without anyone noticing, until it’s too late. Involving a subject matter expert doesn’t just make your flow better; it ensures it’s built for scale, clarity, and supportability.&lt;/p>
&lt;p>The best automation is collaborative. It lives on a supported platform, it follows design principles, and it doesn’t break when you go on vacation. You might still enjoy building it, but you’ll also sleep better knowing you’re not the only one holding it together.&lt;/p>
&lt;h2 id="conclusion-should-you-automate-it">Conclusion: should you automate it?&lt;/h2>
&lt;p>The honest answer? Please don&amp;rsquo;t kill me:&lt;/p>
&lt;blockquote>
&lt;p>It depends.&lt;/p>
&lt;/blockquote>
&lt;p>But it genuinely does. If the process you’re looking at is stable, well-understood, and worth the investment, if the data is clean, the impact is clear, and the scope is manageable, then yes, you should probably automate it. You’ll free up time, reduce errors, and maybe even improve morale. But automation isn’t always the answer. If you&amp;rsquo;re dealing with an unstable process, murky ownership, questionable data, or unclear value, then automating might just amplify existing issues. And once an automation is live, people assume it&amp;rsquo;s reliable, even when it&amp;rsquo;s quietly making the same mistake 1,000 times a day. Done well, automation scales good decisions. Done poorly, it scales dysfunction. That’s why every &lt;em>quick win&lt;/em> deserves at least a few deeper questions before you dive in.&lt;/p>
&lt;p>So take your time. Involve the right people. Document what matters. Build with care and intention. Because the goal isn’t just to automate, it’s to make work better, safer, more transparent, and yes, sometimes faster too.&lt;/p></description></item><item><title>From duct tape to data discipline</title><link>https://m365princess.com/blogs/data/</link><pubDate>Sat, 17 May 2025 12:11:24 +0000</pubDate><guid>https://m365princess.com/blogs/data/</guid><description>&lt;blockquote>
&lt;p>Where does the path toward a proper data strategy begin? With technology, processes, or mindset?&lt;/p>
&lt;/blockquote>
&lt;p>It’s a good question, which I received in a &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7329435398080708608?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7329435398080708608%2C7329437120773697537%29&amp;replyUrn=urn%3Ali%3Acomment%3A%28activity%3A7329435398080708608%2C7329448996035297281%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287329437120773697537%2Curn%3Ali%3Aactivity%3A7329435398080708608%29&amp;dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287329448996035297281%2Curn%3Ali%3Aactivity%3A7329435398080708608%29">discussion on LinkedIn&lt;/a> and it gets right to the heart of why so many organizations feel stuck.&lt;/p>
&lt;h2 id="excel-got-us-this-far-but-it-wont-get-us-further">Excel got us this far. but it won’t get us further&lt;/h2>
&lt;p>Excel filled the gaps. When systems were too rigid, when processes weren’t documented, when no one had the time or budget to do it properly: Excel stepped in. It’s fast, flexible, and widely understood. That’s precisely why it became the default platform for everything from planning and forecasting to reporting and reconciliation. It is the biggest LowCode Platform of all times. (😭)&lt;/p>
&lt;p>But over time, flexibility becomes fragility. Business logic ends up buried in formulas. Data definitions evolve in isolation. Files multiply across mailboxes and shared drives, and nobody knows which version is the truth. What started as a quick fix becomes critical infrastructure—with no oversight, no ownership, and no backup plan.&lt;/p>
&lt;p>We didn’t set out to build essential business processes on spreadsheets; it just happened. Now we’re trying to modernize, automate, and even build AI on top of that foundation. And it’s not holding up.&lt;/p>
&lt;h2 id="you-cant-automate-what-you-cant-trust">You can’t automate what you can’t trust&lt;/h2>
&lt;p>Every organization says they want to be data-driven. But that depends on one thing: trust. You can’t build automation, run analytics, or deploy AI unless your data is accurate, complete, consistent, and contextualized. More importantly, it needs to be owned. When a report is wrong, when a model fails, when an automation breaks: someone has to know why. And who that someone is, also needs to be known.&lt;/p>
&lt;p>Spreadsheets rarely offer that kind of clarity. They’re opaque by design. There’s no version control, no lineage, and no governance. You can’t trace a number back to its source; you just have to trust whoever sent the file. That might work in a crisis. It doesn’t scale into strategy.&lt;/p>
&lt;p>This isn’t just a tooling issue. It’s a question of confidence. And if you don’t trust the data, you won’t automate it. If you can’t explain the logic, you can’t expect AI to understand it.&lt;/p>
&lt;p>&lt;img alt="work chronicles cartoon" src="https://m365princess.com/images/wc-data-driven.png">&lt;/p>
&lt;h2 id="so-where-does-the-shift-begin">So where does the shift begin?&lt;/h2>
&lt;p>There’s no single entry point. But you need a few conditions in place before anything meaningful happens.&lt;/p>
&lt;p>You need some executive support: not a strategic program with three layers of steering committees, but enough cover to experiment without getting shut down the moment it gets uncomfortable. Someone up top needs to understand that doing nothing is the greater risk. That air cover gives teams the freedom to test, question, and improve.&lt;/p>
&lt;p>But the shift doesn’t work if it’s only top-down. Real transformation starts when teams on the ground begin questioning the way things have always been done. Why is this file passed around by email? Why does it take a week to align numbers across teams? Why do three departments define &lt;em>active customer&lt;/em> differently? These aren’t annoying details. They’re the fault lines that undermine every initiative built on top.&lt;/p>
&lt;p>&lt;img alt="active customers meme" src="https://m365princess.com/images/meme-active-customers.jpg">&lt;/p>
&lt;p>So, no, it’s not just mindset. And it’s not just process or tech. It’s a loop. You need room to act, courage to ask questions, and a willingness to rebuild some of the invisible scaffolding holding the business together.&lt;/p>
&lt;h2 id="from-duct-tape-to-data-discipline">From duct tape to data discipline&lt;/h2>
&lt;blockquote>
&lt;p>One of the most important mindset shifts is treating data not as a byproduct, but as a product.&lt;/p>
&lt;/blockquote>
&lt;p>Many organizations still run on &lt;em>data as a service&lt;/em>: someone requests data, someone else extracts it, massages it, and sends it over, usually in a spreadsheet. It’s reactive and transactional.&lt;/p>
&lt;p>&lt;em>Data as a product&lt;/em> flips that. It means designing data with intent. It means treating datasets like reusable, reliable components with clear definitions, ownership, and documentation. Just like any product, they’re built to serve multiple users, not just one-off requests. They’re versioned, governed, and improved over time. That mindset unlocks scale.&lt;/p>
&lt;h2 id="start-with-a-lighthouse-project">Start with a lighthouse project&lt;/h2>
&lt;p>This isn’t a call for a grand data transformation program. Most of those fail under their own weight. What actually works is a lighthouse project. A focused, contained initiative that demonstrates what &lt;em>good&lt;/em> can look like.&lt;/p>
&lt;p>Pick a domain where data trust is low but business impact is high: customer records, service requests, onboarding metrics. Something visible. Then work through it end to end: Who owns it? How is it created? What decisions depend on it? Clean it up. Define it. Publish it. Govern it. Then show your work.&lt;/p>
&lt;p>A good lighthouse doesn’t just solve a problem; it sets a precedent. It becomes the reference point for others. That’s how you build momentum without boiling the ocean.&lt;/p>
&lt;h2 id="the-real-cost-of-inaction">The real cost of inaction&lt;/h2>
&lt;p>If you’re unsure where your organization stands, the warning signs are easy to spot. If Excel files are still passed around as the source of truth, if basic KPIs vary between teams, if no one knows who maintains the customer master list, or if AI pilots keep getting delayed because &lt;em>the data isn’t ready&lt;/em>, you’re not dealing with a tech problem. You’re dealing with a credibility problem. And that won’t fix itself.&lt;/p>
&lt;h2 id="build-trust-before-you-build-ai">Build trust before you build AI&lt;/h2>
&lt;p>You don’t need to fix everything at once. But you do need to stop pretending that spreadsheets are good enough.&lt;/p>
&lt;p>&lt;img alt="flawed data xkcd" src="https://m365princess.com/images/flawed-data.png">&lt;/p>
&lt;p>Start by identifying one fragile process, one risky spreadsheet, one misunderstood metric. Use it to show what’s broken. Then fix it. Not just with a better tool, but with clarity, ownership, and repeatability. Build your first real data product. And let it light the way 🔦.&lt;/p>
&lt;p>If you want your AI to deliver value, your automation to scale, and your teams to make decisions based on something other than gut feeling, you need trust in your data. That trust isn’t built with dashboards. It’s built with discipline.&lt;/p>
&lt;p>The tools will come. The governance will follow. But it starts with someone asking: Why are we still doing it this way? And it continues when someone else answers: We don’t have to.&lt;/p></description></item><item><title>Still running on spreadsheets: the hidden cost of outdated infrastructure</title><link>https://m365princess.com/blogs/spreadsheet/</link><pubDate>Fri, 16 May 2025 10:39:19 +0000</pubDate><guid>https://m365princess.com/blogs/spreadsheet/</guid><description>&lt;p>I often hear some version of this: &lt;em>We should probably modernize it… but to be honest, the Excel spreadsheet works just fine&lt;/em>. It’s usually said with a shrug, as if running a key business process on a two-decade-old file is just how things are done.&lt;/p>
&lt;p>We joke about it. We meme about it. But the truth is this: some of your most essential operations are still running on duct-taped Excel files built years ago by someone no longer with the company. That’s not just inefficient; it’s reckless.&lt;/p>
&lt;p>You know the one.&lt;/p>
&lt;p>The spreadsheet that handles pricing logic for every customer quote. Or the one that reconciles financials before reporting. Or the one with color-coded columns that only Kyle understands — and he&amp;rsquo;s on holiday.&lt;/p>
&lt;p>Every organization has one. Sometimes dozens. And most are mission critical.&lt;/p>
&lt;p>&lt;img alt="mission critical spreadsheet - original from xkcd.com" src="https://m365princess.com/images/mission-critical.png">&lt;/p>
&lt;p>In fact, a &lt;a href="https://www.researchgate.net/publication/237108595_Lessons_from_Mission-Critical_Spreadsheets">study&lt;/a> found that some firms maintain millions of spreadsheets, with a substantial number in daily operational use. These aren’t minor convenience tools; they’re accidental legacy systems at the core of business operations.&lt;/p>
&lt;h2 id="how-we-got-here">How we got here&lt;/h2>
&lt;p>Spreadsheets are brilliant. They&amp;rsquo;re flexible, fast, and accessible to everyone. They let business teams fix gaps, test ideas, and move quickly without waiting three months for IT to approve a ticket. That’s why they took root, and why they’ve become indispensable.&lt;/p>
&lt;p>But what starts as a quick fix in someone’s Downloads folder slowly mutates into something far more dangerous: unofficial infrastructure.&lt;/p>
&lt;p>This isn’t just a theory. The research backs it up. In their study &lt;a href="https://www.researchgate.net/publication/237108595_Lessons_from_Mission-Critical_Spreadsheets">Lessons from Mission-Critical Spreadsheets&lt;/a>, Grossman and colleagues highlight how spreadsheets often evolve into production-grade tools without any formal development process. No documentation. No testing. No governance. Just layers of complexity added over time; until no one really understands how it works, but everyone depends on it.&lt;/p>
&lt;p>Soon, you&amp;rsquo;ve got a dozen interconnected workbooks, each with their own fragile formulas, circular references, and hidden sheets. There’s no documentation; just a colleague named Martina who knows which cells not to touch. Half the logic lives in a macro written in 2016. The other half? Copied from somewhere and tweaked until it worked.&lt;/p>
&lt;p>And this isn’t for tracking office snacks. It’s forecasting revenue, calculating commissions, or feeding data into a board report. A single corrupted cell, and the whole house of cards collapses. Because now you&amp;rsquo;re not just relying on a spreadsheet; you&amp;rsquo;re relying on a ghost system with no governance, no backup plan, and no accountability.&lt;/p>
&lt;h2 id="the-hidden-debt-of-spreadsheet-dependency">The hidden debt of spreadsheet dependency&lt;/h2>
&lt;p>Every time we lean on a spreadsheet to do the job of an actual system, we quietly rack up hidden costs. Here’s what that debt looks like:&lt;/p>
&lt;h3 id="user-experience-debt">User experience debt&lt;/h3>
&lt;p>Most business-critical spreadsheets are unusable unless you’ve been initiated into their secrets.&lt;/p>
&lt;ul>
&lt;li>There’s no UI, just cells and chaos&lt;/li>
&lt;li>Errors propagate like wildfire&lt;/li>
&lt;li>Training new staff means walking them through 18 undocumented steps and praying they don’t press the wrong filter&lt;/li>
&lt;/ul>
&lt;p>You wouldn’t accept this in a customer-facing app. So why do we accept it in our internal backbone?&lt;/p>
&lt;h3 id="security-risks">security risks&lt;/h3>
&lt;p>Spreadsheets get emailed. Uploaded. Downloaded. Left on USB sticks. Copied into random Teams chats. Synced across four cloud accounts. Dragged into personal folders &lt;em>just for backup&lt;/em>. Then they sit there, untracked, unsecured, and forgotten.&lt;/p>
&lt;p>(And please, don’t tell me your users would never do that. We both know they already have.)&lt;/p>
&lt;p>The result? You’re not just dealing with bad habits; you’re dealing with a security disaster hiding in plain sight:&lt;/p>
&lt;ul>
&lt;li>No access control: Anyone with the link, or just the file, can see everything. Customer pricing, payroll data, contract terms. It’s a goldmine for the wrong person.&lt;/li>
&lt;li>No audit trail: Who changed that number? When? Why? No one knows. And no, last modified in Windows Explorer or your SharePoint library doesn&amp;rsquo;t count as governance.&lt;/li>
&lt;li>No encryption — Most Excel files are wide open. Even password-protected ones can be cracked in minutes. And if they’re stored in an unprotected location, you’re already out of compliance.&lt;/li>
&lt;/ul>
&lt;p>Add to that the rogue versions scattered across devices, the shadow copies in email chains, and the manual updates happening off the radar, and you’ve got an environment where a single spreadsheet can undo years of trust, reputation, and regulatory effort. This isn&amp;rsquo;t a warning. It&amp;rsquo;s reality. One that too many organizations only recognize after the breach. And even then, they rinse and repeat.&lt;/p>
&lt;h3 id="lack-of-governance">Lack of governance&lt;/h3>
&lt;p>Who owns the spreadsheet? Who updates it? What happens if that person goes on leave, changes departments, or just stops responding to emails?&lt;/p>
&lt;p>Even in organizations using SharePoint or OneDrive (yes, with version history technically enabled) that safety net unravels quickly. Why? Because people still save copies to their desktop. They still email attachments back and forth. They still rename files with cryptic labels like &lt;code>Q2_calc_FINAL_v2_UseThisOne.xlsx&lt;/code>. They sync files across devices, edit them offline, and accidentally break the logic across three versions without realizing it.&lt;/p>
&lt;p>So even if versioning is available, it doesn’t help when the right file isn’t in the right place, or when no one agrees which version is the source of truth. That’s not a tooling gap. That’s a governance void.&lt;/p>
&lt;p>When something breaks (and it will), five people open five different versions, trying to reverse-engineer where it all went wrong. Everyone’s responsible. Which usually means no one is.&lt;/p>
&lt;p>This kind of chaos would be unthinkable in your ERP or CRM, but somehow, it gets a pass when it lives in Excel. Until Kyle leaves… and takes half the operational logic with him.&lt;/p>
&lt;h3 id="innovation-gridlock">Innovation gridlock&lt;/h3>
&lt;p>Sure, you can automate a process that relies on spreadsheets stored in shared drives. You can feed AI models with the output of nested &lt;code>IFERROR(VLOOKUP())&lt;/code> logic. But just because you can, doesn’t mean you should.&lt;/p>
&lt;p>Because the real question isn’t what’s technically possible. It’s whether you can trust it, scale it, govern it, and explain it.&lt;/p>
&lt;p>When your so-called &lt;em>data pipeline&lt;/em> is a loosely managed web of spreadsheets — some on SharePoint, some on local machines, some emailed back and forth — you’re introducing friction at every step:&lt;/p>
&lt;ul>
&lt;li>No single source of truth&lt;/li>
&lt;li>No guaranteed structure&lt;/li>
&lt;li>No lineage or auditability&lt;/li>
&lt;/ul>
&lt;p>And no confidence that what you’re automating is even correct&lt;/p>
&lt;p>Data excellence is more than having numbers in rows and columns. It’s about consistency, validation, structure, and accountability. Without that, you’re not automating a process; you’re just automating chaos.&lt;/p>
&lt;p>This is why so many digital transformation efforts stall. The tools are capable. The ambition is there. But the data is scattered across files, teams, and assumptions — full of outdated formulas and silent errors.&lt;/p>
&lt;p>Spreadsheets are not the enemy. But building on them as if they were real systems? That’s just building on sand.&lt;/p>
&lt;h3 id="operational-fragility">Operational fragility&lt;/h3>
&lt;p>Payroll doesn’t run because the macro didn’t. Compliance reporting gets delayed because the file won’t open. Revenue forecasts go sideways because someone broke a formula and didn’t notice. These aren’t edge cases. They’re everyday risks when your business relies on fragile spreadsheets.&lt;/p>
&lt;p>In 2021, a UK health authority &lt;a href="https://www.theguardian.com/politics/2020/oct/05/how-excel-may-have-caused-loss-of-16000-covid-tests-in-england?utm_source=chatgpt.com">lost track of 16,000 COVID-19 test results&lt;/a> due to Excel’s row limits. That’s not a tech problem. That’s using the wrong tool for the job — with real-world consequences.&lt;/p>
&lt;p>Let me say it again:&lt;/p>
&lt;blockquote>
&lt;p>Your spreadsheet is not a database.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;em>It works fine&lt;/em> — until it doesn’t!&lt;/p>
&lt;p>Executives often justify the status quo with variations of:&lt;/p>
&lt;ul>
&lt;li>&lt;em>We’ve always done it this way&lt;/em>&lt;/li>
&lt;li>&lt;em>It’s not worth the cost to replace&lt;/em>&lt;/li>
&lt;li>&lt;em>We’re too busy to change it now&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>But the real cost isn’t in the migration. It’s in every wasted hour, every manual fix, every security hole, every innovation that never ships because your data is stuck in Excel jail. You’re not saving money. You’re leaking value.&lt;/p>
&lt;h2 id="how-to-break-the-cycle">How to break the cycle&lt;/h2>
&lt;p>This is a call to use Excel for what it was built for: personal productivity and prototyping — not mission-critical operations.&lt;/p>
&lt;p>Here’s how to start the shift:&lt;/p>
&lt;ul>
&lt;li>Inventory your risks: Which spreadsheets support strategic decisions or operational workflows? You can’t fix what you don’t see.&lt;/li>
&lt;li>Modernize incrementally: Don’t start with a six-month platform project. Start by moving logic into structured databases, and rebuilding workflows in Power Platform.&lt;/li>
&lt;li>Establish ownership: Every spreadsheet with real business value needs a named owner, lifecycle policy, and documentation.&lt;/li>
&lt;li>Invest in skills, not just tools: Equip your teams to design resilient processes, not just workarounds. This isn’t about replacing Excel; it’s about building maturity. And yes, this means investment in time and money.&lt;/li>
&lt;li>Make the invisible visible: Track how much time is spent maintaining or correcting spreadsheet-based work. Show the hidden cost of doing nothing.&lt;/li>
&lt;/ul>
&lt;h2 id="the-bottom-line">The bottom line&lt;/h2>
&lt;p>Spreadsheets aren’t the problem. Treating them like enterprise systems is. If you&amp;rsquo;re serious about innovation, resilience, and responsible governance, then it’s time to ask the uncomfortable question: What are we still propping up on Tom’s spreadsheet?&lt;/p>
&lt;p>And what’s it going to cost us when that spreadsheet finally breaks?&lt;/p></description></item><item><title>Outsourcing your brain: cognitive offloading 'in the age of AI'</title><link>https://m365princess.com/blogs/cognitive-offloading/</link><pubDate>Thu, 15 May 2025 15:23:14 +0000</pubDate><guid>https://m365princess.com/blogs/cognitive-offloading/</guid><description>&lt;p>Here’s a fun experiment: when was the last time you memorized a phone number? Or did math in your head? Or remembered your colleague’s birthday without Outlook whispering it to you like a digital butler?&lt;/p>
&lt;p>Exactly.&lt;/p>
&lt;p>Welcome to the age of cognitive offloading, where our brains have quietly delegated entire functions to apps, calendars, and lately: AI. It’s efficient, it’s seductive, and depending on how you do it, it’s either genius… or digital lobotomy.&lt;/p>
&lt;h2 id="the-case-for-outsourcing-some-thinking">the case for outsourcing (some) thinking&lt;/h2>
&lt;blockquote>
&lt;p>Cognitive offloading isn’t a bug; it’s a feature.&lt;/p>
&lt;/blockquote>
&lt;p>Humans have always done it. Cave people probably scratched notes on walls because memory is unreliable and berries are seasonal. Fast-forward to 2025: we offload everything: navigation, scheduling, spelling, even ideation. And now, thanks to AI, we can outsource things that previously looked like our actual job: writing, analysis, even decision-making. And sometimes? That’s brilliant. And vendors like Microsoft tell us that it increases our productivity, as we do &lt;em>a thing&lt;/em> faster. Managers keep echoing that, and now we have tighter deadlines to do that thing with the help of AI. Don&amp;rsquo;t get me wrong: You shouldn&amp;rsquo;t be doing long division if Excel can do it faster. You shouldn’t be rewriting the same email 17 times if Copilot nails it in one shot (If I&amp;rsquo;m really honest with you: not even Copilot should write the very same email 17 times&amp;hellip; please &lt;a href="https://www.m365princess.com/blogs/fail-ai/">rethink your processes&lt;/a> before you automate them). And honestly, if an AI assistant reminds you that its your skip managers birthday today, that’s not cheating, it counts as survival.&lt;/p>
&lt;h2 id="but-when-does-delegation-become-abdication">But when does delegation become abdication?&lt;/h2>
&lt;p>Still there’s a difference between using a tool and becoming dependent on it. A &lt;a href="https://www.mdpi.com/2075-4698/15/1/6">recent study&lt;/a> puts it plainly: when we offload without awareness, we lose track of what we know and what the tool knows. And then we start making decisions based on&amp;hellip; vibes. Or worse, whatever the glorified autocomplete spits out.&lt;/p>
&lt;p>Ever seen someone present an AI-generated deck with absolute confidence and zero clue? That’s offloading gone rogue. Another &lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC8358584/">study on transactive memory&lt;/a> (read: shared brainpower in teams) shows that when people coordinate their offloading (when they know who or what knows what) performance improves. But when everyone assumes &lt;em>someone else has got it&lt;/em>, things fall apart fast. Sound familiar?&lt;/p>
&lt;p>It does, because that&amp;rsquo;s the real reason why meetings are full of people Googling or ChatGPTing the same thing while pretending to be experts.&lt;/p>
&lt;h2 id="ai-isnt-just-a-tool-its-a-mirror">AI isn’t just a tool; it’s a mirror&lt;/h2>
&lt;p>What makes AI different is that it doesn’t just store info, but it also creates. It drafts, analyzes, summarizes, even jokes (as badly as prompted). It’s like a hyper-efficient intern who never sleeps and always says &lt;em>Yes, of course&lt;/em>. That sounds great, but just like that overenthusiastic intern, AI tools have no idea when they are wrong unless you check. Please also don&amp;rsquo;t rely on them checking themselves when promoted. A lot of times, an LLM will confirm its wrong response with great confidence. So now we’re not just offloading memory, we’re offloading thinking. And if you’re not careful, you end up rubber-stamping whatever the AI produces and calling it productivity.&lt;/p>
&lt;p>⚡ Newsflash: if you can&amp;rsquo;t explain your own report, you didn&amp;rsquo;t write it; the machine did. You&amp;rsquo;re just cosplaying a knowledge worker.&lt;/p>
&lt;h2 id="the-creative-paradox-more-help-less-soul">The creative paradox: more help, less soul&lt;/h2>
&lt;p>For creative workers, the risk is different. AI can be a great collaborator. It’s a jazz partner, a writing buddy, a second brain that never runs out of metaphors. But when every idea starts sounding like the internet’s greatest hits, you have a problem. If you’ve ever read a LinkedIn post and thought, this sounds like someone asked ChatGPT to write like a thought leader after three coffees, you know what I mean. Originality needs friction. AI removes friction. Which is great, until your work becomes smooth, bland, and utterly forgettable. And that means, that you become arbitrary as you don&amp;rsquo;t show your human ingenuity anymore.&lt;/p>
&lt;h2 id="how-to-use-ai-without-becoming-a-puppet-on-a-string">How to use AI without becoming a puppet on a string&lt;/h2>
&lt;p>First things first: you don’t have to ditch AI completely. You just have to use it like a pro. A few rules for staying sharp:&lt;/p>
&lt;ul>
&lt;li>Know what you’re offloading: Don’t just paste in a prompt. Ask yourself: what’s the actual work I’m avoiding&lt;/li>
&lt;li>Interrogate the output: If it writes something for you, make sure you understand it. Better yet, rewrite it&lt;/li>
&lt;li>Bring your own brain: Use AI to get unstuck, not to opt out. Use it to test ideas, not generate them wholesale.&lt;/li>
&lt;li>Audit yourself: Can you explain the logic behind the conclusion? Can you defend it in a meeting without checking notes?&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>If your whole workflow is Ctrl+C from AI and Ctrl+V to your boss, you’re not a knowledge worker; you’re a middleman in a very short supply chain.&lt;/p>
&lt;/blockquote>
&lt;h2 id="whats-at-stake">What’s at stake&lt;/h2>
&lt;p>Business leaders love AI because it promises efficiency. But there’s a cost to shaving off all the hard parts of cognition. Sometimes, the struggle is the point. That’s where insight lives. If we optimize everything down to &lt;em>content velocity&lt;/em> and &lt;em>time to resolution&lt;/em>, we may end up with faster output—and shallower thinking.&lt;/p>
&lt;h2 id="closing-thought-its-your-brain-use-it-and-have-fun-while-doing-so">Closing thought: it’s your brain, use it (and have fun while doing so)&lt;/h2>
&lt;p>Look, I love AI. I use it daily. But it’s not my brain. It’s a sparring partner. A lab assistant. A caffeine substitute when I’m on deadline (because I quit drinking ☕). We need to stop pretending that delegating your thinking is the same as working smart. It’s not. It’s outsourcing your value.&lt;/p>
&lt;p>Use AI to think better; not less.&lt;/p></description></item><item><title>I'm never going to financially recover from this</title><link>https://m365princess.com/blogs/premium/</link><pubDate>Thu, 15 May 2025 06:49:31 +0000</pubDate><guid>https://m365princess.com/blogs/premium/</guid><description>&lt;h2 id="dont-get-knocked-out-by-licensing-costs">Don’t get knocked out by licensing costs&lt;/h2>
&lt;p>Power Platform licensing is often seen as the scary part. Mention a 5€ per user license, and someone inevitably reaches for a calculator: &lt;em>“Wait, with 3,000 users, that’s €15,000 a month — €180,000 a year… for a single app?!”&lt;/em>&lt;/p>
&lt;p>&lt;img alt="tiger king meme" src="https://m365princess.com/images/tiger-king.jpg">&lt;/p>
&lt;p>That reaction is common. And completely understandable, if you stop at the price tag. But if that’s the entire discussion, you&amp;rsquo;re missing the value conversation entirely.&lt;/p>
&lt;h3 id="why-the-question-is-wrong">Why the question is wrong&lt;/h3>
&lt;p>Too many Power Platform projects begin with the wrong question: &lt;em>“Can we build this without needing a premium license?”&lt;/em> The better question is: &lt;em>What are we trying to achieve?&lt;/em>&lt;/p>
&lt;p>Are we looking to reduce costs? Improve efficiency? Free people from repetitive tasks? Reduce legal exposure? Support growth? If so, the conversation needs to be about value, not just license line items.&lt;/p>
&lt;p>Because if we stop here, there will be a lot of canvas apps, that should have been model-driven apps in the first place, and this wrong architectural decision at the beginning of the project will have implications not only to security, compliance, performance, but also time to build the app. I saw it over and over again: Teams avoid premium licensing end up overusing canvas apps, over-engineering connections, and cutting governance corners. The result is higher development effort, lower performance, and avoidable complexity. Sometimes (often) it even leads to failure.&lt;/p>
&lt;p>Trying to save money by avoiding licenses will cost far more in the long run. It&amp;rsquo;s inevitable. It&amp;rsquo;s not a &lt;em>maybe we need to revisit this app&lt;/em> but &lt;em>this will by a massive cost grave if we don&amp;rsquo;t do it right straight from the beginning&lt;/em>.&lt;/p>
&lt;p>To make an informed decision, we should take a very close look at the options we have.&lt;/p>
&lt;blockquote>
&lt;p>Decide based on data, not on bias&lt;/p>
&lt;/blockquote>
&lt;h3 id="1-app-vs-app-1">1 app vs App #1&lt;/h3>
&lt;p>One app might look expensive. But that app is rarely operating in isolation.&lt;/p>
&lt;p>When you invest in the Power Platform as a strategic capability, not just a tactical tool to avoid some dev cost, you start to see compounding returns. Governance, security, Dataverse structure, and connectors become reusable assets. Every future app becomes faster to build and easier to manage. That’s where scale and efficiency live.&lt;/p>
&lt;h3 id="the-benefits">The benefits&lt;/h3>
&lt;p>Yes, there are direct, measurable results. Automating processes reduces operational costs. Workflows speed up. Time gets saved. Sales tools become sharper. Fewer mistakes, faster outcomes. But just as important, and often overlooked, are the long-term, strategic gains like security, risk mitigation, developer experience, employer branding.&lt;/p>
&lt;p>These don’t always show up in your quarterly KPIs, but they move the needle in a sustainable way. And while they may seem intangible at first glance, they &lt;strong>can be measured&lt;/strong>. You can quantify how much risk costs. You can measure reduction in incidents. You can track retention rates and hiring speed if your tooling is modern and attractive. These aren’t soft benefits. They’re just harder to fake.&lt;/p>
&lt;h3 id="the-real-cost-of-risk">The real cost of risk&lt;/h3>
&lt;p>Skipping proper licensing often means skipping governance, security roles, data segregation, and controlled environments. That introduces real risk. Not hypothetical. Real. To put a price tag on that, start with a basic framework:&lt;/p>
&lt;ul>
&lt;li>Identify the risk (e.g., data breach, compliance failure, fraud, downtime)&lt;/li>
&lt;li>Estimate the likelihood&lt;/li>
&lt;li>Estimate the financial impact&lt;/li>
&lt;li>Calculate the &lt;strong>Expected Monetary Value (EMV)&lt;/strong>:
EMV = Probability × Impact&lt;/li>
&lt;/ul>
&lt;p>Then compare it to the cost of mitigating that risk. If the premium license helps you prevent a €100,000 issue with just a few thousand euros of investment, the business case is clear.&lt;/p>
&lt;h3 id="i-made-an-app-for-that">I made an app for that&lt;/h3>
&lt;p>So, if my customers wonder if paying the premium licenses are really worth it, I can help them with this decision, because I built a tool to help estimate benefits, costs, and risks. Not because it’s fun (though it is), but because too many decisions are made on instinct, not insight. If you’re not calculating return, and you’re not quantifying risk, you’re guessing. Of course, I built this as a model-driven Power Apps app, which first calculates all the hours that we can save by automating repetetive tasks away, how much manually post-processing needs to be done because of human errors that naturally occurs, but also assigns a risks score etc. to the more intangible benefits to put a price tag on things like effectiveness of dta classification, flexibility to new compliance requirements or speed of implementing changes. Organizations can assign a certain weight to those categories to get an individual assessment of the risk they are taking when they wonder of going the &lt;em>free&lt;/em> route with creating Canvas Apps with a datasource on SharePoint would be a great idea.&lt;/p>
&lt;p>&lt;img alt="Value of a business solution app - made by Luise" src="https://m365princess.com/images/value-of-a-solution-app2.png">&lt;/p>
&lt;h2 id="in-the-long-run">In the long run&lt;/h2>
&lt;p>If you read this and wonder if you really, really, really need to buy the licenses&amp;hellip; then here is some perspective, that might change your mind. If you constantly try to avoid licensing cost, then obviously, you drive Power Platform with the handbrake on. The not as obvious effects are even worse:&lt;/p>
&lt;p>If you can&amp;rsquo;t spend costs, chances are, that there is no proper Power Platform enablement initiative in your organization. As you only work with included &lt;strong>seeded licenses&lt;/strong>, you won&amp;rsquo;t put any effort into an onramp program. That leads to inadequate user training and adoption programs, Limited governance and security controls, absence of community and expert development as well as delayed innovation and competitive disadvantage.&lt;/p>
&lt;p>Let me say this again: If you don&amp;rsquo;t invest in the platform, you have to deal with minimal-skilled users who are massively over-privileged in an immature environment. That is not saving costs, it is a recipe for desaster, as your projects will die and there will be data breaches and worse. But good news is: your competitors did invest. &lt;em>muahaha&lt;/em>&lt;/p>
&lt;h3 id="closing">Closing&lt;/h3>
&lt;blockquote>
&lt;p>If your app delivers less than $5 of value per user, it is just a toy, not a tool.&lt;/p>
&lt;/blockquote>
&lt;p>But most solutions I see deliver far more than that, especially when they reduce errors, cut manual effort, support governance, or provide real insight. The next time someone asks, &lt;em>Can we build this without premium?&lt;/em> — flip the script.&lt;/p>
&lt;p>&lt;img alt="horse drawing" src="https://m365princess.com/images/horse.png">&lt;/p>
&lt;ul>
&lt;li>Ask what it would cost to build it &lt;em>wrong&lt;/em>.&lt;/li>
&lt;li>Ask what risks you’re accepting.&lt;/li>
&lt;li>Ask what you’re really trying to achieve.&lt;/li>
&lt;/ul>
&lt;p>Then measure &lt;em>that&lt;/em>. And make your decision based on value — not fear. Hit me up if you need help with this!&lt;/p></description></item><item><title>Blueprint for a successful AI project</title><link>https://m365princess.com/blogs/blueprint/</link><pubDate>Wed, 14 May 2025 12:27:30 +0000</pubDate><guid>https://m365princess.com/blogs/blueprint/</guid><description>&lt;h2 id="from-chaos-to-clarity--how-to-build-ai-projects-that-actually-work">From chaos to clarity – how to build AI projects that actually work&lt;/h2>
&lt;p>&lt;img alt="Ben-afflec-balancing-meme" src="https://m365princess.com/images/ben-ai.png">&lt;/p>
&lt;p>If you’ve worked on an AI project recently, this probably isn’t a joke, it’s your backlog. For all the buzz around AI, many initiatives are still chaotic, short-lived, and poorly aligned with real business needs. The problem isn’t the technology but how we approach it.&lt;/p>
&lt;p>And the landscape isn’t getting simpler. Especially in the Microsoft ecosystem, where customers are now faced with a growing menu of AI tools: Microsoft 365 Copilot for productivity, custom GPTs for tailored use cases, Copilot Studio for low-code automation, declarative agents for complex workflows. Each is powerful. Each promises value. But very few customers get clear guidance on what to use when and how these puzzle pieces fit together. This isn’t about a lack of choice, but about a lack of clarity. And clarity is exactly what AI projects need most.&lt;/p>
&lt;h3 id="start-with-a-problem-not-a-prototype">Start with a problem, not a prototype&lt;/h3>
&lt;p>Too many AI initiatives begin with the tech. A new tool, a powerful model, or someone’s excitement about generative features. But without a clear, valuable problem to solve, even the most sophisticated solutions become expensive experiments. The right starting point is always the business challenge. What are we trying to change? What outcome defines success? And why is now the right time to tackle it with AI? If those questions aren’t answered upfront, the project isn’t ready, no matter how tempting the tooling looks.&lt;/p>
&lt;h3 id="treat-data-like-infrastructure-not-a-side-task">Treat data like infrastructure, not a side task&lt;/h3>
&lt;p>Every successful AI initiative is built on data that’s available, trusted, and understood. That doesn’t happen by accident. It requires structure: governance, ownership, and often, a fair bit of human labeling work that rarely fits into the budget or timeline.&lt;/p>
&lt;p>If the data isn’t usable, the model won’t be either. And if you’re not thinking about how that data will stay accurate over time, you&amp;rsquo;re not building a product, you’re making a throwaway demo.&lt;/p>
&lt;h3 id="design-for-durability">Design for durability&lt;/h3>
&lt;p>Getting a model to produce decent results once is relatively easy. Keeping it accurate, explainable, and performant in a live system is not. That’s why so many PoCs vanish quietly after the initial excitement fades. From day one, design for monitoring, retraining, and change management. Make the system maintainable, auditable, and resilient to the inevitable drift in both input data and business priorities. A working model isn’t the end goal; it’s the beginning of a longer responsibility.&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;Software being &amp;lsquo;finished&amp;rsquo; is like the lawn being mowed. It&amp;rsquo;s not a state, it&amp;rsquo;s a moment.&amp;rdquo; - Ted Neward&lt;/p>
&lt;/blockquote>
&lt;h3 id="bring-in-compliance-before-it-becomes-urgent">Bring in compliance before it becomes urgent&lt;/h3>
&lt;p>Many teams delay thinking about legal, ethical, or regulatory implications until something breaks, or worse, ships. That’s not just risky; it’s expensive. Treat privacy, bias mitigation, and model explainability as design constraints, not edge cases. The earlier compliance gets involved, the more likely your solution is to make it to production&amp;hellip;and stay there.&lt;/p>
&lt;h3 id="assemble-the-right-team">Assemble the right team&lt;/h3>
&lt;p>No single role can carry an AI project. Data scientists bring models to life, but without business context, domain expertise, and operational insight, even a technically brilliant model can misfire. The best AI outcomes come from cross-functional teams. Legal understands the risk. Ops knows the workflows. Designers think about trust and usability. Business leads connect the work to actual outcomes. AI isn’t a silo; it’s a team capability.&lt;/p>
&lt;h3 id="measure-outcomes-not-just-accuracy">Measure outcomes, not just accuracy&lt;/h3>
&lt;p>It’s easy to over-focus on model metrics like precision, recall, confidence scores. They’re important, but they don’t tell you whether the project actually improved anything. Instead, measure impact: reduced costs, faster decisions, fewer errors, better customer experiences. And watch adoption. If no one uses the AI output, it doesn’t matter how accurate it is. Success means making the business better in ways that last.&lt;/p>
&lt;h2 id="make-ai-boring">Make AI boring&lt;/h2>
&lt;p>Commodity is the goal. Not shiny demos or endless PoCs, but systems that just work; models that can be trusted, maintained, and improved; solutions that quietly solve real problems and keep moving the business forward. Because right now, most organizations don’t need more AI options. They need AI decisions, based on purpose, not panic.&lt;/p></description></item><item><title>A hill to die on: there are no tiny changes</title><link>https://m365princess.com/blogs/tiny/</link><pubDate>Tue, 13 May 2025 05:23:08 +0000</pubDate><guid>https://m365princess.com/blogs/tiny/</guid><description>&lt;h2 id="theres-no-such-thing-as-a-small-change">There’s no such thing as a small change&lt;/h2>
&lt;p>“&lt;em>Can we just move that button?&lt;/em>”&lt;/p>
&lt;p>“&lt;em>It’s just a quick fix.&lt;/em>&amp;quot;&lt;/p>
&lt;p>“&lt;em>Tiny change, shouldn’t take long.&lt;/em>&amp;quot;&lt;/p>
&lt;p>If I had a euro for every time I’ve heard that, I’d have long since retired from software, though probably not from cleaning up after so-called small changes. Let me say this loud and clear for the people in the back:&lt;/p>
&lt;blockquote>
&lt;p>There is no such thing as a small change in software.&lt;/p>
&lt;/blockquote>
&lt;p>Every tweak, no matter how innocent it looks, sets off a chain of technical, architectural, and organizational consequences. Some are immediate; others quietly sit in the shadows until they break something important.&lt;/p>
&lt;h3 id="the-myth-of-the-harmless-tweak">The myth of the harmless tweak&lt;/h3>
&lt;p>On the surface, it all seems trivial. Move a button. Rename a label. Add a checkbox. But under the hood, things are rarely that simple. Changing a layout might mean updating spacing logic, adjusting responsive behavior, and validating across multiple device types. A renamed label could trigger updates to automated tests, training documentation, screenshots, and localization files. Even a single UI element can ripple across the stack, touching everything from accessibility validation to release notes.&lt;/p>
&lt;p>The development time for the change itself might be ten minutes. But doing it properly means ensuring nothing else breaks, verifying it behaves correctly in edge cases, and navigating governance processes. That can easily consume an entire day. Multiply that by the number of “quick wins” in a backlog, and you start to see the cost of this thinking.&lt;/p>
&lt;h3 id="when-layout-changes-break-real-things">When layout changes break real things&lt;/h3>
&lt;p>Changing a layout can, and often does, break things you didn’t intend to touch. That internal report used once a quarter? Gone, because the JavaScript function that rendered it was tied to a layout element that no longer exists. And oh I know! Ideally, a report-generating function shouldn&amp;rsquo;t be tightly coupled to a specific layout element. In a well-architected system, UI layout and business logic are clearly separated, with appropriate abstraction layers and clean interfaces. Unfortunately, lots of projects are not in that ideal state - and they suffer from &lt;em>just ship it&lt;/em> mindset badly, where separation of concerns and encapsulation are treated as a nice to have, not a standard of how we develop software. But what about the screen reader that used to announce the &amp;ldquo;Submit&amp;rdquo; button? Now silent, because your oh-so-tiny change reordered the tab focus and wiped out the ARIA labels. The automated tests that ran fine last week? All failed last night, because your cleaned-up CSS classes broke the selectors used in the test scripts.&lt;/p>
&lt;p>These aren’t hypotheticals. They’re the kind of things that creep in precisely because the original feature was rarely used, or only by specific teams like finance, legal, or compliance. It doesn&amp;rsquo;t matter that no one noticed during testing. They’ll notice when they actually need it. And by then, you’re already halfway through the next “minor” change.&lt;/p>
&lt;h3 id="small-surface-big-impact">Small surface, big impact&lt;/h3>
&lt;p>One of the most dangerous misconceptions in software is the assumption that something that &lt;em>looks&lt;/em> small &lt;em>must be&lt;/em> easy. It’s not.&lt;/p>
&lt;blockquote>
&lt;p>Simplicity in appearance hides the complexity of interaction.&lt;/p>
&lt;/blockquote>
&lt;p>A design tweak that shifts the position of a component might sound like a harmless adjustment, but it could require revisiting layout logic, accessibility compliance, interaction flows, and translation strings, just to name a few. UI changes are rarely just UI. They’re system changes that happen to start with pixels.&lt;/p>
&lt;p>What makes it worse is that developers often know this, but hesitate to say it out loud. Saying “this will take a day” makes you look like a blocker. So you nod, write the ticket, and let the cost show up later in the form of missed deadlines, brittle tests, or unhappy users. Everyone keeps the illusion alive because it’s easier in the moment.&lt;/p>
&lt;blockquote>
&lt;p>Short-term relaxation means long-term tension, short term tension warrants long term relaxation.&lt;/p>
&lt;/blockquote>
&lt;h3 id="the-business-cost-of-tiny-lies">The business cost of tiny lies&lt;/h3>
&lt;p>These illusions are expensive. You won’t see the impact of “&lt;em>just small changes&lt;/em>” on a dashboard. It’s spread out: an hour lost here in testing, another in debugging, another rewriting documentation. But the cost is real. The more time teams spend chasing cosmetic requests, the less time they spend on real improvements. Productivity drops. Morale takes a hit. And quality quietly suffers. Meanwhile, the team that asked for the change? They’ve already moved on. The next request is in, and it starts with the same words: “Could we just&amp;hellip;”&lt;/p>
&lt;h3 id="but-what-about-typos">But what about typos?&lt;/h3>
&lt;p>This is where someone always chimes in: &lt;em>“Surely a typo fix is small?”&lt;/em> Sometimes, yes. If it’s a plain text correction in a UI, it’s usually straightforward, just don’t forget to update the translation strings. But once you move beyond that, even typos can carry weight.&lt;/p>
&lt;p>Fixing a typo in an error message, for example, can trigger a full localization workflow across ten languages. A label change might require updating automated tests, screenshots, and training documentation. A spelling mistake in a configuration file might not be a spelling mistake at all—it might be the one character that causes a parsing error and crashes the deployment.&lt;/p>
&lt;p>Even a harmless code comment goes through version control, review, and deployment. It competes for attention with real fixes. So yes, some typos are genuinely tiny. But others are the software equivalent of stepping on a rake. And you usually don’t know which it is until your face hits the handle.&lt;/p>
&lt;h3 id="treat-change-like-the-risk-it-is">Treat change like the risk it is&lt;/h3>
&lt;p>If you want to move fast and deliver value, that’s great. But the path to speed isn’t paved with unexamined changes. It’s paved with clear priorities, contextual trade-offs, and honest conversations. Stop pretending that changes are small just because they’re visual. Evaluate changes by their impact, not their appearance. Bundle related work so teams aren’t testing blind. And for the love of software 💖, build a culture where asking “Why?” is seen as diligence, not defiance.&lt;/p>
&lt;h3 id="final-thought">Final thought&lt;/h3>
&lt;p>Every change is a choice. Every “just one more thing” is a debt someone else pays. So the next time someone says, “&lt;em>Can we just&amp;hellip;&lt;/em>”, pause and ask:
&lt;strong>Do they understand what they’re asking for, or just what it looks like?&lt;/strong>&lt;/p></description></item><item><title>No, a crash course won’t make you an AI leader</title><link>https://m365princess.com/blogs/crashcourse/</link><pubDate>Mon, 12 May 2025 06:49:31 +0000</pubDate><guid>https://m365princess.com/blogs/crashcourse/</guid><description>&lt;p>Lately, I keep seeing weekend seminars popping up: Promising to turn you into an “&lt;em>AI leader&lt;/em>”, prepare you for the role of &lt;em>Chief AI Officer&lt;/em>, or help your company tick the compliance box for the EU AI Act. The messaging is clear: show up for two days, leave with a certificate after a multiple-choice test, and suddenly your organization has AI competency.&lt;/p>
&lt;p>Does that sound convenient? Yes!&lt;/p>
&lt;p>Is it dangerously misleading? Also Yes!&lt;/p>
&lt;p>Does it get sold out anyway? [no comment💀]&lt;/p>
&lt;p>Unfortunately, what’s being sold here isn’t transformation, it’s reassurance. And that won’t protect you from the real responsibilities that come with deploying AI at scale, especially in a regulatory environment that now requires you to prove that your teams are both competent and empowered to use AI responsibly. Now, you might think: “&lt;em>Sure, you can’t become an AI leader in a weekend, but what’s the harm in learning the basics?&lt;/em>” On the surface, that sounds reasonable. But the problem isn’t the learning, it’s the illusion of readiness it creates. When a certificate suggests you’re now “&lt;em>qualified&lt;/em>” to lead AI in your organization, it replaces the hard work of capability-building with a shortcut that feels productive but changes nothing. Worse, it can lead to false confidence, performative leadership, and decisions made without the depth to support them. Learning basics is good; mistaking them for strategic maturity is not. Self-organized learning is essential, especially for non-technical leaders. Understanding the basics of AI, its limitations, and its real-world implications should be part of every executive’s toolkit; not to become experts, but to ask better questions and make better decisions over time.&lt;/p>
&lt;h2 id="if-you-could-solve-transformation-in-a-weekend-you-wouldve-done-it-by-now">If you could solve transformation in a weekend, you would’ve done it by now&lt;/h2>
&lt;p>Change fatigue is real. So it’s tempting when someone offers a shortcut. A crisp two-day seminar. A certificate with “&lt;em>AI Officer&lt;/em>” on it. A promise that you&amp;rsquo;re now ready to lead the future. I&amp;rsquo;m sorry to break it to you, but no title, no badge, no training event will fix what’s broken if your organization lacks the ability to learn, unlearn, and relearn as part of daily life. You can’t inject AI competency from the outside. You have to build it in.&lt;/p>
&lt;p>Weekend seminars don’t change behavior. Culture does.&lt;/p>
&lt;h2 id="ai-capability-isnt-a-line-item">AI capability isn’t a line item&lt;/h2>
&lt;p>Most orgs aren’t suffering from a lack of information. They’re suffering from an inability to apply it consistently. A certificate doesn’t change that. A multiple-choice test at the end of a seminar won’t suddenly make your managers ask better questions, experiment responsibly, or challenge outdated assumptions.Worse: these kinds of “quick fix” approaches can create a false sense of progress. You’ve ticked the box, updated your Linkedin profile, announced the initiative, but nothing has actually changed. AI requires new patterns of work, new definitions of value, and a comfort with ambiguity that no workshop can magically install.&lt;/p>
&lt;h2 id="transformation-doesnt-come-with-a-certificate">Transformation doesn’t come with a certificate&lt;/h2>
&lt;p>The work of AI transformation is messy. It involves tearing down legacy processes, empowering teams to make decisions faster, and shifting how you &lt;a href="https://m365princess.com/blogs/unmeasured0">measure success&lt;/a>. It’s cross-functional, ongoing, and often more than just uncomfortable. Change hurts.&lt;/p>
&lt;p>So when you’re offered a seminar that wraps all this up into a neat package: beware. Real change doesn’t look like that.&lt;/p>
&lt;blockquote>
&lt;p>Real change isn’t convenient.&lt;/p>
&lt;/blockquote>
&lt;p>What your company needs isn’t a weekend boost of inspiration. It needs a culture where learning is continuous, literacy is widespread, and leadership owns the responsibility for evolving the system, not outsourcing it.&lt;/p>
&lt;h2 id="if-you-still-think-the-problem-is-skills-youre-not-looking-deep-enough">If you still think the problem is skills, you’re not looking deep enough&lt;/h2>
&lt;p>Yes, technical skills matter. But the real blockers in AI adoption aren’t tools or training gaps, they’re structural. They’re the slow approvals, the siloed data, the outdated KPIs, the fear of failure, the lack of space for experimentation. None of that gets solved by putting a handful of people through a seminar, no matter how slick the website or how shiny the PDF you get at the end. A single event won’t shift how decisions are made. A certificate won’t fix the absence of psychological safety. A role title won’t overcome a culture that punishes risk.&lt;/p>
&lt;p>You don’t need another training to feel good about progress. You need to build systems that allow progress to emerge, daily, from within. AI maturity is not a credential; it’s a condition of your organization’s culture. And you can’t buy that in a weekend.&lt;/p>
&lt;p>&lt;img alt="inner peace meme - Me after outsourcing AI responsibility to a role I invented last week" src="https://m365princess.com/images/inner-peace.png">&lt;/p>
&lt;h2 id="what-to-do-instead">What to do instead&lt;/h2>
&lt;p>If you’re serious about building AI capability in your organization, forget the fast-track badges. Focus on structure, not symbolism. Here’s what I do in projects that actually work out and move the needle:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Embed continuous learning into everyday work. AI isn’t a one-time rollout; it’s a moving target. Make learning loops part of your operating rhythm: cross-functional learning sessions, hands-on experimentation, peer exchange, and internal showcases. Not slides; not hype—practice. Make sure that people have time to do so. If they are buried in meetings that should have been an email, they won&amp;rsquo;t get far.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Distribute AI literacy, don’t centralize it. You don’t need a single “AI Officer”; you need hundreds of AI-literate employees who are confident to use the tools, question their outcomes, and escalate when something breaks. Make that normal. Make it safe.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Build trust into the process, not just compliance into the policy. The EU AI Act is about accountability. That starts with real governance: role clarity, transparency, auditability—and also with culture. If your people are afraid to touch AI without permission, you’ve failed before you began.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Tie AI efforts to actual business decisions. Stop treating AI like innovation theater. Use it to challenge cost structures, improve decisions, reduce friction, and rethink where human time is best spent. If it doesn’t change how value is created, it’s noise.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Invest in enablement, not vanity roles. The smartest AI initiatives are backed by practical enablement: internal communities, reusable templates, clear guardrails, and real leadership engagement. That takes time and commitment.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Transformation won’t happen in a seminar. But it will happen: quietly and sustainably, if you create the conditions for it.&lt;/p></description></item><item><title>AI as a finite vs infinite game in organizations</title><link>https://m365princess.com/blogs/finite/</link><pubDate>Wed, 07 May 2025 06:49:31 +0000</pubDate><guid>https://m365princess.com/blogs/finite/</guid><description>&lt;p>Most organizations are asking the wrong question about AI.&lt;/p>
&lt;p>They’re asking: &lt;em>How do we win with AI?&lt;/em>
What they should be asking is: &lt;em>How do we keep playing?&lt;/em>&lt;/p>
&lt;p>The difference is subtle, but transformative. It’s the difference between approaching AI as a finite game or an infinite game. Simon Sinek popularized this distinction (originally developed by James P. Carse), and it offers a fresh, much-needed lens on how to lead AI adoption. (So now you know what&amp;rsquo;s on my nightstand :-))&lt;/p>
&lt;h2 id="finite-games-vs-infinite-games">Finite games vs infinite games&lt;/h2>
&lt;p>A &lt;strong>finite game&lt;/strong> has fixed rules, known players, and a clear endpoint. Football is a finite game. So is a sales competition. In business, finite games are things like launching a product, completing a project, or winning market share in a specific quarter.&lt;/p>
&lt;p>An &lt;strong>infinite game&lt;/strong> has known and unknown players, changing rules, and no fixed endpoint. The goal isn’t to win, it’s to keep playing, to keep evolving. Relationships are infinite. So is learning. So is the long-term health of your organization.&lt;/p>
&lt;p>Now let’s apply this to AI.&lt;/p>
&lt;h2 id="the-finite-game-of-ai-short-term-wins-long-term-waste">The finite game of AI: short-term wins, long-term waste&lt;/h2>
&lt;p>When companies adopt AI with a finite mindset, they’re thinking like this:&lt;/p>
&lt;ul>
&lt;li>&lt;em>We need to show ROI in the next 6 months&lt;/em>&lt;/li>
&lt;li>&lt;em>Let’s automate something to cut costs fast&lt;/em>&lt;/li>
&lt;li>&lt;em>Let’s buy a tool and be seen as an AI-first company&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>This is the AI equivalent of trying to win a sprint when the race never ends. These companies launch a chatbot or predictive model, then stop improving it, they fail to build internal capabilities (so they stay dependent on consulting firms), they focus on tools, not data foundations and they treat AI as a tech implementation project, not a capability or culture shift.&lt;/p>
&lt;p>Let me give you an example: A retail chain installs AI-powered cameras to detect shoplifting. They deploy fast, announce success, and move on. Six months later, false positives spike; staff don’t trust it; customers complain. The team has moved on to the next tech project. The AI system is abandoned or quietly switched off.&lt;/p>
&lt;p>That’s a finite game result:&lt;/p>
&lt;blockquote>
&lt;p>Project delivered, headline grabbed, but no sustained value 💀&lt;/p>
&lt;/blockquote>
&lt;h2 id="the-infinite-game-of-ai-capability-over-completion">The infinite game of AI: capability over completion&lt;/h2>
&lt;p>Organizations playing the infinite game think differently:&lt;/p>
&lt;ul>
&lt;li>&lt;em>AI is a journey, not a deliverable&lt;/em>&lt;/li>
&lt;li>&lt;em>We need to build literacy, governance, and ethical capacity&lt;/em>&lt;/li>
&lt;li>&lt;em>Let’s focus on value creation over time, not one-off wins&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>This mindset leads to very different decisions. They start small but keep improving (e.g., one use case, continuously refined), they invest in data quality and cross-functional teams, they Align AI initiatives with long-term business goals, not quarterly targets and they create systems to learn from feedback; human and machine.&lt;/p>
&lt;p>Also here I&amp;rsquo;d like to give you an example: A logistics company starts with a small route optimization model. It doesn’t save much money at first. But over 18 months, they improve their data quality, retrain staff, and integrate feedback from drivers. AI becomes part of how they think about operations. They begin experimenting with predictive maintenance and inventory forecasting. There is no big &amp;ldquo;launch&amp;rdquo;, just steady evolution.&lt;/p>
&lt;blockquote>
&lt;p>That’s the infinite game at work: resilience, trust, and adaptability built over time.&lt;/p>
&lt;/blockquote>
&lt;h2 id="5-signs-your-ai-strategy-is-playing-a-finite-game">5 Signs your AI strategy is playing a finite game&lt;/h2>
&lt;ul>
&lt;li>No long-term plan for AI talent development&lt;/li>
&lt;li>Success measured only by ROI in months&lt;/li>
&lt;li>Projects that launch but don’t evolve&lt;/li>
&lt;li>AI decisions made in isolation from business strategy&lt;/li>
&lt;li>Ethical risks treated as an afterthought&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="boyfriendmeme" src="https://m365princess.com/images/bfmeme-ai.png">&lt;/p>
&lt;h2 id="5-signs-youre-playing-the-infinite-game">5 Signs you’re playing the infinite game&lt;/h2>
&lt;ul>
&lt;li>You’re building AI literacy across the organization&lt;/li>
&lt;li>You have a data governance strategy aligned to AI goals&lt;/li>
&lt;li>Your AI roadmap is flexible and revisited regularly&lt;/li>
&lt;li>You learn from failures and adjust course&lt;/li>
&lt;li>You&amp;rsquo;re measuring cultural and capability shifts—not just outcomes&lt;/li>
&lt;/ul>
&lt;h2 id="why-this-matters-now">Why this matters now&lt;/h2>
&lt;p>AI isn’t a tool you finish installing. It’s a capability you build and grow with. The rules will keep changing. New models will emerge. Regulations will evolve. Expectations from customers, employees, and the public will shift. If your mindset is fixed, your AI strategy will snap under pressure. But if your mindset is adaptive and designed to keep playing, you’ll build trust, innovation, and real value over time. This isn’t just about being more responsible, but about being more resilient.&lt;/p>
&lt;blockquote>
&lt;p>AI is not a game to win. It’s a game to stay in.&lt;/p>
&lt;/blockquote>
&lt;p>Are you building to keep playing?&lt;/p></description></item><item><title>How to let your AI project fail in spectacular fashion!</title><link>https://m365princess.com/blogs/fail-ai/</link><pubDate>Fri, 02 May 2025 07:39:11 +0000</pubDate><guid>https://m365princess.com/blogs/fail-ai/</guid><description>&lt;p>There’s never been more urgency to &lt;em>do something with AI&lt;/em>. Boardrooms are buzzing; slide decks are glowing; consultants are circling. And somewhere in between the executive mandate and the whiteboard workshop, someone says it:&lt;/p>
&lt;blockquote>
&lt;p>Let’s just start with a few use cases.&lt;/p>
&lt;/blockquote>
&lt;p>What follows is rarely malicious. It’s process. It’s governance. It’s a carefully choreographed dance of inertia, ambition, and misplaced optimism. And it almost always leads to the same result: nothing that truly matters changes. So if you&amp;rsquo;re aiming to fail your AI project with grace and budget intact, here&amp;rsquo;s a carefully curated guide.&lt;/p>
&lt;h2 id="1-begin-with-a-boundless-context-free-excel-file">1. Begin with a boundless, context-free Excel file&lt;/h2>
&lt;p>Forget Miro. Or talking with people. &lt;a href="https://www.m365princess.com/blogs/usecases/">Real AI projects start in Excel&lt;/a>, always have, always will. Someone heard at a conference that &lt;em>you need to start with a list of use cases&lt;/em>, so you do exactly that. You send out a template to every department asking them to fill in:&lt;/p>
&lt;ul>
&lt;li>the use case title&lt;/li>
&lt;li>a short description&lt;/li>
&lt;li>expected value (TBD)&lt;/li>
&lt;li>business owner (also TBD)&lt;/li>
&lt;li>priority (which is always “High”)&lt;/li>
&lt;/ul>
&lt;p>By the end of the week, you have 137 rows. No duplicates, just thirteen slightly different versions of &lt;em>summarize meeting notes&lt;/em> and seven interpretations of &lt;em>intelligent assistant for everything&lt;/em>. Congratulations. You’ve achieved alignment through tabulation.&lt;/p>
&lt;h2 id="2-democratize-the-discussion-until-it-becomes-unsolvable">2. Democratize the discussion until it becomes unsolvable&lt;/h2>
&lt;p>Next up is stakeholder alignment. But make it a buffet. Invite everyone: product, HR, legal, compliance, data, ops. Now spend six meetings carefully defining terms like &lt;em>value&lt;/em> and &lt;em>impact&lt;/em>. Each department will have its own interpretation. The result? Two opposing paths:&lt;/p>
&lt;ul>
&lt;li>A minimal, low-risk use case—safe, invisible, and guaranteed not to offend&lt;/li>
&lt;li>A grand vision: &lt;em>AI-powered transformation&lt;/em>, that spans every business unit and requires 18 months, 3 new vendors, and a dedicated tiger team. Choose whichever one ensures you won’t have to demonstrate impact before your next planning cycle.&lt;/li>
&lt;/ul>
&lt;h2 id="3-embrace-the-myths-that-quietly-sabotage-you">3. Embrace the myths that quietly sabotage you&lt;/h2>
&lt;p>AI is new territory, but bad assumptions are timeless. These are the organizational beliefs that sound reasonable, until they quietly derail the entire effort:&lt;/p>
&lt;ul>
&lt;li>&lt;em>You can’t give everyone Copilot licenses &amp;rsquo;like Halloween candy&amp;rsquo;&lt;/em> - Because clearly, we should wait until we know who’s productive before enabling productivity tools. Genius.&lt;/li>
&lt;li>&lt;em>We should only give licenses to power users&lt;/em> - As if AI won’t reshape how non-power users work, collaborate, or decide. The logic: let’s transform work by empowering exactly the people who don’t want to change it.&lt;/li>
&lt;li>&lt;em>Use cases must show immediate ROI&lt;/em> - A classic. We’ll only invest in experiments that have guaranteed returns, thus eliminating the concept of experimentation entirely&lt;/li>
&lt;li>&lt;em>Let&amp;rsquo;s focus on things that are easy to measure&lt;/em> - Like token counts, number of summaries generated, or chatbot response times. Not actual impact on decisions, outcomes, or time saved&lt;/li>
&lt;li>&lt;em>Let’s test this in a department where nothing mission-critical happens&lt;/em>. Perfect. If it fails, it doesn’t matter; and if it succeeds, it’s still irrelevant. A no-risk, no-reward strategy.&lt;/li>
&lt;li>&lt;em>We don’t need to involve IT until phase two&lt;/em>. IT loves when people roll out security-sensitive pilots behind their back. Trust me.&lt;/li>
&lt;li>&lt;em>AI can just learn from our content&lt;/em>. Yes, of course! AI will parse our 15-year-old SharePoint graveyard and instantly understand our unique company culture and acronyms.&lt;/li>
&lt;/ul>
&lt;h2 id="4-misunderstand-the-value-of-an-ai-driven-workplace-on-purpose">4. Misunderstand the value of an AI-driven workplace (on purpose)&lt;/h2>
&lt;p>For real. Most organizations aren’t failing at AI because they’re technologically incapable; they’re failing because they don’t know what success looks like. Or worse, they define success in such narrow, quantifiable terms that they miss the point entirely. When we talk about AI in the workplace, we usually rush to tangible benefits:&lt;/p>
&lt;ul>
&lt;li>reduce hours spent on repetitive tasks&lt;/li>
&lt;li>generate faster outputs (emails, proposals, analyses)&lt;/li>
&lt;li>summarize content so we don’t have to read it&lt;/li>
&lt;li>streamline approval flows or ticket triage&lt;/li>
&lt;li>automate report generation and document creation&lt;/li>
&lt;/ul>
&lt;p>These are fine. They’re easy to measure and easy to sell. But if that’s all you get from AI, you’ve just made a slightly faster version of your current system, without changing how work actually happens.&lt;/p>
&lt;p>The real potential lies in the intangible benefits:&lt;/p>
&lt;ul>
&lt;li>cognitive relief: not having to remember which folder holds the policy document from 2021&lt;/li>
&lt;li>confidence in starting: a blank page is intimidating; AI gives you something to react to&lt;/li>
&lt;li>inclusivity: helping people with different working styles, language proficiency, or neurodiverse needs&lt;/li>
&lt;li>better decisions: when analysis becomes trivial, people spend more time thinking&lt;/li>
&lt;li>momentum: removing friction so good ideas don’t stall at the first approval gate&lt;/li>
&lt;/ul>
&lt;p>But these only emerge when you &lt;a href="https://www.m365princess.com/blogs/unmeasured5/">redesign&lt;/a> the systems people work in. If you just drop AI into a dysfunctional process, you get slightly faster dysfunction. And most orgs are fine with that.&lt;/p>
&lt;p>&lt;img alt="AI fail boardroom meeting meme" src="https://m365princess.com/images/ai-fail-boardroom.png">&lt;/p>
&lt;h2 id="5-replace-strategy-with-prompt-engineering">5. Replace strategy with prompt engineering&lt;/h2>
&lt;p>Now comes the execution phase. Naturally, you’ll skip over:&lt;/p>
&lt;ul>
&lt;li>existing process inefficiencies&lt;/li>
&lt;li>broken data foundations&lt;/li>
&lt;li>user interviews&lt;/li>
&lt;li>change management&lt;/li>
&lt;/ul>
&lt;p>Why? Because you found someone who knows how to ✨&lt;em>talk to the model&lt;/em>✨. Your new prompt whisperer assures you that success isn’t about systems or behavior change; but about mastering phrasing. Invest heavily in personas, tone sliders, and clever ways to ask AI to &lt;em>act as a senior consultant with 20 years of experience in our industry&lt;/em>. After all, if the demo works, surely production will be fine.&lt;/p>
&lt;h2 id="6-automate-dysfunction-at-scale">6. Automate dysfunction at scale&lt;/h2>
&lt;p>Every failing AI project eventually arrives here: automation without transformation.&lt;/p>
&lt;ul>
&lt;li>don’t fix the process; just add a chatbot&lt;/li>
&lt;li>don’t question the workflow; just wrap it in a summarizer&lt;/li>
&lt;li>don’t challenge poor decision-making structures; just hope AI will clarify them&lt;/li>
&lt;/ul>
&lt;p>This is where AI becomes expensive window dressing. You might reduce some clicks, add a dashboard, or auto-generate a summary. But the system underneath—the approvals, the duplication, the unclear ownership—remains untouched. You’ve accelerated the wrong thing.&lt;/p>
&lt;h2 id="7-celebrate-the-illusion-of-progress">7. Celebrate the illusion of progress&lt;/h2>
&lt;p>At this point, you’ve technically delivered something. There’s a pilot. It has a name. Maybe even a logo. You announce it with fanfare:&lt;/p>
&lt;p>🚀&lt;em>We’re proud to launch our first AI-powered assistant!&lt;/em>🚀&lt;/p>
&lt;p>No one uses it. No one trusts it. But it exists. And more importantly, it ticks the box on your innovation roadmap. You didn’t change how decisions are made. You didn’t create new leverage. But you did produce a very impressive PowerPoint. Mission accomplished.&lt;/p>
&lt;h2 id="8-the-lesson-we-will-now-politely-ignore">8. The lesson we will now politely ignore&lt;/h2>
&lt;p>AI projects don’t fail because the models are weak. They fail because the organization avoids the hard stuff:&lt;/p>
&lt;ul>
&lt;li>clearly defining a real problem&lt;/li>
&lt;li>confronting the inefficiencies of its own processes&lt;/li>
&lt;li>acknowledging cultural resistance to change&lt;/li>
&lt;li>committing to outcomes, not experiments&lt;/li>
&lt;/ul>
&lt;p>We want the benefits of transformation without doing the work of transformation. And AI, with its promise of instant acceleration, makes that temptation harder to resist than ever. We could approach this differently. With honesty. With focus. With uncomfortable but necessary conversations. But there’s a new model out this week. Let’s start again 🙃&lt;/p></description></item><item><title>Still stuck in Permit A38? Stop AI-ing the wrong things</title><link>https://m365princess.com/blogs/permita38/</link><pubDate>Wed, 30 Apr 2025 07:23:53 +0000</pubDate><guid>https://m365princess.com/blogs/permita38/</guid><description>&lt;h2 id="from-control-to-trust-the-cultural-shift-companies-must-embrace">From control to trust: the cultural shift companies must embrace&lt;/h2>
&lt;p>In many organizations, getting anything done still feels like hunting for a mythical permit – a modern &lt;strong>Permit A38&lt;/strong> &lt;em>(Originally from the 1976 animated film Asterix Conquers Rome, &amp;ldquo;Permit A38&amp;rdquo; refers to a hilariously absurd bureaucratic ordeal, where characters are trapped in an endless cycle of forms, counters, and conflicting instructions. Over time, it became a cultural symbol for senseless administrative madness. My german readers will know it by the term &amp;ldquo;Passierschein A38&amp;rdquo;.&lt;/em>
For everyone who wants to cry in front of their screen: &lt;a href="https://www.dailymotion.com/video/x36574i">https://www.dailymotion.com/video/x36574i&lt;/a>)&lt;/p>
&lt;p>Employees battling endless approvals, layers of oversight and opaque rules find themselves stuck in bureaucratic mazes. This control‑based mindset is the legacy of industrial‑age management: every decision must climb a pyramid of sign‑offs. The result is a costly gridlock – projects stall, morale sinks, and the organization moves too slowly to adapt. In practical terms, managers who insist on rigid control create exactly the kinds of absurd roadblocks that the Asterix metaphor exposes through satire.&lt;/p>
&lt;p>The antidote is a culture of trust and transparency (Yeah i know, what a revolutionary thought, that one could trust their employees and coworkers 😇) Instead of micromanaging every task, leaders give teams context and let them decide how best to achieve goals. Research shows that organizations with high‑trust cultures enjoy significantly better business results: high trust correlates with higher growth, profitability, employee retention and agility.&lt;/p>
&lt;p>Trust means fewer hand‑offs and approvals: decisions happen where the work is done. Transparency (instead of information hoarding) removes hidden bottlenecks so staff can spot problems early. Together, trust and openness liberate creative energy: teams move fast because they aren’t slowed by fear of stepping on toes. Decisions aren’t rubber‑stamped by executives; instead teams act with great freedom as long as they align with clear company goals &lt;em>(but wait&amp;hellip; did you communicate those? Just a friendly reminder to do this as well)&lt;/em>. An inspiring example? Morningstar (the tomato processing company) scrapped bosses entirely. Each employee negotiates their own mission and commitments, with &lt;em>all interactions being voluntary&lt;/em> and no one forcing tasks on others​.&lt;/p>
&lt;p>This radical self‑management model has no place for the traditional HR “control tower”. The result is an extremely agile factory: employees handle surprises and make smart trade‑offs on the fly, instead of waiting on managers. Outcomes speak for themselves – Morningstar’s system yields record response times and customer loyalty.&lt;/p>
&lt;blockquote>
&lt;p>Without this shift, companies will drown in bureaucracy&lt;/p>
&lt;/blockquote>
&lt;p>&lt;img alt="downing meme" src="https://m365princess.com/images/drown.png">&lt;/p>
&lt;p>As Fujitsu strategist Robin Speculand puts it, &lt;em>painting a digital interface on old processes is just applying digital lipstick – it looks modern at first glance, but behind the scenes… there is too much bureaucracy. This will never deliver the agility and outcomes possible through true transformation​&lt;/em>&lt;/p>
&lt;p>To genuinely break the A38 cycle, the entire culture must move from fear of risk and blind adherence to rules, toward trusting employees to do the right thing. As one study puts it: automation alone won’t fix flawed processes – you’ll just be &lt;em>doing the wrong thing, faster™️&lt;/em>&lt;/p>
&lt;h2 id="changing-culture-is-hard-but-the-payoff-is-real">Changing culture is hard, but the payoff is real&lt;/h2>
&lt;p>Organizations that invest in openness and empower their people become the agile, innovative ones. Indeed, Deloitte found that trusted companies outperform their peers by up to 400%, with trusted brands keeping customers and employees far longer​.&lt;/p>
&lt;p>In short, it’s time for executives to ask: are we creating more A38s in our org? Or are we replacing them with clear mission and trust? Companies that embrace a trust‑first culture move at the speed of ideas, not permissions. They bust bureaucracy instead of nurturing it, and ultimately achieve far more.&lt;/p>
&lt;h2 id="breaking-bureaucracy-with-tools">Breaking bureaucracy with tools?&lt;/h2>
&lt;p>It’s not just rules and forms – outdated tools fuel the madness. Too many offices still run on 1970s technology. &lt;em>Why are you still running your business entirely on email (1971) and spreadsheets (1976)?&lt;/em> asks &lt;a href="https://www.linkedin.com/in/nickellis74/">Nick Ellis&lt;/a> – after all, &lt;em>time has moved on, but a lot of business practice has not.&lt;/em>&lt;/p>
&lt;p>In practice, clinging to email chains, paper forms and siloed spreadsheets costs time and money. Every hand‑written approval or attachment email is a mini‑A38: it forces employees to chase one another, re-enter data and wait days for a reply. Simple tasks like approvals that take an hour by email can be sliced to 10–15 minutes with an automated workflow​.&lt;/p>
&lt;p>But before we now jump onto the &lt;em>Everything is awesome&lt;/em> wagon with Microsoft 365, Power Platform and more&amp;hellip; let&amp;rsquo;s press pause for a while.&lt;/p>
&lt;blockquote>
&lt;p>The most important insight is that technology should empower better processes, not just digitize flawed ones.&lt;/p>
&lt;/blockquote>
&lt;p>Before automating any workflow, leaders should ask whether each approval step still adds value. In many companies, extra sign-offs exist not to improve outcomes but to protect individuals from blame. We usually call this the &lt;em>cover my ass- culture&lt;/em>. Simply automating an unnecessary approval only cements an outdated culture of caution and finger-pointing. Truly forward-looking organizations use new tools to question legacy habits. They strip out redundant approvals and give teams the autonomy to move quickly. The goal is not just a faster version of yesterday’s process, but a reimagined™️ process where trust, transparency, and clear responsibility replace rote checkboxes. By using technology to remove (not reinforce) needless checks and bureaucratic safeguards, businesses can unleash genuine agility and growth.&lt;/p>
&lt;h2 id="lets-escape-shallow-ai-use-cases-and-fix-systems-that-dont-work">Let&amp;rsquo;s escape shallow AI use cases and fix systems that don’t work&lt;/h2>
&lt;p>After culture and tools, many executives pin their hopes on AI – yet too often it’s applied superficially. &lt;em>Let’s launch a chatbot&lt;/em>, becomes the battle cry, without questioning if the root process is broken. In practice, these half‑baked AI deployments can create a new layer of frustration, not freedom. Consider chatbots: a recent survey found that more than two-thirds of customers have had a poor chatbot experience. The biggest complaints? Bots that can’t answer questions and fail to understand human needs, cited by roughly 70% of users​.&lt;/p>
&lt;p>In other words, people still demand to talk to a person when things get complex. Many companies indeed admit that replacing human agents with bots (to cut costs) leads to angry customers when the bot can’t help​.&lt;/p>
&lt;p>Internally, the story is similar: Helpdesk AI might quickly answer &lt;em>I need to reset my pasword&lt;/em>, but when an unusual issue arises it simply dead-ends or loops. The net effect? Workers still spend hours waiting for a human fix – now worse off because they think the AI will solve it. This is the proverbial “new A38” in drag – technology promising relief but glossing over the real problem.&lt;/p>
&lt;blockquote>
&lt;p>Slapping generative AI on a bad process is digital lipstick 💄&lt;/p>
&lt;/blockquote>
&lt;p>For example, a sales team might get an AI assistant that drafts emails, but if their CRM workflows are chaotic, they end up sending the wrong information to the wrong customer faster than before. We must be careful not to chase every shiny AI trend blindly. True “AI for good” is about fixing the system, not papering over it. It means applying AI to reduce friction and do work humans shouldn’t have to do. Here are some better examples than the &lt;em>let me chat to my documents because our SharePoint is an entire mess&lt;/em>-use case:&lt;/p>
&lt;p>&lt;img alt="don&amp;rsquo;t make me build you yet another " src="https://m365princess.com/images/dontmakeme.png">&lt;/p>
&lt;h3 id="process-mining">Process Mining&lt;/h3>
&lt;p>Before automating, understand the flow. AI‑driven process mining tools connect to event logs (from email systems, ERPs, etc.) to map out actual business processes – including all the detours and rework. This “MRI scan” of the organization uncovers where approvals loop or people redo work. For instance, an analysis might reveal 15 ways a purchase order gets approved, with some taking 10 days. Armed with that insight, teams can eliminate a bad step, combine roles, or automate a hand‑off. Rather than guess-working, companies using process mining can identify millions in wasted time.&lt;/p>
&lt;h3 id="intelligent-document-processing-idp">Intelligent Document Processing (IDP)&lt;/h3>
&lt;p>Many businesses drown in paperwork – invoices, forms, claims, contracts. AI‑powered IDP tools use OCR plus machine learning to read and extract data from any document. The impact is dramatic. Industry studies report that IDP can halve processing time and almost eliminate errors​. In effect, IDP gives every worker a tireless assistant who “keys in” paperwork instantly.&lt;/p>
&lt;h3 id="aiassisted-decision-support">AI‑Assisted Decision Support&lt;/h3>
&lt;p>Some decisions involve digesting complex data (risk assessments, scheduling, forecasts). Here AI can act as a true copilot. For example, machine learning models can score loan applicants instantly, highlighting the few that need human review. In inventory planning, AI can predict likely stockouts so buyers can reorder before shelves empty (fixing the “run down to the last piece” chaos). In HR, AI can flag the strongest candidates from thousands of resumes, ensuring recruiters focus on the best fit (rather than leaving applicants lost in inboxes). In each case, the bottleneck is not the chat, but the information overload – AI can cut through that.&lt;/p>
&lt;h3 id="preventing-fake-automation">Preventing &amp;ldquo;Fake Automation&amp;rdquo;&lt;/h3>
&lt;p>The worst trap is so-called automation that merely shifts work. For example, emailing a PDF and expecting staff to print-sign-scan it is not automation – it’s still a paper chase in disguise. Instead, organizations must integrate AI with workflow. An “AI‑powered invoice bot” should automatically post and pay the invoice once it recognizes approval, for instance.&lt;/p>
&lt;h2 id="stop-glorifying-the-pain">Stop glorifying the pain&lt;/h2>
&lt;p>AI should make the pain points disappear, not glorify them. True success stories share a theme: AI unleashed human potential by eliminating drudgery. If employees felt stuck for a human reason (confusing form, unknown next step), talk to them and refine the process first. Then deploy AI to amplify the fix. Above all, remember Fujitsu’s warning: adding “digital lipstick” on broken processes frustrates both customers and employees alike​.&lt;/p>
&lt;p>Don’t let a bot become the new A38 – a sleek interface over a crumbling foundation. Instead, aim for digital transformation that truly, well, transforms: build trust and transparency and modern processes, then apply AI where it brings clear benefit. Escaping the bureaucratic maze isn’t about the next gadget or AI trick, but about aligning people, processes and technology. That means pausing after every automation sprint to ask: “Have we really solved the problem, or just done it faster?”&lt;/p>
&lt;blockquote>
&lt;p>Automation does not equal transformation – if the underlying process is flawed, a faster car still gets you lost in the woods​.&lt;/p>
&lt;/blockquote>
&lt;p>Now go fix the system, but don&amp;rsquo;t chase a new &lt;em>permit A38&lt;/em> with flashy new hats!&lt;/p>
&lt;h2 id="some-resources">Some resources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://hbr.org/webinar/2018/12/building-the-agile-workforce#:~:text=Truly%20great%20workplaces%20understand%20the,trust%20cultures%2C%20profitability%2C%20and%20growth">Building the Agile Workforce&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://business-ecosystem-alliance.org/2024/11/15/morning-star/#:~:text=Morning%20Star%20is%20renowned%20for,commitments%20they%20make%20to%20others">Morning Star: Pioneering Self-Management in Manufacturing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://corporate-blog.global.fujitsu.com/fgb/2024-01-26/01/#:~:text=One%20other%20point%20to%20mention,there%20is%20too%20much%20bureaucracy">Lessons for Digital Transformation success&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.deloitte.com/ng/en/issues/trust/four-factors-of-trust.html">Order The Four Factors of Trust today&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.customerexperiencedive.com/news/consumer-frustration-self-service-live-agent-ivr-chatbot/724620/#:~:text=%2A%20More%20than%20two,thirds%20of%20respondents">Consumers frustrated by inability to switch from self-service to live agent, survey finds&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.reinventingorganizations.com/">REINVENTING ORGANIZATIONS&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Unmeasured Part 5 – Rethinking productivity for a human future</title><link>https://m365princess.com/blogs/unmeasured5/</link><pubDate>Mon, 28 Apr 2025 09:29:31 +0000</pubDate><guid>https://m365princess.com/blogs/unmeasured5/</guid><description>&lt;p>We questioned the obsession with &amp;ldquo;more&amp;rdquo; in &lt;a href="https://www.m365princess.com/blogs/unmeasured/">Part 0&lt;/a>, we exposed the wrong metrics in &lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Part 1&lt;/a>, we showed how running the business is easier to measure than changing it in &lt;a href="https://www.m365princess.com/blogs/unmeasured2/">Part 2&lt;/a>, we dissected fake agility in &lt;a href="https://www.m365princess.com/blogs/unmeasured3/">Part 3&lt;/a> and finally we revealed the emotional wreckage of productivity theater in &lt;a href="https://www.m365princess.com/blogs/unmeasured4/">Part 4&lt;/a>.&lt;/p>
&lt;blockquote>
&lt;p>Now it’s time to ask:
Is the system really broken or was it just built this way?&lt;/p>
&lt;/blockquote>
&lt;p>To me, it works as designed, still, let&amp;rsquo;s go fix it.&lt;/p>
&lt;h2 id="productivity-isnt-the-enemy-our-definition-is">Productivity isn&amp;rsquo;t the enemy. Our definition is.&lt;/h2>
&lt;p>The problem isn&amp;rsquo;t that we want to be productive. The problem is what we&amp;rsquo;ve come to &lt;em>mean&lt;/em> by productive. Faster. Louder. Busier. Visible. Measurable. Exhausted.&lt;/p>
&lt;p>We&amp;rsquo;ve collapsed &amp;ldquo;productive&amp;rdquo; into &amp;ldquo;busy&amp;rdquo;, and then we wonder why teams move fast but go nowhere. Real productivity (the kind that actually changes things, builds value, and sustains people) is slower. It doesn&amp;rsquo;t neatly fit into sprint cycles, it doesn&amp;rsquo;t show up well in dashboards and it can&amp;rsquo;t b automated or templated. And that&amp;rsquo;s exactly why it matters.&lt;/p>
&lt;h2 id="beyond-output-measuring-value-and-health">Beyond output: measuring value and health&lt;/h2>
&lt;p>If we want a future worth building, we have to move beyond measuring units of work produced.
We have to measure what actually creates progress:&lt;/p>
&lt;ul>
&lt;li>Outcomes, not just deliverables&lt;/li>
&lt;li>Learning velocity, not just story points&lt;/li>
&lt;li>Sustainability of pace, not just effort levels&lt;/li>
&lt;li>Creative breakthroughs, not just task lists&lt;/li>
&lt;li>Psychological safety, not just attendance&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>You can automate output.
You can’t automate meaning.&lt;/p>
&lt;/blockquote>
&lt;h2 id="rethinking-systems-not-just-speed">Rethinking systems, not just speed&lt;/h2>
&lt;p>The future of productivity isn’t about speeding up tired systems with better tools. It’s about redesigning the systems themselves. Systems that:&lt;/p>
&lt;ul>
&lt;li>Leave space for deep work, real rest, and human rhythms&lt;/li>
&lt;li>Reward challenging the status quo, not just clearing the backlog&lt;/li>
&lt;li>Trust teams to make decisions, instead of drowning them in status meetings&lt;/li>
&lt;li>Make recovery, creativity, and long-term thinking visible parts of performance&lt;/li>
&lt;/ul>
&lt;p>in short: Systems that see people as humans—not resources to be optimized. We don’t need faster teams. We need smarter environments.&lt;/p>
&lt;p>So while we are at it: We should drop &amp;ldquo;HR&amp;rdquo; as in human resources. This technical framing on people is dehumanizing.&lt;/p>
&lt;h2 id="what-real-metrics-could-look-like">What real metrics could look like&lt;/h2>
&lt;p>Imagine this:&lt;/p>
&lt;ul>
&lt;li>Instead of asking, &lt;em>How many tasks were completed this week&lt;/em>? You ask, What friction did we remove?&lt;/li>
&lt;li>Instead of tracking sprint velocity like a blood sport, you ask, &lt;em>What did we learn that will make the next sprint smarter&lt;/em>?&lt;/li>
&lt;li>Instead of bragging about &lt;em>being heads-down all quarter&lt;/em>, you celebrate, &lt;em>What breakthroughs did space and rest create?&lt;/em>&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Numbers still matter—but they need to tell the right stories. Stories of impact, not just activity.&lt;/p>
&lt;/blockquote>
&lt;h2 id="copilot-ai-and-the-future-of-human-work">Copilot, AI, and the future of human work&lt;/h2>
&lt;p>AI is already taking over the visible, repeatable tasks:&lt;/p>
&lt;ul>
&lt;li>Summarizing meetings&lt;/li>
&lt;li>Drafting reports&lt;/li>
&lt;li>Automating workflows&lt;/li>
&lt;/ul>
&lt;p>That’s not the threat. That’s the opportunity.&lt;/p>
&lt;p>Because the parts of work that remain human, the ambiguous, the relational, the creative, the emotional, those are the parts we’ve historically undervalued and undermeasured. The future belongs to teams that stop fighting to outpace the machines and start doubling down on what machines can’t replicate:
Judgment. Creativity. Empathy. Adaptability. Vision.&lt;/p>
&lt;blockquote>
&lt;p>AI can draft a plan.
Only humans can know whether it’s the right plan.&lt;/p>
&lt;/blockquote>
&lt;h2 id="closing-beyond-unmeasured">Closing: Beyond Unmeasured&lt;/h2>
&lt;p>We don&amp;rsquo;t need another round of optimization. We need a new philosophy of work. One that starts with a different set of assumptions:&lt;/p>
&lt;ul>
&lt;li>People are not productivity machines&lt;/li>
&lt;li>Value is not the same as volume&lt;/li>
&lt;li>Rest is not a reward; it’s a requirement&lt;/li>
&lt;li>Thinking time is not wasted time&lt;/li>
&lt;li>Trust is not optional—it’s operational&lt;/li>
&lt;/ul>
&lt;p>This isn’t about working less, it’s about working right.&lt;/p>
&lt;p>It’s about asking not just, &lt;em>How much did we do?&lt;/em> but, &lt;em>Was it the right work? Did it matter? Did we leave space for better ideas, better systems, and better lives?&lt;/em>&lt;/p>
&lt;p>Better work begins with better systems. And better systems start when we finally measure the things that truly matter.&lt;/p>
&lt;h2 id="more-parts-of-this-series">More parts of this series&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured0/">Unmeasured: Part 0 - More ain&amp;rsquo;t better&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Unmeasured: Part 1 – We are measuring the wrong things&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured2/">Unmeasured: Part 2 – The difference between running and changing your business&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured3/">Unmeasured: Part 3 – The agility illusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured4/">Unmeasured: Part 4 – The emotional cost of the productivity theater&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Unmeasured: Part 4 – The emotional cost of the productivity theater</title><link>https://m365princess.com/blogs/unmeasured4/</link><pubDate>Sat, 26 Apr 2025 09:29:31 +0000</pubDate><guid>https://m365princess.com/blogs/unmeasured4/</guid><description>&lt;p>We gave everyone Copilot. We automated the meetings, the summaries, the reports. We freed up time. And what did we do with it?&lt;/p>
&lt;blockquote>
&lt;p>We built a faster treadmill.&lt;/p>
&lt;/blockquote>
&lt;p>(I&amp;rsquo;d even call it a &lt;em>dreadmill&lt;/em>)&lt;/p>
&lt;p>We turned the productivity theater into a 24/7 show. We forgot that humans are not machines, and now we’re burning them out faster than ever.&lt;/p>
&lt;p>&lt;img alt="everyday I&amp;rsquo;m hustling" src="https://m365princess.com/images/hustle.png">&lt;/p>
&lt;h2 id="always-performing-never-breathing">Always performing, never breathing&lt;/h2>
&lt;p>Modern work has turned into a constant stage production. It’s not enough to do your job; you have to be seen doing it. Green lights in Teams chats, super-fast replies to emails, endless calendar invites that prove how busy and important you are. Every minute of your day is visible, trackable, and ultimately judged.&lt;/p>
&lt;p>Thinking? Invisible. Strategic reflection? Invisible. Actual problem-solving? Mostly invisible too.&lt;/p>
&lt;p>Copilot accelerates the visible tasks: writing emails, summarizing meetings, producing outlines faster than ever before. But instead of using the time gained to breathe, to think, to recover, we fill it with more noise. We push even harder. We demand even more.&lt;/p>
&lt;p>We freed up time; then we punished people for not refilling it with even more visible busyness.&lt;/p>
&lt;p>&lt;img alt="xkcd automate" src="https://m365princess.com/images/xkcd-automate.png">&lt;/p>
&lt;h2 id="speed-isnt-freedom-its-a-trap">Speed isn&amp;rsquo;t freedom. It&amp;rsquo;s a trap.&lt;/h2>
&lt;p>Copilot and other AI tools were sold to us as a path to smarter, freer work. Less admin. Less repetitive grind. More room for creativity, for deep work, for innovation. But nobody updated the expectations. Nobody paused to ask: if the work speeds up, does the human pace stay the same?&lt;/p>
&lt;p>Instead, faster output led directly to faster demands. Tasks that once took hours now take minutes, but rather than creating space to think better, we just stack more tasks on top. Deadlines shrink. Response times tighten. Expectations creep upward. The system got faster, but the humans inside it are still operating on the same biological limits, and they’re breaking.&lt;/p>
&lt;h2 id="the-metrics-are-still-killing-us">The metrics are still killing us&lt;/h2>
&lt;p>The new world of Copilot-generated deliverables didn’t come with a new dashboard. It’s still the same old obsession: output, throughput, activity.&lt;/p>
&lt;p>Managers look at the new velocity numbers with excitement, celebrating the “boost” in productivity without asking the harder questions:&lt;/p>
&lt;ul>
&lt;li>Are people thinking better, or just producing faster?&lt;/li>
&lt;li>Are teams learning, or just sprinting?&lt;/li>
&lt;/ul>
&lt;p>We have smarter tools, but we still treat people like battery packs: how much can we squeeze out before the lights flicker?&lt;/p>
&lt;h2 id="productivity-theater-a-survival-strategy">Productivity theater: a survival strategy&lt;/h2>
&lt;p>In this system, survival isn’t about doing better work but about being seen doing work. You learn quickly: Stay visible. Move fast. Never pause too long or someone will assume you’re slacking. The cycle feeds itself.&lt;/p>
&lt;p>Hard thinking becomes risky. Questioning becomes dangerous. Deep work becomes a liability.&lt;/p>
&lt;p>And so, people perform.&lt;/p>
&lt;p>They perform busyness. They perform progress. They perform commitment.
Not because they are lazy, but because the system rewards performance over purpose.&lt;/p>
&lt;p>Productivity theater isn’t a glitch; it’s a rational adaptation to an irrational environment.&lt;/p>
&lt;h2 id="burnout-isnt-a-bug">Burnout isn&amp;rsquo;t a bug&lt;/h2>
&lt;p>It&amp;rsquo;s the system working as designed. We pretend burnout is a personal failing.
We say people should &amp;ldquo;manage their stress better&amp;rdquo;, &amp;ldquo;find balance&amp;rdquo;, &amp;ldquo;use their wellness apps&amp;rdquo;. Meanwhile, the entire structure demands more, faster, louder, constantly.&lt;/p>
&lt;ul>
&lt;li>We don&amp;rsquo;t measure emotional load&lt;/li>
&lt;li>We don&amp;rsquo;t track cognitive exhaustion&lt;/li>
&lt;li>We don&amp;rsquo;t reward sustainable pacing or deep work or creative recovery&lt;/li>
&lt;/ul>
&lt;p>Burnout isn’t a rare tragedy. It’s a feature. It&amp;rsquo;s the predictable outcome of a system obsessed with deliverables and blind to human cost.&lt;/p>
&lt;p>Now Copilot lets you create more deliverables, faster. And if you think that will reduce burnout, you haven&amp;rsquo;t been paying attention.&lt;/p>
&lt;h2 id="what-if-we-measured-what-actually-matters">What if we measured what actually matters?&lt;/h2>
&lt;p>Imagine if the health of a team wasn’t judged by how many tickets they closed or how many meetings they sat through. Imagine measuring trust, safety, learning, and resilience. Imagine dashboards that asked:&lt;/p>
&lt;ul>
&lt;li>Are teams making space for recovery after big pushes?&lt;/li>
&lt;li>Are people allowed to say no to bad ideas without fear?&lt;/li>
&lt;li>Are leaders modeling sustainable work, or just sprinting toward the next collapse?&lt;/li>
&lt;/ul>
&lt;h2 id="you-cant-automate-empathy">You can&amp;rsquo;t automate empathy&lt;/h2>
&lt;p>Copilot can write your meeting summaries. It can help you draft your status updates. It can suggest faster ways to complete tasks.&lt;/p>
&lt;p>But it cannot see when your engineer is one deliverable away from collapse. It cannot notice when your designer stops taking creative risks because they&amp;rsquo;re too drained. It cannot feel when your team starts working out of fear instead of purpose.&lt;/p>
&lt;ul>
&lt;li>Empathy is leadership.&lt;/li>
&lt;li>Empathy is strategy.&lt;/li>
&lt;li>Empathy cannot be automated.&lt;/li>
&lt;/ul>
&lt;p>If we only optimize for visible output, we will burn through our best people, and no dashboard will warn us until it&amp;rsquo;s too late.&lt;/p>
&lt;h2 id="closing-thought">Closing thought&lt;/h2>
&lt;p>If your dashboards look great but your teams are exhausted, you’re not succeeding, you’re just managing a slow-motion collapse. Productivity gains mean nothing if they cost your people their energy, their creativity, their trust. The future isn’t about automating faster. It’s about &lt;em>caring&lt;/em> smarter.&lt;/p>
&lt;p>The next post will close out the &lt;strong>Unmeasured&lt;/strong> series by exploring how we can rethink productivity altogether: Not just to get more done, but to build something more human, more sustainable, and ultimately more valuable.&lt;/p>
&lt;p>Because better work begins with better systems. And better systems start by respecting the people inside them.&lt;/p>
&lt;h2 id="more-parts-of-this-series">More parts of this series&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured0/">Unmeasured: Part 0 - More ain&amp;rsquo;t better&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Unmeasured: Part 1 – We are measuring the wrong things&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured2/">Unmeasured: Part 2 – The difference between running and changing your business&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured3/">Unmeasured: Part 3 – The agility illusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured4/">Unmeasured: Part 4 – The emotional cost of the productivity theater&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="some-resources">Some resources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://enriquesomoza.medium.com/burnout-isnt-a-bug-in-product-management-it-s-a-feature-564fcba681f3">Burnout Isn’t a Bug in Product Management — It’s a Feature&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://hrzone.com/you-cant-automate-compassion-strong-culture-starts-with-human-leaders-not-ai/">You can’t automate compassion: Strong culture starts with human leaders, not AI&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.sap.com/germany/blogs/how-to-fix-emotional-labor">Emotional labor is frying your staff. You can fix it.&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Unmeasured: Part 3 – The agility illusion</title><link>https://m365princess.com/blogs/unmeasured3/</link><pubDate>Thu, 24 Apr 2025 10:45:29 +0000</pubDate><guid>https://m365princess.com/blogs/unmeasured3/</guid><description>&lt;p>Agile is everywhere.&lt;br>
Every team is agile. Every board is a Kanban. Every meeting is a stand-up. Every backlog is groomed. Every two weeks is a sprint.&lt;/p>
&lt;p>It looks good. It sounds good.&lt;br>
But let’s be honest—most of the time, it’s complete theater.&lt;/p>
&lt;p>We don’t have an agility problem.&lt;br>
We have a &lt;strong>pretending-to-be-agile&lt;/strong> problem.&lt;/p>
&lt;blockquote>
&lt;p>How delulu are you?&lt;/p>
&lt;/blockquote>
&lt;h2 id="rituals-without-responsiveness">Rituals without responsiveness&lt;/h2>
&lt;p>You can follow every step in the agile playbook and still be stuck in old behaviors:&lt;/p>
&lt;ul>
&lt;li>Stand-ups that are just status updates&lt;/li>
&lt;li>Sprints that are really just mini-waterfalls with fixed scopes&lt;/li>
&lt;li>“Product owners” who just translate stakeholder requests&lt;/li>
&lt;li>“Retrospectives” that produce action items nobody follows up on&lt;/li>
&lt;li>“Velocity” charts used to pressure teams into overcommitting&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="agile waterfall meme" src="https://m365princess.com/images/agile-waterfall.png">&lt;/p>
&lt;p>These are rituals. And they often exist &lt;strong>without&lt;/strong> the mindset that makes agility work: trust, adaptability, continuous improvement.&lt;/p>
&lt;p>Teams burn through tasks at speed, but they’re not solving the right problems. They’re just sprinting in place.&lt;/p>
&lt;blockquote>
&lt;p>If you ship fast but never stop to ask, “Should we be building this at all?”&lt;br>
You’re not agile. You’re just efficient at being wrong.&lt;/p>
&lt;/blockquote>
&lt;h2 id="weaponizing-the-agile-manifesto">Weaponizing the Agile Manifesto&lt;/h2>
&lt;p>The &lt;a href="https://agilemanifesto.org/">Agile Manifesto&lt;/a> wasn’t supposed to be a loophole.&lt;/p>
&lt;p>It was written as a rebellion against heavy processes and rigid hierarchies: a call for teams to focus on people, collaboration, and real outcomes. But today? It’s often misquoted, misused, and turned into a shield for bad behavior.&lt;/p>
&lt;p>Let’s talk examples:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>“Working software over comprehensive documentation”&lt;/strong> becomes&lt;br>
&lt;em>“We don’t write documentation at all.”&lt;/em>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>“Customer collaboration over contract negotiation”&lt;/strong> becomes&lt;br>
&lt;em>“We’re building cool stuff&amp;hellip; we throw it over the fence and keep fingers cross in hope for the customer likes it.”&lt;/em>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Agility gets turned into an excuse not to plan, not to reflect, not to write things down. That’s not adaptive. That’s lazy.&lt;/p>
&lt;h2 id="agile-in-form-waterfall-in-spirit">Agile in form, waterfall in spirit&lt;/h2>
&lt;p>Many organizations claim to be agile but still operate with a waterfall mindset:&lt;/p>
&lt;ul>
&lt;li>Quarterly “agile” planning cycles that lock in work for 3 months&lt;/li>
&lt;li>Story point estimates (the bane of my existence) treated as fixed commitments&lt;/li>
&lt;li>Teams with no autonomy, just assigned work from the top&lt;/li>
&lt;li>Managers using Jira like it’s a performance tracker&lt;/li>
&lt;/ul>
&lt;p>You get speed, but not flexibility. Delivery, but no discovery. A product roadmap that’s full, but nobody’s sure why those things are on it.&lt;/p>
&lt;p>It’s agile in form, but waterfall in spirit. (And a cry for help!)&lt;/p>
&lt;p>&lt;img alt="victoria agile meme" src="https://m365princess.com/images/victoria-agile.png">&lt;/p>
&lt;h2 id="the-control-paradox">The control paradox&lt;/h2>
&lt;p>Agile is supposed to be about &lt;strong>trust&lt;/strong>. But in practice, it’s often wrapped in even tighter control:&lt;/p>
&lt;ul>
&lt;li>More metrics. More meetings. More layers.&lt;/li>
&lt;li>Teams are “self-organizing” on paper, but still need approval for everything&lt;/li>
&lt;li>Leaders say they want experimentation (but only if it delivers predictable results on a fixed timeline with zero risk 🤡)&lt;/li>
&lt;/ul>
&lt;p>That’s not agility. That’s micromanagement in a hoodie and cool sneakers.&lt;/p>
&lt;h2 id="the-measurement-problem-again">The Measurement problem (again)&lt;/h2>
&lt;p>Agile teams are constantly measured: velocity, burndown, throughput, cycle time. These can be useful. But when they become &lt;strong>targets&lt;/strong>, they backfire:&lt;/p>
&lt;ul>
&lt;li>Velocity becomes a scoreboard; teams inflate story points or sandbag to hit the number (been there, done that 😇)&lt;/li>
&lt;li>Burndown charts become sources of anxiety instead of insight&lt;/li>
&lt;li>“Team performance” becomes about moving units of work, not solving actual problems&lt;/li>
&lt;/ul>
&lt;p>Just like with productivity metrics, these tools stop being helpful when they’re used to judge instead of to learn.&lt;/p>
&lt;blockquote>
&lt;p>If your metrics create fear instead of curiosity, you’ve lost the point.&lt;/p>
&lt;/blockquote>
&lt;h2 id="so-what-does-real-agility-look-like">So what does real agility look like?&lt;/h2>
&lt;p>Real agility is &lt;em>not&lt;/em> stand-ups, sprints, or post-its on a whiteboard.&lt;/p>
&lt;p>&lt;img alt="work chronicles" src="https://m365princess.com/images/wc-agile2.png">&lt;/p>
&lt;p>It’s a system that:&lt;/p>
&lt;ul>
&lt;li>Empowers teams to make decisions&lt;/li>
&lt;li>Encourages reflection and iteration&lt;/li>
&lt;li>Prioritizes value over velocity&lt;/li>
&lt;li>Builds space for thinking, not just doing&lt;/li>
&lt;li>Respects quality—including tests, documentation, and design&lt;/li>
&lt;li>Focuses on solving real customer problems, not just shipping features&lt;/li>
&lt;/ul>
&lt;p>It’s a system where people are trusted to change course. To challenge the brief. To say, “This isn’t the right thing to build”.&lt;/p>
&lt;p>See how this neatly ties into the &lt;a href="https://www.m365princess.com/blogs/unmeasured2/">2nd part of my unmeasured series&lt;/a> (please, do yourself a favor and go read this if you came unprepared to this one, thanks)&lt;/p>
&lt;blockquote>
&lt;p>Real agility supports “Change the Business” work.&lt;br>
Fake agility just helps you “Run the Business” faster.&lt;/p>
&lt;/blockquote>
&lt;h2 id="final-thoughts">Final thoughts&lt;/h2>
&lt;p>You can’t measure agility by counting how often you stand up. You can’t fake adaptability by sprinting harder. If the rituals are there but the responsiveness isn’t, it’s not agility. It’s performance. And performance is exhausting.&lt;/p>
&lt;p>&lt;strong>Part 4&lt;/strong> will explore the emotional cost of productivity theater, and what it means to measure well-being, not just output.&lt;/p>
&lt;p>Until then, maybe skip the retro this week and ask the real question:&lt;br>
&lt;em>Are we actually getting better?&lt;/em> Or just faster at doing the wrong things?&lt;/p>
&lt;h2 id="more-parts-of-this-series">More parts of this series&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured0/">Unmeasured: Part 0 - More ain&amp;rsquo;t better&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Unmeasured: Part 1 – We are measuring the wrong things&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured2/">Unmeasured: Part 2 – The difference between running and changing your business&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured3/">Unmeasured: Part 3 – The agility illusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured4/">Unmeasured: Part 4 – The emotional cost of the productivity theater&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="some-resources">Some resources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.mb-consulting.dev/scrum-sucks-9960011fc5cf">Scrum sucks&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://mariachec.medium.com/scrum-is-a-cancer-stop-doing-scrum-thoughts-on-a-post-i-read-79ced3dc749e">Scrum: Failure By Design?&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Unmeasured: Part 2 – The difference between running and changing your business</title><link>https://m365princess.com/blogs/unmeasured2/</link><pubDate>Thu, 24 Apr 2025 06:45:29 +0000</pubDate><guid>https://m365princess.com/blogs/unmeasured2/</guid><description>&lt;h2 id="only-measuring-the-visible-half">Only measuring the visible half&lt;/h2>
&lt;p>In &lt;a href="https://www.m365princess.com/blogs/unmeasured0/">Part 0&lt;/a>, I opened the series with a core challenge: &lt;em>How do we measure productivity in knowledge work when the work itself is abstract, dynamic, and often invisible?&lt;/em> In &lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Part 1&lt;/a>, I unpacked how measurement tends to flatten complex human effort into simple outputs—units, tickets, hours, tasks.&lt;/p>
&lt;p>Now in this part 2, we shift the lens from &lt;em>what&lt;/em> we measure to &lt;em>where&lt;/em> we measure. And the problem becomes clear: most organizations are only measuring the half of the work they can see: &lt;strong>Running the Business&lt;/strong>, while ignoring the less visible but more impactful work of &lt;strong>Changing the Business&lt;/strong>.&lt;/p>
&lt;h2 id="the-comfort-of-the-tangible">The comfort of the tangible&lt;/h2>
&lt;p>Dashboards are filled with numbers that signal control: cases resolved, hours billed, emails sent, campaigns launched. These are the markers of Run-the-Business performance. They feel satisfying because they’re concrete, immediate, and easy to quantify.&lt;/p>
&lt;p>But there’s a trap here.&lt;/p>
&lt;p>Tangible results are often short-term by design. They represent &lt;em>activity&lt;/em>, not necessarily &lt;em>progress&lt;/em>. Running the business efficiently doesn’t guarantee you’re heading in the right direction; it just means you’re moving fast.&lt;/p>
&lt;p>In contrast, the work of Changing the Business is&lt;/p>
&lt;ul>
&lt;li>Messy&lt;/li>
&lt;li>Hard to measure&lt;/li>
&lt;li>Long-term by nature&lt;/li>
&lt;li>Usually intangible until much later&lt;/li>
&lt;/ul>
&lt;p>And yet, this is the work that drives real transformation. Strategy. Design. Capability building. Cultural change. These don’t show up in weekly reports, but they shape the next five years.&lt;/p>
&lt;h2 id="run-vs-change-the-divide-that-shapes-behavior">Run vs. change: The divide that shapes behavior&lt;/h2>
&lt;p>We often use “Run the Business” and “Change the Business” as simple labels, but their influence is anything but simple:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Run&lt;/strong> is visible, urgent, and metric-friendly. It thrives in dashboards.&lt;/li>
&lt;li>&lt;strong>Change&lt;/strong> is invisible, patient, and risk-laden. It often struggles for airtime.&lt;/li>
&lt;/ul>
&lt;p>Because Run work is easier to track, it becomes the default priority. That’s not just a reporting issue; it’s a behavioral one. When the organization rewards delivery over discovery, people adapt. They chase what&amp;rsquo;s measurable, even when it&amp;rsquo;s not meaningful.&lt;/p>
&lt;h2 id="the-eisenhower-matrix-where-change-gets-lost">The Eisenhower Matrix: Where Change Gets Lost&lt;/h2>
&lt;p>Let’s map this out using the Eisenhower Matrix:&lt;/p>
&lt;p>&lt;img alt="Eisenhower Matrix" src="https://m365princess.com/images/eisenhower.png">&lt;/p>
&lt;p>“Run the Business” activities dominate the &lt;strong>Urgent&lt;/strong> half. They feel vital, because they are. But they can also crowd out the &lt;strong>Important&lt;/strong> half where Change work lives.&lt;/p>
&lt;p>The most &lt;em>impactful&lt;/em> work is rarely urgent. It’s strategic, slow-cooked, and context-heavy. But because it&amp;rsquo;s not immediately tangible, it’s the first to be postponed or deprioritized when the calendar fills up, especially, as there are rarely any passive-aggressive emails that state to be a &amp;ldquo;friendly reminder&amp;rdquo; or &amp;ldquo;bump this up in one&amp;rsquo;s inbox&amp;rdquo;. Change work is usually not depending on deadlines, nor does iit usually originate from someone else&amp;rsquo;s ideas.&lt;/p>
&lt;h2 id="the-genai-shift-productivity-boost-or-treadmill-acceleration">The GenAI shift: productivity boost or treadmill acceleration?&lt;/h2>
&lt;p>Now enter &lt;strong>Copilot&lt;/strong> and other GenAI tools. They promise a huge leap in productivity by handling summaries, content drafts, meeting notes, and even analysis.&lt;/p>
&lt;p>That’s a game-changer for Run work. Copilot excels (pun intended) at accelerating the visible. But what does that mean for the invisible?&lt;/p>
&lt;p>We now face a choice:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Accelerate the status quo&lt;/strong>, using AI to do the same tasks faster, creating space that gets filled with more of the same. (If you think that won&amp;rsquo;t happen, please dive into the beautiful rabbit hole of &lt;a href="https://en.wikipedia.org/wiki/Parkinson%27s_law">Parkinson&amp;rsquo;s Law&lt;/a>)&lt;/li>
&lt;li>&lt;strong>Redesign the system&lt;/strong>, using the space AI creates to invest in strategic thinking, experimentation, and long-view transformation (but how do we justify this towards middle management with reliable stats 🤡)&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Parkinson&amp;rsquo;s Law" src="https://m365princess.com/images/parkinsons-law.gif">&lt;/p>
&lt;blockquote>
&lt;p>Copilot &lt;em>can&lt;/em> help us Change the Business, if we’re intentional. If not, it simply deepens our addiction to the tangible.&lt;/p>
&lt;/blockquote>
&lt;h2 id="why-we-need-to-measure-the-invisible">Why we need to measure the invisible&lt;/h2>
&lt;p>The obsession with short-term, measurable outcomes doesn’t just distort reporting; it distorts decision-making. It leads to&lt;/p>
&lt;ul>
&lt;li>Undervaluing the people doing strategic or enabling work&lt;/li>
&lt;li>Underfunding initiatives that take longer than a quarter&lt;/li>
&lt;li>Mislabeling the slow, invisible work of systems change as unproductive&lt;/li>
&lt;/ul>
&lt;p>And yet, it’s precisely that work—planning, learning, experimenting that builds future value. We don’t feel the loss until it’s too late, as this is only noticeable in retrospective.&lt;/p>
&lt;p>&lt;img alt="work chronicles" src="https://m365princess.com/images/wc-work.png">&lt;/p>
&lt;h2 id="a-new-approach-to-measurement">A new approach to measurement&lt;/h2>
&lt;p>To work smarter, not just faster, we need to&lt;/p>
&lt;ol>
&lt;li>Surface the critical but hard-to-measure activities: coaching, stakeholder alignment, redesign work&lt;/li>
&lt;li>Elevate metrics that track readiness, resilience, and options, not just velocity.&lt;/li>
&lt;li>Use the capacity unlocked by GenAI to push deeper into transformation—not just crank the handle faster. AI should be your catalyst, not your crutch.&lt;/li>
&lt;li>Pair operational metrics with insight-driven narratives. Numbers show activity; stories reveal impact.&lt;/li>
&lt;/ol>
&lt;h2 id="closing-thought">Closing thought&lt;/h2>
&lt;p>We’re measuring the wrong half of the work. And we’re doing it with tools that are getting sharper, faster, and more seductive.&lt;/p>
&lt;p>But if we don’t rebalance how we measure, we’ll keep winning at the wrong game. More emails. More deliverables. More output, without outcomes.&lt;/p>
&lt;p>&lt;strong>Part 3&lt;/strong> will dive into agility—why so many teams &lt;em>look&lt;/em> agile but don’t &lt;em>act&lt;/em> agile, and how measurement plays a hidden role in that illusion.&lt;/p>
&lt;h2 id="more-parts-of-this-series">More parts of this series&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured0/">Unmeasured: Part 0 - More ain&amp;rsquo;t better&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Unmeasured: Part 1 – We are measuring the wrong things&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured2/">Unmeasured: Part 2 – The difference between running and changing your business&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured3/">Unmeasured: Part 3 – The agility illusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured4/">Unmeasured: Part 4 – The emotional cost of the productivity theater&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="some-resources">Some resources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://varkalos.com/post-book-how-to-measure.html">How to measure anything&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.linkedin.com/pulse/measuring-intangible-innovation-network-ray1c/">Measuring the intangible&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Unmeasured: Part 1 – We are measuring the wrong things</title><link>https://m365princess.com/blogs/unmeasured1/</link><pubDate>Wed, 23 Apr 2025 08:41:29 +0000</pubDate><guid>https://m365princess.com/blogs/unmeasured1/</guid><description>&lt;p>There’s a spreadsheet* somewhere that says you had a good week.&lt;/p>
&lt;ul>
&lt;li>You closed 19 Jira tickets&lt;/li>
&lt;li>You replied to 143 emails&lt;/li>
&lt;li>You joined 17 meetings and dropped 6 comments in a shared doc&lt;/li>
&lt;li>You cleared your inbox twice&lt;/li>
&lt;li>You sent a Teams message at 11:06 p.m., just so everyone knows you’re still &amp;ldquo;on it&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>Congratulations. You’re crushing it. Right?&lt;/p>
&lt;p>Right?&lt;/p>
&lt;p>[* In fact, there is not &lt;em>one&lt;/em> spreadsheet. There are several exported spreadsheets from all kind of apps that don&amp;rsquo;t talk to each other.]&lt;/p>
&lt;h2 id="still-worshipping-the-assembly-line">Still worshipping the assembly Line&lt;/h2>
&lt;p>Most organizations today are swimming in metrics, but they&amp;rsquo;re still measuring knowledge work like we’re on the factory floor. It’s Henry Ford’s mindset in a Copilot world: track every movement, clock every hour, and reward visible output.&lt;/p>
&lt;blockquote>
&lt;p>We call this “data-driven”.
But let’s be honest: it’s mostly bullshit-driven.&lt;/p>
&lt;/blockquote>
&lt;p>We’ve built elegant dashboards on top of shallow metrics. We praise activity over impact, and we confuse quantifiable noise for real value. We pretend more data equals better decisions, but we’re just dressing up dysfunction in charts and calling it strategy.&lt;/p>
&lt;h2 id="the-tyranny-of-vanity-metrics">The tyranny of vanity metrics&lt;/h2>
&lt;p>We celebrate the wrong things because they’re easy to track:&lt;/p>
&lt;ul>
&lt;li>Lines of code written (even if they&amp;rsquo;re rolled back tomorrow)&lt;/li>
&lt;li>Emails sent (even if they’re unread)&lt;/li>
&lt;li>Meetings attended (even if they could’ve been a Loom)&lt;/li>
&lt;li>Teams activity (even if it’s performative panic)&lt;/li>
&lt;li>Time spent in the office (even if you&amp;rsquo;re just wandering between meetings)&lt;/li>
&lt;/ul>
&lt;p>These are vanity metrics. They give us the illusion of productivity. They make dashboards light up in happy greens and blues. But they don’t tell us if the work had impact. If it solved a real problem. If it made anything better.&lt;/p>
&lt;p>And now? AI can write your emails. Draft your code. Summarize your meetings. You can game the system with ChatGPT and still look “productive” on paper. So what does your activity even prove anymore?&lt;/p>
&lt;blockquote>
&lt;p>How do you measure thinking?
You can’t.&lt;/p>
&lt;/blockquote>
&lt;p>You can’t measure the moment you step out of the shower and think, &lt;em>Wait, why are we doing it this way?&lt;/em> You can’t measure the five-minute walk where your brain does the background processing and finally connects the dots. You can’t quantify the watercooler conversation that sparked a new direction for the team—because it wasn’t a scheduled &amp;ldquo;collaboration block&amp;rdquo; in the calendar. Thinking doesn’t show up in productivity metrics. Rest doesn’t either. Neither does curiosity, intuition, or the skill of asking the one question that unlocks a week’s worth of work.&lt;/p>
&lt;p>&lt;img alt="cut tennis balls in half to save space" src="https://m365princess.com/images/balls.png">&lt;/p>
&lt;h2 id="the-nonsense-of-the-40-hour-week">The nonsense of the 40-hour week&lt;/h2>
&lt;p>And while we’re at it: who decided that a full-time job must be 40 hours? (I know, Henry Ford, but why do we still &lt;em>still&lt;/em> stick to that?)&lt;/p>
&lt;p>The 40-hour workweek wasn’t designed for creative problem-solving, deep thinking, or high-trust collaboration. It was designed for factory floors: to split the day into shifts and keep production lines moving. That model had one goal: obedience. Show up. Stay put. Follow the rhythm of the machine.&lt;/p>
&lt;p>And we’re still running on it.&lt;/p>
&lt;p>Say you’re a working dad*. You put in 30 focused hours a week, because you leave on time to pick up your kids, because you’re organized, and you don’t waste time. You deliver the same outcomes. Maybe better.&lt;/p>
&lt;p>But you earn less. Get fewer opportunities. You&amp;rsquo;re treated as “less than full-time”.&lt;/p>
&lt;p>Why? Because you didn’t reach the magic number. You didn’t conform to the schedule. You broke the rhythm of the system built around visibility, not value. This mindset punishes efficiency and rewards endurance. It favors people who perform time over people who perform impact.&lt;/p>
&lt;p>Same goes for contractors - Customers want to know their daily rate and then compare these to the competitors and make the very big (and uneducated) assumption, that a lower daily rate will result in a less costly project. Well. Sorry to break it to you, but that&amp;rsquo;s not how things work.&lt;/p>
&lt;blockquote>
&lt;p>The 40-hour week isn’t about productivity. It’s about control.
And it’s time we stopped confusing obedience with contribution.&lt;/p>
&lt;/blockquote>
&lt;p>[* See what I did there 😇]&lt;/p>
&lt;h2 id="success-isnt-linear-so-why-do-we-measure-it-like-it-is">Success isn’t linear. So why do we measure it like it is?&lt;/h2>
&lt;p>The myth of linear productivity (that every hour produces an equal slice of value) is one of the most damaging ideas in modern work. Especially in knowledge work, where the value often comes in spikes: one conversation, one insight, one wild idea scribbled on a napkin.&lt;/p>
&lt;p>Some of the most high-leverage work I’ve ever seen looked like “nothing”. Some examples?&lt;/p>
&lt;ul>
&lt;li>A dev spending a day not coding to decide what shouldn’t be built.&lt;/li>
&lt;li>A PM spending two hours rewriting the problem statement.&lt;/li>
&lt;li>A senior designer deleting five ideas so the sixth one could breathe.&lt;/li>
&lt;/ul>
&lt;p>None of this shows up on a timesheet. But it&amp;rsquo;s the stuff that changes everything. But also&amp;hellip; don&amp;rsquo;t get me started on timesheets 🙄. Well maybe a little rant:&lt;/p>
&lt;h2 id="timesheets-are-a-farce">Timesheets are a farce&lt;/h2>
&lt;p>Most timesheets are fiction. (will die on that hill)&lt;/p>
&lt;p>They don’t capture what matters, because what matters (thinking, mentoring, unblocking, problem-solving) doesn’t fit neatly into time slots. So employees spend time they could use for something meaningful trying to retroactively guess how they spent their week. It’s not reflection; it’s a wild guessing exercise wrapped in administrative guilt.&lt;/p>
&lt;p>And on the other side? Their manager:&lt;/p>
&lt;ol>
&lt;li>Can’t verify whether any of it is accurate&lt;/li>
&lt;li>Doesn’t have time to check even if they could&lt;/li>
&lt;li>Just hits “Approve” to get on with their actual job&lt;/li>
&lt;/ol>
&lt;p>The result is a semi-automated, zero-value ritual that creates the illusion of control while delivering none. It adds overhead without insight. It&amp;rsquo;s bureaucracy disguised as accountability.&lt;/p>
&lt;p>(And still every organization has some sort of timesheet approval process - or they ask me to build that for them. &lt;em>Thanks, but no, thanks.&lt;/em>)&lt;/p>
&lt;h2 id="the-return-to-office-illusion">The Return-to-Office illusion&lt;/h2>
&lt;p>And then there’s the control problem that sits at the root of all that mess.&lt;/p>
&lt;p>We don’t trust people to be productive unless we can see them. So we roll out return-to-office mandates. Mandatory in-person Tuesdays. Badge scans. Not because the work requires it, but because the building is lonely.&lt;/p>
&lt;blockquote>
&lt;p>It’s not about outcomes. It’s about optics.&lt;/p>
&lt;/blockquote>
&lt;p>If someone delivers thoughtful, creative work from a quiet cabin, we call it slacking. If they’re triple-booked in glass-walled rooms five days a week, we call it committed. As if breathing stale air under fluorescent lights equals impact.&lt;/p>
&lt;p>Especially to my US-american friends where this seems to be even more an issue than in europe: RTO isn’t strategy. It’s building management disguised as culture.&lt;/p>
&lt;p>&lt;img alt="barbie meme" src="https://m365princess.com/images/barbenheimer.png">&lt;/p>
&lt;h2 id="what-are-we-actually-optimizing-for">What are we actually optimizing for?&lt;/h2>
&lt;p>We build dashboards like they’re truth machines. But what if they’re just measuring noise? What if the things that really matter; insight, clarity, connection; are precisely the things that get crushed under the pressure to look busy?&lt;/p>
&lt;p>What if we’ve built entire systems that punish the best parts of human work?&lt;/p>
&lt;ul>
&lt;li>Creativity is messy&lt;/li>
&lt;li>Progress is uneven&lt;/li>
&lt;li>Meaningful work is often quiet&lt;/li>
&lt;li>And the best ideas don’t come when we’re cranking out tickets; they come when we step away long enough to think&lt;/li>
&lt;/ul>
&lt;h2 id="toward-something-better">Toward something better&lt;/h2>
&lt;p>This isn’t just a rant. It’s a reckoning. We can’t fix the productivity problem by tweaking the metrics. We have to change the lens entirely. The question is no longer, &lt;em>How do we do more?&lt;/em>
It’s, &lt;em>How do we do what matters?&lt;/em>&lt;/p>
&lt;p>The next post explores the &amp;ldquo;Run vs. Change the Business&amp;rdquo; divide, and why we&amp;rsquo;re only measuring the part that&amp;rsquo;s easiest to track, not the part that drives transformation.&lt;/p>
&lt;p>But until then, ask yourself:&lt;/p>
&lt;p>Are you being productive, or just looking like it?&lt;/p>
&lt;p>And who, exactly, benefits from the difference?&lt;/p>
&lt;h2 id="more-parts-of-this-series">More parts of this series&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured0/">Unmeasured: Part 0 - More ain&amp;rsquo;t better&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured1/">Unmeasured: Part 1 – We are measuring the wrong things&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured2/">Unmeasured: Part 2 – The difference between running and changing your business&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured3/">Unmeasured: Part 3 – The agility illusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/unmeasured4/">Unmeasured: Part 4 – The emotional cost of the productivity theater&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="some-resources">Some resources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.corporate-rebels.com/">Corporate Rebels&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://digiday.com/marketing/billable-hours-are-killing-agency-creativity/">The Timesheet Tax on Creativity&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Unmeasured: Part 0 - More ain't better</title><link>https://m365princess.com/blogs/unmeasured0/</link><pubDate>Tue, 22 Apr 2025 08:41:29 +0000</pubDate><guid>https://m365princess.com/blogs/unmeasured0/</guid><description>&lt;blockquote>
&lt;p>If something grows endlessly, consumes everything around it, and destroys the host. What do we usually call it? Cancer.&lt;/p>
&lt;/blockquote>
&lt;p>It’s a stark metaphor, but an accurate one.&lt;/p>
&lt;p>We’re obsessed with more. More meetings. More output. More growth, more velocity, more performance. Behind it all sits a deeply ingrained assumption: that more is better. Always. And the faster we can get to more, the more successful we’ll be. This logic is rarely questioned. It’s embedded into our calendars, OKRs, dashboards, and even our casual conversations. Ask someone how they are, and the answer is usually “busy” - and strangely they say it like it’s a good thing. We’ve accepted the idea that nonstop activity is a sign of relevance, that a packed calendar is a mark of status, and that burnout is just collateral damage on the path to achievement.&lt;/p>
&lt;p>But we need to ask: at what cost?&lt;/p>
&lt;p>We live in a culture that measures everything. Every click, every message, every hour. In the name of efficiency, we’ve turned productivity into something mechanical—transactional: Effort becomes recognition. Speed becomes value. Hustle becomes worth. It’s a system that promises clarity, but actually delivers alienation. Karl Marx once wrote about workers being alienated, not only from the things they produce, but from the process of work, from their fellow workers, and ultimately from themselves. In modern knowledge work, this is not theory, it’s everyday reality.&lt;/p>
&lt;p>You finish the sprint, but don’t know who used the feature. You send the report, but don’t see the decision it informed. You sit in the meeting, speak your piece, and although you talk to people all day long, you are disconnected from any sense of shared purpose.&lt;/p>
&lt;p>This isn’t just inefficient. It’s dehumanizing.&lt;/p>
&lt;p>&lt;img alt="draw 25 meme" src="https://m365princess.com/images/draw25.png">&lt;/p>
&lt;h2 id="when-rest-becomes-resistance">When rest becomes resistance&lt;/h2>
&lt;p>Against this backdrop, simply resting (truly resting) is radical:&lt;/p>
&lt;p>Daydreaming. Lying in bed without reaching for your phone. Taking a walk with no productivity podcast in your ears. Staring out the window and letting your mind wander. In these moments, you are doing something profound: you are refusing to be productive in the way capitalism defines it. You are not producing. You are not consuming. You are not making someone else richer. You are simply existing, on your own terms.&lt;/p>
&lt;p>And in a system designed to extract, track, and monetize every moment of your time and attention, that is an act of quiet rebellion. (and if you read my blog regularly, you know how much I like a lil rebellion)&lt;/p>
&lt;h2 id="rest-is-not-a-reward">Rest is not a reward&lt;/h2>
&lt;p>One of the most dangerous ideas we’ve internalized is that rest must be earned. Once the quarter ends. Once the deadline is met. Once the burnout becomes undeniable. But the truth is this: rest is not a luxury. It is not a treat. It is not something that comes &lt;em>after&lt;/em> you’ve done the work. Rest is a &lt;strong>prerequisite&lt;/strong> for clarity, resilience, and creativity. It’s the foundation, not the bonus. And yet our systems, our tools, and our culture often fail to account for this. They treat stillness as waste, silence as emptiness, and reflection as optional. This to me is a narrow and harmful view of what it means to be productive.&lt;/p>
&lt;h2 id="so-what-are-we-actually-optimizing-for">So what are we actually optimizing for?&lt;/h2>
&lt;p>We build dashboards and define KPIs. We measure tickets closed, lines of code written, emails answered. But rarely do we step back and ask: what’s the point?&lt;/p>
&lt;ul>
&lt;li>Are we trying to maximize output or create lasting value?&lt;/li>
&lt;li>Are we building faster teams or better ones?&lt;/li>
&lt;li>Are we rewarding movement or meaningful progress?&lt;/li>
&lt;/ul>
&lt;p>Until we ask these questions, we’ll keep designing work environments that push people to perform but leave them detached from purpose. We’ll keep treating exhaustion as excellence. We’ll keep mistaking motion for meaning.&lt;/p>
&lt;h2 id="a-more-holistic-perspective">A more holistic perspective&lt;/h2>
&lt;p>This series is not about how to squeeze more out of people. It’s about stepping back and rethinking what productivity even means, especially now, in a world where AI tools like ChatGPT and Copilot are automating the visible, repeatable parts of knowledge work. We’re entering a new phase of work, one that demands a more holistic view of performance. One that honors strategy as much as speed, reflection as much as results, and &lt;em>people&lt;/em> as more than inputs in a machine.&lt;/p>
&lt;p>In the next post, we’ll explore how we ended up measuring the wrong things, and how we can begin to shift the focus back to outcomes, purpose, and impact.&lt;/p>
&lt;p>But for now, let’s sit with this:&lt;/p>
&lt;ul>
&lt;li>A full calendar is not a fulfilled life&lt;/li>
&lt;li>Six figures is not a guarantee of happiness (unless they are the ones of the thumbnail of this post 😇)&lt;/li>
&lt;li>And sometimes, the most productive thing you can do is absolutely nothing&lt;/li>
&lt;/ul>
&lt;p>What do you think?&lt;/p>
&lt;h2 id="some-resources">Some resources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.thecut.com/2022/10/rest-is-resistance-manifesto-nap-ministry-book-excerpt.html">Rest is resistance&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.researchgate.net/publication/235304790_An_exploration_of_factors_predicting_work_alienation_of_knowledge_workers">An exploration of factors predicting work alienation of knowledge workers&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://viktorijadamcevska.medium.com/the-poisonous-antidote-to-success-the-hustle-culture-fb568870c582">The poisonous antidote to success — the hustle culture&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Isolation as a business model</title><link>https://m365princess.com/blogs/isolation/</link><pubDate>Mon, 14 Apr 2025 08:41:29 +0000</pubDate><guid>https://m365princess.com/blogs/isolation/</guid><description>&lt;p>Something strange is happening in how we live our lives. Helping a friend get to the airport, picking up groceries for an elderly neighbor, sharing a power drill with someone down the hall; these used to be everyday things. Now they feel rare. Not because people have changed, but because the system around us has.&lt;/p>
&lt;p>When a friend drives you, Uber makes no money. When you help your neighbor, delivery apps lose a transaction. If four people share one drill, that’s three fewer sales. Every time we act together, someone loses a profit opportunity. And today’s economy is designed to make sure that doesn’t happen too often.&lt;/p>
&lt;p>What we’re seeing isn’t just a shift in habits. It’s the result of decades of intentional design. A system that works best when we’re not working together.&lt;/p>
&lt;h2 id="the-myth-of-the-special-individual">The myth of the special individual&lt;/h2>
&lt;p>We’re constantly told that we are unique. And I know, I get it, we ARE!. But we are not the super special snowflakes entire industries like to tell us. That our preferences, needs, and routines deserve to be fully personalized. Every product and service is made just for you. Your news feed. Your fitness plan. Your therapy app. Your meal delivery.&lt;/p>
&lt;p>It sounds empowering, but there’s something else going on. When everything is tailored to you, nothing is shared. No common experience. No shared reference points. No reason to talk to your neighbor about what you both saw, felt, or learned, because you each saw something different.&lt;/p>
&lt;p>This isn’t a coincidence. It’s part of a larger strategy. If you think your problems are personal, you’re less likely to see the system behind them. If everything is about your individual needs, you stop looking for collective answers. And if you’re always focused on improving yourself, it’s hard to see how deeply connected your struggles are to everyone else’s.&lt;/p>
&lt;p>Companies aren’t just selling you things. They’re collecting your behavior, predicting your actions, and shaping your decisions. Not to help you, but to turn your life into a set of profitable patterns.&lt;/p>
&lt;h2 id="enter-ai">Enter AI&lt;/h2>
&lt;p>Now AI is taking this even further. AI doesn’t just automate tasks. It automates connection. It gives you advice, teaches you new skills, and sometimes even talks to you like a friend. It never gets tired. It never asks for anything. It makes everything feel easier.&lt;/p>
&lt;p>But in doing so, it slowly makes other people feel unnecessary. Why ask for help when an algorithm can give you what you want? Why deal with the mess of human conversation when you can talk to something that already understands you?&lt;/p>
&lt;p>It sounds harmless. But when you step back, you realize what’s happening. AI isn’t just replacing jobs. It’s replacing relationships. It’s replacing the reasons we need each other. The more we turn to AI for emotional and social support, the more we forget how to do that with other people.&lt;/p>
&lt;p>At the same time, these systems are far from neutral. AI systems tend to reinforce the same old inequalities. They rely on data from biased systems, and they’re built on the backs of underpaid and invisible workers who label, clean, and process the data. It’s exploitation hidden behind shiny interfaces.&lt;/p>
&lt;p>&lt;img alt="two buttons meme: Convenient AI-generated everything vs Messy but real human connection" src="https://m365princess.com/images/twobuttons.png">&lt;/p>
&lt;h2 id="convenience-as-control">Convenience as control&lt;/h2>
&lt;p>What’s striking is that none of this feels forced. No one is making you give up on community. You’re just tired. Busy. Looking for something that works. And the apps are there, ready to help. Personalized. Frictionless. Always on.&lt;/p>
&lt;p>This is control through convenience. You’re not being locked up. You’re being nudged. Just slightly. Away from others. Toward the app. Toward the subscription. Toward the idea that being self-sufficient is the goal.&lt;/p>
&lt;blockquote>
&lt;p>“I have become comfortably numb.”
— Pink Floyd&lt;/p>
&lt;/blockquote>
&lt;p>We don’t resist it because it doesn’t hurt. It feels easy. Safe. Efficient. But that numbness, that dull sense of something missing, isn&amp;rsquo;t a bug. It’s the point.&lt;/p>
&lt;p>It&amp;rsquo;s a quiet coup. Not a political one, but a psychological one. Where the tools that promise freedom slowly take away the things that made us human: interdependence, trust, shared experience.&lt;/p>
&lt;h2 id="what-were-losing">What we’re losing&lt;/h2>
&lt;p>And with that, something deeper disappears. When we no longer share the same experiences, we lose collective memory. And without memory, it’s harder to notice patterns. To see injustice. To organize against it. To say, “This has happened before, and here’s how we responded.”&lt;/p>
&lt;p>One of my favorite authors, Haruki Murakami, once said, “History is collective memories.” And if we let those go, we lose not just our stories, but our ability to resist.&lt;/p>
&lt;h2 id="what-we-can-reclaim">What we can reclaim&lt;/h2>
&lt;p>The solution isn’t to reject technology. It’s to rebuild what it has quietly dismantled. Community. Mutual aid. Shared tools. Public spaces. Trust.&lt;/p>
&lt;p>We have to make the choice to be less efficient sometimes. To ask each other for help. To slow down. To remember that not everything that scales is good. And not everything that’s frictionless is freedom. Because the more we act together, the harder we are to control. And that’s what makes solidarity so powerful. And so dangerous to a system that profits from our separation.&lt;/p></description></item><item><title>How to Build a List Formatting Web Part with all the cool samples from the Universal Sample Gallery</title><link>https://m365princess.com/blogs/listformatting-webpart/</link><pubDate>Thu, 20 Mar 2025 10:41:29 +0000</pubDate><guid>https://m365princess.com/blogs/listformatting-webpart/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>If you&amp;rsquo;re a little bit like me and love working with SharePoint, you know how powerful it can be to format lists to make data more readable and visually appealing. But let&amp;rsquo;s be honest, the process of finding and applying column formatting samples can be a bit tedious. That&amp;rsquo;s why I decided to create an SPFx Web Part that simplifies this process. In this blog post, I&amp;rsquo;ll walk you through how I built this web part using React, PnP.js, and Octokit.&lt;/p>
&lt;h2 id="the-usual-process">The Usual Process&lt;/h2>
&lt;p>Before I show you my the solution, let&amp;rsquo;s talk about the usual process for applying column formatting:&lt;/p>
&lt;ol>
&lt;li>Find the &lt;a href="https://adoption.microsoft.com/en-us/sample-solution-gallery/">Sample Solution Gallery&lt;/a> or the &lt;a href="https://github.com/pnp/List-Formatting">List Formatting GitHub repo&lt;/a>: This is where you can find a gazillion of formatting samples contributed by the community&lt;/li>
&lt;li>Find a sample that is applicable to your column type: Not all samples work for all column types, so you need to find the right one
Copy the code: Once you find the right sample, you copy the JSON code&lt;/li>
&lt;li>Go back to your library: Navigate back to your SharePoint library&lt;/li>
&lt;li>Paste the code: Finally, you paste the JSON code into the column formatting settings&lt;/li>
&lt;/ol>
&lt;h2 id="my-idea">My Idea&lt;/h2>
&lt;p>I thought, &lt;em>Wouldn&amp;rsquo;t it be great if we could choose from all the cool column formatting samples and apply them directly to a column without all the back-and-forth?&lt;/em> So, I built a web part that integrates these samples seamlessly into SharePoint, where the work actually happens. This improves the user experience by making it easier and faster to apply column formatting.&lt;/p>
&lt;p>To not make you scroll down, how this would look like: This is your sneak peek:&lt;/p>
&lt;p>&lt;img alt="webpart" src="https://m365princess.com/images/listformatting-webpart-samples.png">&lt;/p>
&lt;h2 id="project-structure">Project Structure&lt;/h2>
&lt;p>Here&amp;rsquo;s a mermaid diagram that illustrates the architecture of the web part:&lt;/p>
&lt;p>&lt;img alt="mermaid diagram of the Web Part" src="https://m365princess.com/images/mermaid-webpart.png">&lt;/p>
&lt;h3 id="explanation">Explanation&lt;/h3>
&lt;ul>
&lt;li>ListformattingWebpart: The main component that ties everything together&lt;/li>
&lt;li>SiteSelector: Allows users to select a SharePoint site&lt;/li>
&lt;li>ListSelector: Allows users to select a list from the selected site&lt;/li>
&lt;li>ColumnSelector: Allows users to select a column from the selected list&lt;/li>
&lt;li>ColumnTypeDisplay: Displays the type of the selected column&lt;/li>
&lt;li>SampleGallery: Displays a gallery of formatting samples&lt;/li>
&lt;li>SampleModal: Shows a preview of the selected sample&lt;/li>
&lt;li>ApplyButton: Allows users to apply the selected column format&lt;/li>
&lt;li>FeedbackForm: Allows users to submit feedback on the samples&lt;/li>
&lt;/ul>
&lt;p>Each component uses specific hooks to fetch data and manage state. For example, &lt;code>useFetchSites&lt;/code> fetches the list of sites, &lt;code>useFetchLists&lt;/code> fetches the lists, and so on. Using hooks makes my code modular, reusable, and easier to maintain. (Also I love that the individual files don&amp;rsquo;t get to long this way)&lt;/p>
&lt;h2 id="how-to-build-this">How to build this&lt;/h2>
&lt;h3 id="selecting-the-site-list-and-column">Selecting the Site, List, and Column&lt;/h3>
&lt;h4 id="fetching-sites">Fetching Sites&lt;/h4>
&lt;p>We start by allowing users to select a SharePoint site. We use the &lt;code>useFetchSites&lt;/code> hook to fetch the list of sites using the SharePoint Search API. Here&amp;rsquo;s how we integrate it into our SiteSelector component:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useFetchSites&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./useFetchSites&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Dropdown&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">MessageBar&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;@fluentui/react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">strings&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;ListformattingWebpartWebPartStrings&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">WebPartContext&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;@microsoft/sp-webpart-base&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">SiteSelectorProps&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">WebPartContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">onSiteChange&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">siteUrl&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">void&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">className?&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SiteSelector&lt;/span>: &lt;span class="kt">React.FC&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">SiteSelectorProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">onSiteChange&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">className&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">sites&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">messageType&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useFetchSites&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">className&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">message&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBar&lt;/span> &lt;span class="na">messageBarType&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">messageType&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">MessageBar&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Dropdown&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SelectSite&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">options&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">sites&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">label&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Sites&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onChange&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">option&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">onSiteChange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">option&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">key&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">SiteSelector&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="fetching-lists">Fetching Lists&lt;/h4>
&lt;p>Once a site is selected, we fetch the lists within that site using the &lt;code>useFetchLists&lt;/code> hook. This hook uses the &lt;code>sp.web.lists()&lt;/code> method from PnP.js to retrieve the lists.&lt;/p>
&lt;h4 id="fetching-columns">Fetching Columns&lt;/h4>
&lt;p>After selecting a list, we fetch the columns using the &lt;code>useFetchFields&lt;/code> hook. This hook uses the &lt;code>sp.web.lists.getById(listId).fields()&lt;/code> method from PnP.js to retrieve the columns.&lt;/p>
&lt;h4 id="displaying-column-type">Displaying Column Type&lt;/h4>
&lt;p>We display the type of the selected column using the &lt;code>useFetchColumnType&lt;/code> hook. This hook uses the &lt;code>sp.web.lists.getById(listId).fields.getByInternalNameOrTitle(columnName)()&lt;/code> method from PnP.js to retrieve the column type.&lt;/p>
&lt;h4 id="fetching-column-formatting-samples-from-github">Fetching Column Formatting Samples from GitHub&lt;/h4>
&lt;p>We use the &lt;code>useFetchColumnFormattingSamples&lt;/code> hook to fetch column formatting samples from the GitHub repository. This hook leverages the &lt;strong>Octokit&lt;/strong> library to interact with the GitHub API.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useFetchColumnFormattingSamples&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./useFetchColumnFormattingSamples&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ...other imports...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SampleGallery&lt;/span>: &lt;span class="kt">React.FC&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">SampleGalleryProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">columnType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">includeGenericSamples&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">searchQuery&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">samples&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">messageType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">totalSamples&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useFetchColumnFormattingSamples&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">columnType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">includeGenericSamples&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">searchQuery&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">message&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBar&lt;/span> &lt;span class="na">messageBarType&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">messageType&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">MessageBar&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">samples&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sample&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">SampleCard&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">sample&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="na">sample&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">sample&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">))}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">SampleGallery&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="applying-the-selected-formatting-to-the-column">Applying the Selected Formatting to the Column&lt;/h4>
&lt;p>We use the &lt;code>useApplyColumnFormatting&lt;/code> hook to apply the selected column formatting. This hook uses the &lt;code>sp.web.lists.getById(selectedList).fields.getByInternalNameOrTitle(selectedColumn).update()&lt;/code> method from PnP.js to apply the formatting.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useApplyColumnFormatting&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./useApplyColumnFormatting&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ...other imports...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">ApplyButton&lt;/span>: &lt;span class="kt">React.FC&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ApplyButtonProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">selectedList&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedColumn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedSample&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedSite&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedListName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resetInputs&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">applyColumnFormatting&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">messageType&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useApplyColumnFormatting&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">selectedList&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedColumn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedSample&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedSite&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">selectedListName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resetInputs&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">message&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBar&lt;/span> &lt;span class="na">messageBarType&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">messageType&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">MessageBar&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">PrimaryButton&lt;/span> &lt;span class="na">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Apply&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">applyColumnFormatting&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">ApplyButton&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="recording-feedback-in-a-sharepoint-list">Recording Feedback in a SharePoint List&lt;/h4>
&lt;p>We use the &lt;code>useSaveFeedback&lt;/code> hook to save feedback submitted by users. This hook uses the &lt;code>sp.web.lists.getByTitle('Listformatting-Feedback').items.add()&lt;/code> method from PnP.js to save the feedback.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useSaveFeedback&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./useSaveFeedback&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ...other imports...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">FeedbackForm&lt;/span>: &lt;span class="kt">React.FC&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FeedbackFormProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">context&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">saveFeedback&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">feedbackSaved&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useSaveFeedback&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">handleSubmit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">sampleName&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rating&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">feedback&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">linkToSample&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">saveFeedback&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sampleName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rating&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">feedback&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">linkToSample&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">error&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBar&lt;/span> &lt;span class="na">messageBarType&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">MessageBarType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">MessageBar&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">feedbackSaved&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBar&lt;/span> &lt;span class="na">messageBarType&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">MessageBarType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">success&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FeedbackSaved&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">MessageBar&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">form&lt;/span> &lt;span class="na">onSubmit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleSubmit&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* Form fields for sample name, rating, feedback, and link */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">PrimaryButton&lt;/span> &lt;span class="na">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SubmitFeedback&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;submit&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">form&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">FeedbackForm&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In this blog post, I showed how to create a powerful SPFx Web Part that allows users to select a site, list, and column, fetch column formatting samples from GitHub, apply the selected formatting to the column, and record feedback in a SharePoint list. Next up: Applying Form formats!&lt;/p>
&lt;p>Happy coding!&lt;/p></description></item><item><title>A Manifesto for Ethical Software</title><link>https://m365princess.com/blogs/manifesto/</link><pubDate>Wed, 19 Mar 2025 07:44:05 +0000</pubDate><guid>https://m365princess.com/blogs/manifesto/</guid><description>&lt;p>&lt;img alt="Manifesto for ethical software" src="https://m365princess.com/images/manifesto.png">&lt;/p>
&lt;p>Technology shapes how we live, work, and interact. It has the power to create opportunities, but too often it reinforces inequality instead. Software should not contribute to racism, sexism, ableism, or colonial power structures. It should not automate oppression, deny access, or prioritize profit over people. The systems we build reflect the choices we make, and I refuse to create or deploy technology that harms marginalized communities.&lt;/p>
&lt;p>This also means looking at who gets to make decisions. When leadership lacks representation from women, BIPOC, disabled people, and other marginalized groups, technology is shaped by a narrow perspective. That is not just a missed opportunity for innovation, but a direct cause of harm. Diverse leadership leads to better, more inclusive products. Without it, bias becomes embedded in the foundations of our systems. If a project does not reflect that reality, I will not be part of it.&lt;/p>
&lt;p>Ethical responsibility does not stop at representation. Every developer has the right to know how their work will be used and to walk away from projects that put people at risk. AI trained on biased datasets, surveillance tools that target vulnerable communities, and software that collects data without informed consent all contribute to harm. These are not just technical challenges, but ethical failures. Transparency, accountability, and the ability to say no are essential in building software that serves rather than exploits.&lt;/p>
&lt;p>Too often, the industry moves at a pace that ignores these responsibilities. Prioritizing speed and profit over care and safety has consequences. When accessibility is treated as an afterthought, people are excluded. When systems are tested only on a narrow group of users, they fail to serve the wider world. When documentation is written in a way that only a small group can understand, barriers are created instead of solutions. Ethical software requires slowing down, questioning assumptions, and designing for real people from the start. Testing must include diverse user groups, and documentation should be clear, inclusive, and available in multiple formats.&lt;/p>
&lt;p>Privacy should not be a privilege for those with the resources to protect it. It should apply equally to everyone, without exception. Software should not rely on deceptive practices to extract user data, and consent should mean more than a checkbox hidden in fine print. People deserve to know what they are agreeing to in language they can understand.&lt;/p>
&lt;p>Building technology is an ongoing responsibility. It is not enough to avoid harm. It is necessary to actively examine systems for bias, listen to those affected, and change course when needed. No software is neutral, and every project involves choices that impact real lives. Ethical development means taking those choices seriously, challenging the status quo, and refusing to accept systems that reinforce injustice.&lt;/p>
&lt;p>Technology should serve people, not exploit them. It should empower rather than exclude. Every decision in software development shapes the future, and I choose to build a future that is fair, responsible, and inclusive. This is, what my claim means:&lt;/p>
&lt;blockquote>
&lt;p>Changing the world one app at a time.&lt;/p>
&lt;/blockquote></description></item><item><title>Why the infamous 'Lets make a list of use cases' is THE way to kill AI innovation</title><link>https://m365princess.com/blogs/usecases/</link><pubDate>Sat, 15 Mar 2025 15:12:35 +0000</pubDate><guid>https://m365princess.com/blogs/usecases/</guid><description>&lt;p>AI is here. Everyone knows it. Every leadership team wants a piece of the action. And so, the first instinct? &amp;ldquo;Let’s make a list of use cases!&amp;rdquo; Sounds logical, right? Compile a list, prioritize, evaluate, and eventually pilot the best ones. In theory. Because in practice, this approach rarely leads to anything meaningful. Instead, it becomes a corporate graveyard for AI ideas.&lt;/p>
&lt;h2 id="the-illusion-of-progress">The illusion of progress&lt;/h2>
&lt;p>On the surface, making a list of AI use cases feels productive. It creates the illusion of momentum. Meetings are scheduled, brainstorming sessions are held, and a massive spreadsheet is filled with potential AI applications. But then… nothing. The energy fizzles out, and AI remains a PowerPoint slide rather than a business reality.&lt;/p>
&lt;h2 id="so-whats-the-problem">So what&amp;rsquo;s the problem?&lt;/h2>
&lt;p>Glad you asked. So many!&lt;/p>
&lt;h3 id="1-the-black-hole-of-stakeholder-alignment">1. The black hole of stakeholder alignment&lt;/h3>
&lt;p>AI use cases often require input from multiple departments: IT, business units, compliance, legal, finance, operations. And if you are in Germany like I am: involvement of the workers council, the data protection officer, a union, and several more representative bodies. Getting all these stakeholders to the table is an exercise in herding cats. When they do finally meet, discussions stall because:&lt;/p>
&lt;ul>
&lt;li>Some people don’t understand the technology well enough to make an informed decision&lt;/li>
&lt;li>Others overcomplicate things, insisting on massive, transformative AI projects instead of quick wins&lt;/li>
&lt;li>No one knows who actually &amp;ldquo;owns&amp;rdquo; the initiative&lt;/li>
&lt;/ul>
&lt;p>Result? Endless debates and no decisions.&lt;/p>
&lt;h3 id="2-paralysis-by-analysis">2. Paralysis by analysis&lt;/h3>
&lt;p>Once the list is created, the next step is &lt;em>evaluation&lt;/em>. This means assigning scores, defining impact metrics, and analyzing feasibility. But AI is complex. Many factors, like data availability, model accuracy, regulatory concerns, are unpredictable. Decision-makers, unfamiliar with AI (or only equipped with very superficial knowledge gained from private ChatGPT usage), struggle to evaluate viability.&lt;/p>
&lt;p>So they delay. And delay. And delay. (Or have more meetings. Or both.)&lt;/p>
&lt;p>Meanwhile, competitors who skipped the endless evaluation phase have already implemented something, learned from it, and moved forward.&lt;/p>
&lt;p>&lt;img alt="paralysis by analysis cartoon" src="https://m365princess.com/images/analysis-paralysis.jpg">&lt;/p>
&lt;h3 id="3-low-cloud-maturity-flying-blind">3. Low Cloud maturity: flying blind&lt;/h3>
&lt;p>One of the biggest, yet least discussed, blockers to AI success is low cloud maturity. Many organizations don’t have the foundational systems in place to even see where AI could be useful. They are essentially flying blind.&lt;/p>
&lt;p>Take customer service as an example. In many companies, customer inquiries land in shared mailboxes, where service reps do their best to process them manually. But because these emails remain locked inside Outlook instead of being routed into a CRM or case management system, there’s no way to measure key metrics like:&lt;/p>
&lt;ul>
&lt;li>When a ticket is opened&lt;/li>
&lt;li>When it is resolved&lt;/li>
&lt;li>What category of inquiry it falls under&lt;/li>
&lt;li>How much back-and-forth is required to resolve it&lt;/li>
&lt;/ul>
&lt;p>Without this basic visibility, organizations can’t even begin to evaluate which categories of inquiries take the longest or where AI could automate processes. Instead of fixing this foundational issue by integrating emails into structured workflows (something that requires cloud maturity and organizational buy-in) companies stay in the dark, making AI decisions based on assumptions rather than data. And so, the &amp;ldquo;use case list&amp;rdquo; exercise becomes meaningless.&lt;/p>
&lt;h3 id="4-skipping-the-homework-ai-needs-a-strong-foundation">4. Skipping the homework: AI needs a strong foundation&lt;/h3>
&lt;p>Before AI can deliver value, organizations need to do their homework. Many companies want AI to solve their problems but haven’t built the necessary infrastructure to support it. This includes:&lt;/p>
&lt;ul>
&lt;li>Cloud adoption - Without cloud-based workflows, AI cannot efficiently integrate into business processes&lt;/li>
&lt;li>Governance frameworks - AI initiatives need clear rules on data access, compliance, and security&lt;/li>
&lt;li>Data readiness - AI is only as good as the data it processes. If data is siloed, messy, or unstructured, AI will fail&lt;/li>
&lt;li>Change management and adoption - Employees need training to understand and trust AI solutions, rather than resisting automation&lt;/li>
&lt;/ul>
&lt;p>Skipping these steps means organizations are trying to apply AI to broken systems. This will lead to inefficiencies, frustration, and failed projects&lt;/p>
&lt;blockquote>
&lt;p>Ask me how I know!&lt;/p>
&lt;/blockquote>
&lt;h3 id="5-overcomplexity-kills-buy-in">5. Overcomplexity kills buy-in&lt;/h3>
&lt;p>Most organizations, when creating AI use case lists, aim high. They propose game-changing, business-critical, high-impact AI applications:&lt;/p>
&lt;ul>
&lt;li>AI-powered supply chain optimization for global operations&lt;/li>
&lt;li>Predictive maintenance across thousands of assets&lt;/li>
&lt;li>Fully automated customer support with zero human intervention&lt;/li>
&lt;/ul>
&lt;p>Sounds great. But these require major investment, long implementation timelines, and buy-in from too many decision-makers. Getting approval for these is like pushing a huge boulder uphill.&lt;/p>
&lt;p>Meanwhile, simpler, practical AI applications like document automation, intelligent search, email triage are overlooked because they don&amp;rsquo;t sound as exciting in a strategy meeting. Ironically, these are the projects that could actually deliver quick ROI and build momentum for larger AI initiatives.&lt;/p>
&lt;h3 id="6-users-dont-know-what-they-need">6. Users don&amp;rsquo;t know what they need&lt;/h3>
&lt;p>Business users and decision-makers often struggle to articulate their AI needs because they don’t know what’s possible. They don&amp;rsquo;t understand the nuances of AI capabilities, nor do they have a clear picture of how different technologies (NLP, computer vision, machine learning) work together.&lt;/p>
&lt;p>As a result, many use cases are either:&lt;/p>
&lt;ul>
&lt;li>Overly generic (&amp;ldquo;Let’s use AI for customer service&amp;rdquo;)&lt;/li>
&lt;li>Technically unrealistic (&amp;ldquo;Can AI predict exactly when each customer will churn?&amp;rdquo;)&lt;/li>
&lt;li>Too vague to act on (&amp;ldquo;We need AI to improve efficiency&amp;rdquo;)&lt;/li>
&lt;/ul>
&lt;p>This leads to further delays as teams struggle to turn these ideas into actionable projects.&lt;/p>
&lt;h2 id="a-better-approach-think-small-move-fast">A better approach: Think small, move fast&lt;/h2>
&lt;p>Instead of making long lists, organizations should focus on rapid experimentation:&lt;/p>
&lt;ol>
&lt;li>Skip the big list and start small - Pick one simple, tangible use case that solves a specific, well-defined problem&lt;/li>
&lt;li>Build a quick Proof of Concept (PoC) - Instead of overanalyzing, test something small within weeks (or even days), not months&lt;/li>
&lt;li>Measure impact fast - Focus on metrics that prove immediate value (e.g., time savings, cost reduction, accuracy improvements)&lt;/li>
&lt;li>Expand based on learnings - Use insights from small wins to refine and scale AI initiatives&lt;/li>
&lt;/ol>
&lt;h2 id="the-reality-execution-beats-ideation">The reality: execution beats ideation&lt;/h2>
&lt;p>Most companies don’t fail at AI because of a lack of ideas. They fail because they get stuck in analysis mode. The longer they debate use cases, the further behind they fall. AI rewards action, not committee-driven wish lists.&lt;/p>
&lt;p>Companies who fail to understand this, always remind me a bit of a cartoon I used to watch when I was way younger&amp;hellip; These orgs try to &amp;ldquo;take over the world&amp;rdquo;, and for &lt;em>some&lt;/em> reasons (see above) these nefarious world domination plans fail.&lt;/p>
&lt;p>&lt;img alt="Pinky and the brain" src="https://m365princess.com/images/pinky.jpg">&lt;/p>
&lt;p>The question isn&amp;rsquo;t &amp;ldquo;What are all the ways we could use AI?&amp;rdquo; but &amp;ldquo;What’s one thing we can implement next month that delivers value?&amp;rdquo;&lt;/p>
&lt;p>&lt;em>Narf.&lt;/em>&lt;/p></description></item><item><title>What are we doing? I mean apart from back-to-back meetings and emails...</title><link>https://m365princess.com/blogs/work/</link><pubDate>Mon, 10 Mar 2025 08:40:45 +0000</pubDate><guid>https://m365princess.com/blogs/work/</guid><description>&lt;h2 id="your-job-title-sounds-impressive-but-what-do-you-actually-do-all-day">Your job title sounds impressive, but what do you actually do all day?&lt;/h2>
&lt;p>Most knowledge workers have impressive job titles. &lt;em>Strategy Lead. Innovation Manager. Digital Transformation Consultant.&lt;/em>&lt;/p>
&lt;p>Sounds impactful, right? But what do these people actually &lt;em>do&lt;/em> all day?&lt;/p>
&lt;ul>
&lt;li>Sit in endless meetings&lt;/li>
&lt;li>Drown in emails&lt;/li>
&lt;li>Chase approvals&lt;/li>
&lt;li>Keep up with Teams, Slack and another dozen apps that act like Tamagotchis who desperately seek our attention&lt;/li>
&lt;/ul>
&lt;p>None of this is in anyone’s job description. But somehow, this &lt;em>is&lt;/em> the job now.&lt;/p>
&lt;p>And instead of fixing the root problem, we’re throwing AI at it.&lt;/p>
&lt;ul>
&lt;li>AI to summarize meetings we probably didn’t need in the first place&lt;/li>
&lt;li>AI to transcribe calls that could’ve been an email&lt;/li>
&lt;li>AI to write emails no one really needs to read&lt;/li>
&lt;/ul>
&lt;p>We call this &amp;ldquo;progress&amp;rdquo;. But are we really winning &lt;em>anything&lt;/em>?&lt;/p>
&lt;p>We’re not solving the problem. We’re just making our inefficiencies more efficient.&lt;/p>
&lt;p>&lt;img alt="alt text" src="https://m365princess.com/images/aiwrittenairead.png">&lt;/p>
&lt;h3 id="how-do-we-fix-this">How do we fix this?&lt;/h3>
&lt;p>AI isn’t the solution, work design is&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Fewer, better meetings&lt;/p>
&lt;ul>
&lt;li>No agenda? No attenda.&lt;/li>
&lt;li>Default to &lt;em>async&lt;/em> over &lt;em>sync&lt;/em>&lt;/li>
&lt;li>Cap meetings at 20 minutes. No, for real. In case you can&amp;rsquo;t decide within that timeframe, it means that the participants aren&amp;rsquo;t prepared.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Kill the email addiction&lt;/p>
&lt;ul>
&lt;li>Reduce reply expectations&lt;/li>
&lt;li>Use structured communication (Teams threads &amp;gt; Email chaos)&lt;/li>
&lt;li>Automate low-value responses, but eliminate unnecessary emails entirely&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Measure actual outcomes, not just activity&lt;/p>
&lt;ul>
&lt;li>Focus on impact, not hours logged&lt;/li>
&lt;li>Reduce performative &amp;ldquo;busyness&amp;rdquo;&lt;/li>
&lt;li>Give teams deep work time, not just AI-powered admin tools&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Leverage AI for what actually matters&lt;/p>
&lt;ul>
&lt;li>AI should augment creative, strategic, and decision-making work—not just clean up our mess&lt;/li>
&lt;li>Use AI to &lt;em>eliminate&lt;/em> low-value tasks, not make them easier to tolerate&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>If you need an AI-powered chatbot today to find the right document, you soon need an AI-powered chatbot to find the right chatbot, to find the right document&amp;hellip;&lt;/p>
&lt;/blockquote>
&lt;p>&lt;img alt="comic strip about someone suggesting to clean up data" src="https://m365princess.com/images/fall.png">&lt;/p>
&lt;p>In short: Do your homework first before you employ Copilot. Otherwise, the results will be disappointing (but then you get the chance to blame Copilot 🤡).&lt;/p>
&lt;p>I am not saying that there are not good genAI use cases, but please stop this nonsense. We don’t need AI to be better at running in circles. We need to break the cycle entirely. Let’s stop optimizing inefficiency and start designing work that actually &lt;em>works&lt;/em>.&lt;/p>
&lt;p>Thoughts? What’s the worst example of &amp;ldquo;fake productivity&amp;rdquo; you’ve seen?&lt;/p></description></item><item><title>Productivity isn’t an assembly line: escaping the 1925 mindset with Microsoft 365 Copilot</title><link>https://m365princess.com/blogs/assembly/</link><pubDate>Sun, 19 Jan 2025 11:04:59 +0000</pubDate><guid>https://m365princess.com/blogs/assembly/</guid><description>&lt;h2 id="welcome-to-the-age-of-ai--but-lets-stop-thinking-like-henry-ford">Welcome to the age of AI – But let’s stop thinking like Henry Ford&lt;/h2>
&lt;p>For over a century, we’ve measured productivity the wrong way. Ever since Henry Ford revolutionized the assembly line, businesses have operated under the assumption that more hours worked = more output produced.&lt;/p>
&lt;p>And sure, that works if you’re assembling Model Ts.&lt;/p>
&lt;blockquote>
&lt;p>But if you’re a knowledge worker, the idea that productivity scales linearly with time spent at your desk is nonsense.&lt;/p>
&lt;/blockquote>
&lt;p>Yet here we are, in 2025, still thinking like we’re running factories&lt;/p>
&lt;ul>
&lt;li>“If AI saves time, we need fewer workers.”&lt;/li>
&lt;li>“If an employee finishes a task faster, they should take on more work.”&lt;/li>
&lt;li>“Let’s measure productivity by how many emails, reports, and meetings someone completes.”&lt;/li>
&lt;/ul>
&lt;p>💡 Spoiler alert: Knowledge work doesn’t function like an assembly line. Adding more hours doesn’t necessarily add more value. (Usually, it&amp;rsquo;s even the opposite)&lt;/p>
&lt;p>Which brings us to Microsoft 365 Copilot, and the huge misconception about its ROI.&lt;/p>
&lt;h2 id="the-factory-fallacy-why-productivity--hours-logged">The factory fallacy: Why productivity ≠ hours logged&lt;/h2>
&lt;p>In an assembly line, if a worker assembles 10 widgets per hour, they’ll assemble 80 in an 8-hour shift. Makes sense.&lt;/p>
&lt;p>But if you tell a writer, analyst, marketer, or strategist to work 10% longer, will they produce 10% better work?&lt;/p>
&lt;ul>
&lt;li>Will a lawyer’s contract be 10% sharper?&lt;/li>
&lt;li>Will a marketer’s campaign be 10% more effective?&lt;/li>
&lt;li>Will a strategist make 10% better decisions just because they sat at their desk longer?&lt;/li>
&lt;/ul>
&lt;p>Of course not.&lt;/p>
&lt;blockquote>
&lt;p>Knowledge work is non-linear. Sometimes, an insight that takes 5 minutes is worth more than a full week of busywork.&lt;/p>
&lt;/blockquote>
&lt;p>Yet, despite overwhelming evidence, we still try to measure productivity in hours, not impact.&lt;/p>
&lt;h2 id="parkinsons-law-why-busyness-is-a-lie">Parkinson’s Law: Why &amp;ldquo;busyness&amp;rdquo; is a lie&lt;/h2>
&lt;p>Have you ever noticed that work tends to expand to fill the time available? That’s &lt;a href="https://en.wikipedia.org/wiki/Parkinson%27s_law">Parkinson’s Law&lt;/a> in action:&lt;/p>
&lt;blockquote>
&lt;p>Work expands so as to fill the time available for its completion.&amp;quot;&lt;/p>
&lt;/blockquote>
&lt;p>Meaning? If you give someone two weeks to write a report, they’ll take two weeks—even if they could have done it in three days.&lt;/p>
&lt;p>The way we structure work creates artificial inefficiencies&lt;/p>
&lt;ul>
&lt;li>Endless meetings that should have been an email&lt;/li>
&lt;li>Reports filled with fluff because they “need to look substantial”&lt;/li>
&lt;li>Workers staying late to seem productive, rather than being efficient&lt;/li>
&lt;/ul>
&lt;p>💡 Copilot doesn’t just save time: it removes the bloat.&lt;/p>
&lt;h2 id="the-real-roi-of-microsoft-365-copilot-less-wasted-time-more-impact">The real ROI of Microsoft 365 Copilot: Less wasted time, more impact&lt;/h2>
&lt;p>Instead of seeing Copilot as a way to cut costs by reducing headcount, smart companies will use it to amplify human potential&lt;/p>
&lt;ol>
&lt;li>Less Repetitive Work = More Creative Thinking
&lt;ul>
&lt;li>If AI can generate a draft contract, the lawyer can focus on strategy&lt;/li>
&lt;li>If AI can summarize a meeting, the executive can focus on decisions&lt;/li>
&lt;li>If AI can clean up a dataset, the analyst can focus on insights&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>💡 Copilot doesn’t replace thinking: it enables it.&lt;br>
2. Time Saved ≠ More Time at Your Desk
Just because an employee completes a report faster doesn’t mean they should immediately get another one&lt;/p>
&lt;p>What if they used that time to&lt;/p>
&lt;ul>
&lt;li>Think critically about the company’s strategy?&lt;/li>
&lt;li>Experiment with new ideas?&lt;/li>
&lt;li>Take a step back and ensure the work actually matters?&lt;/li>
&lt;/ul>
&lt;p>💡 High-value work happens in the gaps—not just in the grind.&lt;/p>
&lt;ol start="3">
&lt;li>Fewer Meetings, Faster Decisions&lt;br>
Copilot can generate meeting summaries, analyze trends, and suggest actions—meaning&lt;/li>
&lt;/ol>
&lt;p>✅ Fewer pointless meetings&lt;br>
✅ Decisions based on real-time insights, not gut feelings&lt;br>
✅ More time spent executing, less time spent debating&lt;/p>
&lt;p>💡 Smart companies won’t fill freed-up time with more meetings. They’ll make faster, better choices.&lt;/p>
&lt;h2 id="the-fatal-roi-mistake-trying-to-measure-ai-like-an-assembly-line">The fatal ROI mistake: Trying to measure AI like an assembly line&lt;/h2>
&lt;p>Most ROI discussions around AI sound like this&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;How many minutes does it save per employee?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How many fewer workers do we need?&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;How many tasks can we automate?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>🚫 This is the wrong way to think about it.&lt;/p>
&lt;p>Real ROI isn’t just about saving time. It’s about using that time for work that actually matters&lt;/p>
&lt;p>Would you rather:&lt;/p>
&lt;ol>
&lt;li>Save 10% of your time and fill it with more low-impact work?&lt;/li>
&lt;li>Save 10% of your time and use it to create something groundbreaking?&lt;/li>
&lt;/ol>
&lt;p>Smart companies choose option 2.&lt;/p>
&lt;h2 id="final-thoughts-the-ai-revolution-is-about-impact-not-hours">Final thoughts: The AI revolution is about impact, not hours&lt;/h2>
&lt;p>Microsoft 365 Copilot isn’t here to help businesses cut headcount, it’s here to help them redefine work itself.&lt;/p>
&lt;p>The companies that thrive will be the ones that stop measuring productivity in hours and start measuring it in outcomes.&lt;/p>
&lt;p>So the real question isn’t:&lt;br>
💭 &lt;em>“How many hours can we save?”&lt;/em>&lt;/p>
&lt;p>It’s:&lt;br>
💡 &lt;em>“What will we do with the time we save?”&lt;/em>&lt;/p>
&lt;h3 id="now-its-your-turn-are-you-still-stuck-in-1925">Now It’s Your Turn: Are You Still Stuck in 1925?&lt;/h3>
&lt;ul>
&lt;li>Are you still measuring productivity like Henry Ford did 100 years ago?&lt;/li>
&lt;li>Are you using AI to reduce costs, or to increase impact?&lt;/li>
&lt;li>Are you thinking about time saved, or value created?&lt;/li>
&lt;/ul>
&lt;p>Let’s stop managing knowledge workers like factory workers. The future of work isn’t about doing more, it’s about doing better.&lt;/p>
&lt;p>🔥 What will you do with your extra time?&lt;/p></description></item><item><title>Transform the way we think about Copilot</title><link>https://m365princess.com/blogs/transform/</link><pubDate>Thu, 16 Jan 2025 11:04:59 +0000</pubDate><guid>https://m365princess.com/blogs/transform/</guid><description>&lt;p>Bumblebee has been one of my favorite characters since &lt;strong>REDACTED&lt;/strong> - this&amp;rsquo;d be too age revealing :-) Also, I LOVE building LEGO. There&amp;rsquo;s something magical about building a model that can seamlessly shift between a car and a robot. To me, this feels both playful and purposeful. The beauty of Bumblebee&amp;rsquo;s transformation is that it&amp;rsquo;s intentional and fluid, it adapts to the situation at hand rather than forcing a function where it doesn’t fit.&lt;/p>
&lt;p>&lt;img alt="Bumblebee" src="https://m365princess.com/images/lego-bumblebee.png">&lt;/p>
&lt;p>Much like how Bumblebee transforms based on necessity, we need to transform the way we approach AI projects. Instead of trying to find use cases to justify buying Microsoft 365 Copilot licenses (or really any AI powered app/solution/platform), we should be asking different questions:&lt;/p>
&lt;blockquote>
&lt;p>Where are our biggest bottlenecks? Where do we waste time, money, or lose customers?&lt;/p>
&lt;/blockquote>
&lt;p>Only after identifying these pain points we should explore how to solve them, whether through automation, process improvements, or AI (and maybe Copilot). The real transformation comes from addressing fundamental inefficiencies, not just adopting technology for the sake of it.&lt;/p>
&lt;p>Industry giants like Microsoft promote AI-driven products such as Copilot and Azure OpenAI and their compelling narratives and promises of enhanced efficiency and innovation (or should I say they just don&amp;rsquo;t stop talking about &lt;em>how everything is awesome&lt;/em> with AI?) can entice organizations to prematurely incorporate these solutions.&lt;/p>
&lt;blockquote>
&lt;p>However, this approach often leads to a scenario where businesses are retrofitting their operations to accommodate technology, rather than leveraging technology to address their unique challenges.&lt;/p>
&lt;/blockquote>
&lt;p>Read this again. Businesses try to proactively find use cases for AI, instead of exploring, evaluating, and addressing their pain points with appropriate tech (maybe AI).&lt;/p>
&lt;p>To truly benefit of the transformative (🐝) potential of AI, organizations must pivot from a solution-first mindset to a problem-centric evaluation. That sounds counterintuitive as we all believe that we need to focus on solutions, not on problems. So let me guide you through how I approach this with my customers:&lt;/p>
&lt;ul>
&lt;li>Take a step back and look at where things are getting stuck. Are employees spending too much time on repetitive tasks? Are budgets being drained in inefficient ways? Are customers frustrated with delays? Identifying these roadblocks first helps focus efforts on what actually needs fixing. Hint: If you find yourself or others saying &lt;em>We&amp;rsquo;ve always done it like this&lt;/em> you might be already on the right track to find something that is worth to explore.&lt;/li>
&lt;li>Once you know what’s slowing things down, it’s time to brainstorm solutions. Maybe it&amp;rsquo;s improving processes, automating routine tasks, or, yes, even bringing in AI where it makes sense. The key is to let the problem dictate the solution, not the other way around. Second hint: If you feel you need an AI agent to summarize all these long emails or all these long meetings for you&amp;hellip; maybe it&amp;rsquo;s time to work on corporate culture. If entire days of the majority of employees are filled with meetings and emails, something is &lt;strong>very&lt;/strong> wrong.&lt;/li>
&lt;li>Prioritize solutions that offer tangible value and align with the organization&amp;rsquo;s overarching goals. For that, it&amp;rsquo;d be beneficial to know these overarching goals, which bring me back to: &amp;ldquo;Don&amp;rsquo;t make this an IT-only project&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;p>If you now think: &amp;ldquo;&lt;em>yeah, sounds all good, but in OUR super special organization, that wouldn&amp;rsquo;t fly, we will search for AI use cases and then implement them&lt;/em>&amp;rdquo;: let me be a bit more candid here:&lt;/p>
&lt;ol>
&lt;li>Your organization is not the snowflake you think it is.&lt;/li>
&lt;li>Without a problem-driven rationale you will misallocate resources and you will need to deal with unwanted operational disruption, which will ultimately lead your AI initiative to fail to deliver the anticipated benefits.&lt;/li>
&lt;/ol>
&lt;p>The true essence of transformation, much like the LEGO Bumblebee model, lies in adaptability and purpose. Organizations can do the same, here are some ideas:&lt;/p>
&lt;ul>
&lt;li>When we encourage teams to regularly assess and question existing processes, instead of having them overworked or pseudo-solving problems by throwing more manpower on them, we foster a culture of continuous improvement.&lt;/li>
&lt;li>Get decision-makers and more stakeholders the insights and data necessary, we do not want them to navigate blindly. This also means, that you potentially need to be me open to share data in your organization&lt;/li>
&lt;li>Stay open to a range of solutions, understanding that the most effective answer may not always involve the latest technology&lt;/li>
&lt;li>Do more in terms of communication and training! Educating employees on AI use cases, governance policies, and best practices is the best investment you can make&lt;/li>
&lt;li>Everything has a lifecycle, this also applies to governance policies, which are usually based on the frequency and impact of AI use cases&lt;/li>
&lt;/ul>
&lt;p>In the end, building and transforming that Bumblebee reminded me of something important: real transformation isn’t about just grabbing the newest, shiniest tech. It’s about stepping back, understanding what actually needs fixing, and making smart, intentional changes. If we approach AI the same way, focusing on real needs instead of justifying costs, we’ll create solutions that are actually valuable and sustainable 🐝.&lt;/p></description></item><item><title>Turning Culture Into Capital: AI and the new means of production</title><link>https://m365princess.com/blogs/means/</link><pubDate>Tue, 10 Dec 2024 13:29:21 +0000</pubDate><guid>https://m365princess.com/blogs/means/</guid><description>&lt;p>I love art. I love stories, music, and all the messy, beautiful ways humans express themselves. I’m also deeply fascinated by AI: It’s part of my job to consult companies on how AI can help them.&lt;/p>
&lt;blockquote>
&lt;p>But I’m also scared.&lt;/p>
&lt;/blockquote>
&lt;p>Not because of the usual &lt;em>Terminator fever dreams&lt;/em> (I’ve &lt;a href="https://www.m365princess.com/categories/ai/">blogged about that&lt;/a>) or even the &lt;em>Will AI take my job?&lt;/em> fears. My unease comes from something deeper: the way AI intersects with late-stage capitalism and its relentless exploitation of our labor, creativity, and culture.&lt;/p>
&lt;p>We live in a world (ChatGPT would even say: &lt;em>In today&amp;rsquo;s fast-paced digital world&lt;/em>) where the gap between the wealthy and the rest grows wider by the day, and where every new technology seems to amplify that divide. AI isn’t just another tool, it’s a whole new &lt;em>means of production&lt;/em>. The raw material? Us. Our stories, our art, and our collective creativity. The exploitation isn’t subtle, and it isn’t hypothetical. It’s happening now, and it’s reshaping not just jobs but society itself.&lt;/p>
&lt;h2 id="the-raw-material-of-ai-us">The Raw Material of AI: Us&lt;/h2>
&lt;p>AI doesn’t create from nothing. It generates text, art, music, and more because it’s trained on the cumulative output of humanity. Think about it: every book ever written, every photo uploaded, every Reddit comment, every meme: This is the data fed into AI systems. It’s a vast, unpaid labor pool of billions of people.&lt;/p>
&lt;p>Take OpenAI or Mistral as examples. Their models are undeniably groundbreaking, but their success rests on data created by countless others. None of us signed up to have our posts, blogs, or digital creations mined to train these systems. And yet, the product that emerges is locked behind subscription paywalls, sold to us as something revolutionary. It is revolutionary, but it’s also exploitative. AI companies have essentially privatized the collective knowledge of humanity, transforming a shared resource into a commodity.&lt;/p>
&lt;p>This mirrors the logic of capitalism: take what belongs to everyone, refine it, and sell it back at a profit. It’s the digital equivalent of the 18th-century enclosure movement, where common lands were fenced off for private gain.&lt;/p>
&lt;h2 id="cultural-commodification-in-late-stage-capitalism">Cultural Commodification in Late-Stage Capitalism&lt;/h2>
&lt;p>In the Marxist sense, AI perfectly embodies the concept of alienation. Creators, whether artists, writers, or coders, are divorced from the value of their labor. When your artwork is scraped from the internet to train a machine, you have no say in how it’s used or profited from. The product (an AI model) may replicate your style or generate content that competes with your work, but you see none of the profits.&lt;/p>
&lt;p>AI has turned culture into a resource to be extracted. Just as capitalism once transformed raw materials like timber or coal into commodities, late-stage capitalism has done the same with creativity. The result? Tools like ChatGPT or MidJourney, which sell back the fruits of our collective cultural labor while further concentrating wealth and power in the hands of a few corporations.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;ul>
&lt;li>Platforms like DeviantArt have seen AI tools mimic specific artists’ styles without their consent, flooding the market with cheap imitations&lt;/li>
&lt;li>AI models can now create music indistinguishable from that of established artists, undermining musicians who rely on their unique sound to earn a living&lt;/li>
&lt;li>Freelancers and content creators face growing competition from AI-generated articles that are faster and cheaper to produce, even if they rely on those same writers’ past works for training data&lt;/li>
&lt;/ul>
&lt;p>The exploitation isn’t limited to individuals: It’s systemic. AI tools are the perfect representation of this era: extracting maximum value from shared resources, privatizing the benefits, and leaving creators behind.&lt;/p>
&lt;h2 id="reclaiming-the-means-of-production">Reclaiming the Means of Production&lt;/h2>
&lt;p>In Marxist terms, the &lt;em>means of production&lt;/em> refers to the tools and resources used to create value. In the industrial age, it was factories and machinery. In the AI era, the means of production are data, algorithms, and computational power. But unlike factories, the raw material for AI (the data) comes from the collective work of millions of people who never consented to its use.&lt;/p>
&lt;p>This exploitation mirrors the capitalist dynamic of surplus value. The AI companies profit enormously, but the creators of the training data, whether knowingly or unknowingly contributing, receive nothing. It’s the same story told across centuries: labor (or creativity) is exploited to generate wealth for someone else.&lt;/p>
&lt;p>But what’s truly at stake here is more than individual livelihoods. It’s culture itself. When the commons (the shared pool of human knowledge and creativity) is turned into a product, we lose something fundamental. Art and knowledge, once accessible to all, become luxuries reserved for those who can afford the subscription fees.&lt;/p>
&lt;h2 id="imagining-a-better-future">Imagining a Better Future&lt;/h2>
&lt;p>This doesn’t have to be our reality. There are principles that offer a lens to imagine alternatives: systems where the benefits of AI are distributed equitably and where creators retain ownership of their labor. Here are some ways we could start reclaiming the means of production:&lt;/p>
&lt;ol>
&lt;li>Supporting open-source projects ensures AI remains accessible to everyone, not just those who can pay&lt;/li>
&lt;li>Implement frameworks to compensate individuals whose work is used in AI training datasets, much like royalties in the music industry&lt;/li>
&lt;li>Treat AI like a public utility. Publicly funded models could democratize access while preventing corporate monopolies.&lt;/li>
&lt;li>Require companies to disclose their data sources and allow creators to opt out of having their work used for AI training&lt;/li>
&lt;/ol>
&lt;p>The AI revolution doesn’t have to mimic the inequalities of the industrial revolution. With collective action and a focus on equity, we can steer this technology toward a future where it serves humanity rather than exploiting it.&lt;/p></description></item><item><title>How to create a wordclock with Python</title><link>https://m365princess.com/blogs/wordclock/</link><pubDate>Tue, 19 Nov 2024 07:22:50 +0000</pubDate><guid>https://m365princess.com/blogs/wordclock/</guid><description>&lt;h1 id="how-i-built-a-word-clock-that-tells-time-in-three-zones">How I built a word clock that tells time in three zones&lt;/h1>
&lt;p>Some projects are born out of necessity; others, out of sheer &amp;ldquo;I never did this before so I guess it will turn out great&amp;rdquo;-attitude. This project firmly falls into the latter category. Inspired by the concept of word clocks (thanks &lt;a href="https://bsky.app/profile/wldk.nl">Waldek Mastykarz&lt;/a>) and motivated by the challenge of tackling time zones, I wanted to create a Python-powered &lt;strong>word clock&lt;/strong> that tells time in phrases across three zones: Pacific Time (PT), Greenwich Mean Time (GMT), and Indian Standard Time (IST).&lt;/p>
&lt;p>&lt;img alt="word clock" src="https://m365princess.com/images/wordclock.png">&lt;/p>
&lt;h2 id="how-i-approached-it">How I approached it&lt;/h2>
&lt;p>When I&amp;rsquo;m tackling something new, it helps to divide and conquer. Here&amp;rsquo;s how I broke the problem down into smaller steps:&lt;/p>
&lt;h3 id="displaying-time-in-words">Displaying time in words&lt;/h3>
&lt;p>The first step was figuring out how to translate digital time into phrases like &amp;ldquo;It&amp;rsquo;s a quarter past six.&amp;rdquo; I created rules based on the minute ranges:&lt;/p>
&lt;ul>
&lt;li>If the minute is between &lt;code>0&lt;/code> and &lt;code>5&lt;/code>, show &amp;ldquo;It&amp;rsquo;s [hour] o&amp;rsquo;clock.&amp;rdquo;&lt;/li>
&lt;li>If the minute is between &lt;code>15&lt;/code> and &lt;code>20&lt;/code>, show &amp;ldquo;It&amp;rsquo;s a quarter past [hour].&amp;rdquo;&lt;/li>
&lt;li>If the minute is between &lt;code>35&lt;/code> and &lt;code>40&lt;/code>, show &amp;ldquo;It&amp;rsquo;s twenty-five to [next hour].&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>I stored these rules in a function called &lt;code>get_highlighted_phrases&lt;/code>, which dynamically selects phrases to highlight based on the current time.&lt;/p>
&lt;h3 id="building-the-clock-gui">Building the clock GUI&lt;/h3>
&lt;p>Once I had the logic for the words, I needed a way to display them. I used &lt;strong>Tkinter&lt;/strong> to build a graphical interface where the phrases are arranged in a circle, mimicking the feel of a classic clock. Each time zone has its own circle with phrases like &amp;ldquo;It&amp;rsquo;s&amp;rdquo; and &amp;ldquo;half past&amp;rdquo; positioned radially.&lt;/p>
&lt;h3 id="handling-time-zones">Handling time zones&lt;/h3>
&lt;p>Here&amp;rsquo;s where things got tricky. Time zones aren&amp;rsquo;t just about adding or subtracting hours. Some, like Indian Standard Time (IST), involve half-hour offsets. To handle this, I used the &lt;code>pytz&lt;/code> library for PT and GMT but calculated IST manually using:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_ist_from_gmt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">gmt_time&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">gmt_time&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">timedelta&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hours&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">minutes&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="ampm-quirks">AM/PM quirks&lt;/h3>
&lt;p>One of the most unexpected challenges was ensuring the AM/PM indicator displayed correctly for IST, especially during transitions like midnight. I ended up flipping AM/PM specifically for IST using a conditional flag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_am_pm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hour_24&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">invert&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">invert&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;AM&amp;#34;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">hour_24&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">12&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;PM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hour_24&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;AM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">hour_24&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">12&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;AM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">hour_24&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">12&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;PM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;PM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="debug-tricks">Debug tricks&lt;/h2>
&lt;p>Testing multiple time zones in real time meant countless &lt;code>print()&lt;/code> statements, which eventually morphed into a helpful debugging function:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">debug_current_times&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pt_time&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">gmt_time&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ist_time&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Debugging Current Times:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;PT: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pt_time&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;GMT: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">gmt_time&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;IST: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">format_time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ist_time&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">invert_am_pm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;-&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="where-you-can-find-the-code">Where you can find the code&lt;/h2>
&lt;p>If you&amp;rsquo;re curious about how it all came together, or you want to try it yourself, check out the code at &lt;a href="https://github.com/LuiseFreese/wordclock">https://github.com/LuiseFreese/wordclock&lt;/a>&lt;/p></description></item><item><title>Introducing Bluesky Terminal Poster</title><link>https://m365princess.com/blogs/bluesky-terminal-poster/</link><pubDate>Mon, 11 Nov 2024 10:00:32 +0000</pubDate><guid>https://m365princess.com/blogs/bluesky-terminal-poster/</guid><description>&lt;h2 id="hello-world-bluesky">Hello World, Bluesky&lt;/h2>
&lt;p>Took me a while to fully adopt Bluesky, but here we are. One of the first things I try to do when adopting a service as a user, is also to have a peak the services API to understand what going on under the hood. So while I had a nice cuppa tea, I read in the &lt;a href="https://docs.bsky.app/docs/get-started">Bluesky API documentation&lt;/a> and had this nice geeky idea. Why not use that API to send posts directly from my terminal? When I just want to get my thoughts out, I do not necessarily need a UI (which will then only distract me again for the near forseeable future by making me read all the interesting posts others put out&amp;hellip;I need to timebox the latter 🙈)&lt;/p>
&lt;p>So how did I build this?&lt;/p>
&lt;h3 id="setting-up-the-project">Setting Up the Project&lt;/h3>
&lt;ul>
&lt;li>In a new folder, create a new Python file: I named mine &lt;code>bluesky_post_image.py&lt;/code>.&lt;/li>
&lt;li>Install necessary packages: The &lt;strong>requests&lt;/strong> library was essential for making HTTP requests to the Bluesky API. I also used Python’s json module to handle API responses. To install requests, run:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install requests
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="understanding-authentication-and-access-tokens">Understanding Authentication and Access Tokens&lt;/h3>
&lt;p>There is an ongoing joke in my filter bubble that&amp;rsquo;s like &lt;em>Once auth is done, everything else is easy&lt;/em>. So let&amp;rsquo;s do this part thouroughly.&lt;/p>
&lt;p>Bluesky’s API requires authentication to ensure that only authorized users can post on their platform. The first thing we need is an access token. Instead of directly using our username and password each time, we generated an access token, which acts as a key for logging in.&lt;/p>
&lt;p>To get our access token:&lt;/p>
&lt;ul>
&lt;li>We use an app password from Bluesky to authenticate with our handle.&lt;/li>
&lt;li>This returns an access token, which we can use for any future requests within the session&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_access_token&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handle&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">app_password&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://bsky.social/xrpc/com.atproto.server.createSession&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;identifier&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">handle&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;password&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">app_password&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">raise_for_status&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;accessJwt&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This function sends a POST request to the Bluesky authentication endpoint with our handle and app password, then returns an access token for further use&lt;/p>
&lt;h3 id="posting-text-content">Posting Text Content&lt;/h3>
&lt;p>With the access token in hand, we can now focus on the main task: posting content to Bluesky. I decided to make the app post a simple text message first.&lt;/p>
&lt;p>In the API call, I used the access token in the request headers:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create_text_post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">access_token&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://bsky.social/xrpc/com.atproto.repo.createRecord&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">access_token&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;repo&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;YOUR_DID&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># Replace with your DID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;collection&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;app.bsky.feed.post&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;record&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;$type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;app.bsky.feed.post&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;text&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;createdAt&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isoformat&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;Z&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">raise_for_status&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># Check for errors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Post created:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This code sends the post content and metadata to the Bluesky API, where it’s added to our feed.&lt;/p>
&lt;h3 id="attaching-images-to-posts">Attaching Images to Posts&lt;/h3>
&lt;p>An image tells more than a 1000 words, right? So once I had text posts working, I moved on to adding images. Posting images is a bit trickier because we need to:&lt;/p>
&lt;ul>
&lt;li>Upload the image file to create a &amp;ldquo;blob&amp;rdquo; in the API&lt;/li>
&lt;li>Use the blob’s metadata in our post request to attach the image&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">upload_image&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">access_token&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://bsky.social/xrpc/com.atproto.repo.uploadBlob&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">access_token&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;image/jpeg&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;rb&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">img_file&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">img_file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">raise_for_status&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;blob&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This reads the image file and sends it to Bluesky’s blob upload endpoint. The server responds with a blob object, which includes the image’s reference ID.&lt;/p>
&lt;h3 id="creating-a-post-with-image">Creating a Post with Image&lt;/h3>
&lt;p>With the blob reference from our image upload, we can now attach it to a new post. I updated my create_text_post function to add the image as an embedded object&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create_post_with_image&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">image_blob&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">access_token&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://bsky.social/xrpc/com.atproto.repo.createRecord&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;Bearer &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">access_token&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;repo&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;YOUR_DID&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># Replace with your DID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;collection&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;app.bsky.feed.post&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;record&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;$type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;app.bsky.feed.post&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;text&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;createdAt&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isoformat&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;Z&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;embed&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;$type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;app.bsky.embed.images&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;images&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;image&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">image_blob&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;alt&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;An image attached to the post&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">headers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">raise_for_status&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Post with image created:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this function, I added an embed field with my image_blob, linking the image in my post.&lt;/p>
&lt;p>Finally, I combined all my functions into a single script and added input prompts for the user to:&lt;/p>
&lt;ul>
&lt;li>Enter the post content.&lt;/li>
&lt;li>Optionally specify an image path to attach.&lt;/li>
&lt;/ul>
&lt;p>If you want to check it out, &lt;a href="https://github.com/LuiseFreese/BlueSkyTerminal">I put it on GitHub&lt;/a> and would love for you to try it out!&lt;/p>
&lt;p>And this is my first post from my terminal which attaches an image:&lt;/p>
&lt;blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:c4zuinv4gfs5kobrnbvtlfoe/app.bsky.feed.post/3lansibymf72l" data-bluesky-cid="bafyreihcasjp2apeqais5rxskffrxvp2gkbovl5qk72ouj3jlpx54l25ky">&lt;p lang="">Can I post images straight from my terminal as well? &lt;br>&lt;br>&lt;a href="https://bsky.app/profile/did:plc:c4zuinv4gfs5kobrnbvtlfoe/post/3lansibymf72l?ref_src=embed">[image or embed]&lt;/a>&lt;/p>&amp;mdash; Luise Freese (&lt;a href="https://bsky.app/profile/did:plc:c4zuinv4gfs5kobrnbvtlfoe?ref_src=embed">@luisefreese.bsky.social&lt;/a>) &lt;a href="https://bsky.app/profile/did:plc:c4zuinv4gfs5kobrnbvtlfoe/post/3lansibymf72l?ref_src=embed">November 11, 2024 at 8:33 AM&lt;/a>&lt;/blockquote>&lt;script async src="https://embed.bsky.app/static/embed.js" charset="utf-8">&lt;/script>
&lt;div class="questions-section text-center my-5 p-4 border rounded shadow-sm">
&lt;h3 class="font-weight-bold mb-3 mt-2" style="color: #333;">Have any questions?&lt;/h3>
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn btn-primary btn-lg contact-btn font-weight-bold">
Contact
&lt;/a>
&lt;/div>
&lt;style>
.questions-section {
background-color: #FF69B426;
}
.questions-section .contact-btn {
font-size: 16px;
padding: 12px 30px;
border-radius: 30px;
color: black;
text-decoration: none;
}
&lt;/style></description></item><item><title>How to make a SharePoint Web Part multilingual</title><link>https://m365princess.com/blogs/spfx-multilanguage/</link><pubDate>Sat, 02 Nov 2024 10:12:16 +0000</pubDate><guid>https://m365princess.com/blogs/spfx-multilanguage/</guid><description>&lt;p>If you want to easily allow users to have your SPFx Web Part in a language of their liking, this guide is for you. To enable this multi language feature, we need&lt;/p>
&lt;ol>
&lt;li>Define the keys and their types for localized strings in the &lt;code>mystrings.d.ts&lt;/code> file like this:&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">declare&lt;/span> &lt;span class="kr">interface&lt;/span> &lt;span class="nx">ISvgToJsonWebPartStrings&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">saveConfiguration&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">libraryName&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">column&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">selectColumn&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">declare&lt;/span> &lt;span class="nx">module&lt;/span> &lt;span class="s1">&amp;#39;SvgToJsonWebPartStrings&amp;#39;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">strings&lt;/span>: &lt;span class="kt">ISvgToJsonWebPartStrings&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">export&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depending on how much text you have in your Web Part, this can be a rather tedious task 😇.&lt;/p>
&lt;p>(replace the &lt;code>SvgToJsonWebPartStrings&lt;/code> with your Web Part Name)&lt;/p>
&lt;ol start="2">
&lt;li>Now in the &lt;code>en-us.js&lt;/code> file (it&amp;rsquo;s in the &lt;code>loc&lt;/code> folder) you will define for each of your keys that you have in the &lt;code>mystrings.d.ts&lt;/code> file, a key-value pair in english. If you also need &lt;code>de-de.js&lt;/code> for german, or &lt;code>fr-fr.js&lt;/code> for french or any other language, create a file with the name of that locale in the &lt;code>loc&lt;/code> folder and provide a translated version of the values of the &lt;code>en-us.js&lt;/code> file (Hello 👋 Copilot).&lt;/li>
&lt;/ol>
&lt;p>This should look a bit like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">define&lt;/span>&lt;span class="p">([],&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;saveConfiguration&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Konfiguration speichern&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;LibraryName&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Bibliotheksname&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;column&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Spalte&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;selectColumn&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Wählen Sie eine Spalte&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>Now replace all the hard coded strings like button texts, placeholders, labels, error messages etc. with &lt;code>strings.&amp;lt;keyname&amp;gt;&lt;/code>, so for example &lt;code>strings.column&lt;/code>.&lt;/li>
&lt;li>As a last step, you need to import the strings into the file you want to use them with something like &lt;code>import * as strings from 'SvgToJsonWebPartStrings';&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>If you now run &lt;code>gulp build&lt;/code> and &lt;code>gulp serve&lt;/code> again to try out your masterpiece in te workbench and want to see your newly added languages, append the URL &lt;code>https://&amp;lt;your tenant&amp;gt;.sharepoint.com/_layouts/15/workbench.aspx&lt;/code> with &lt;code>?locale=de-de&lt;/code> or any other locale that you created.&lt;/p>
&lt;p>&lt;img alt="web part in workbench with enable de-de locale" src="https://m365princess.com/images/webpart-german.png">&lt;/p>
&lt;p>Congrats! You enabled a multi-language feature in your Web Part and also improved the logic of it. Now if you change a text, that will appear two or more times in your Web Part once it reflects this everywhere, so less manual changes!&lt;/p>
&lt;p>If you want to have a look at the Web Part (which is still work in progress), you can do this here: &lt;a href="https://github.com/LuiseFreese/sp-dev-fx-webparts/tree/main/samples/react-svg-to-json-converter">react-svg-to-json-converter/&lt;/a>. Let me know what you think!&lt;/p></description></item><item><title>#FF69B4 or how to read Hex Color Values</title><link>https://m365princess.com/blogs/hex/</link><pubDate>Fri, 01 Nov 2024 06:43:58 +0000</pubDate><guid>https://m365princess.com/blogs/hex/</guid><description>&lt;p>I like pink. Like, a lot. To an extent, where it&amp;rsquo;s not just ANY pink, but a very specific one. It&amp;rsquo;s &lt;a href="https://www.color-hex.com/color/ff69b4">#FF69B4&lt;/a> - hot pink. Whenever I mention this, people ask me how I can remember this value and also how to read hex color values.&lt;/p>
&lt;p>The thing about hex colors is that they&amp;rsquo;re pretty straightforward once you understand what those six characters are doing. It&amp;rsquo;s not some magic - it&amp;rsquo;s just a different way of counting. Think of it as a very efficient way to tell your computer exactly how much red, green, and blue you want in your color. And since I&amp;rsquo;ve spent an embarrassing amount of time explaining this to fellow developers (who always tell me that they are just not good at anything with design 🙈), let me break it down properly.&lt;/p>
&lt;h2 id="color-addition-vs-subtraction-understanding-color-models">Color Addition vs. Subtraction: Understanding Color Models&lt;/h2>
&lt;p>Colors behave fundamentally differently depending on whether you&amp;rsquo;re working with light (like on your screen) or pigments (like in print). Think of it as the difference between adding spotlights versus layering filters.&lt;/p>
&lt;h3 id="additive-color-rgb">Additive Color (RGB)&lt;/h3>
&lt;p>On screens, we&amp;rsquo;re essentially working with three colored spotlights: red, green, and blue. When these lights overlap, they add together to create new colors:&lt;/p>
&lt;pre tabindex="0">&lt;code>Red + Green = Yellow
Green + Blue = Cyan
Red + Blue = Magenta (hello, hot pink&amp;#39;s cousin!)
Red + Green + Blue = White
&lt;/code>&lt;/pre>&lt;p>This is why combining all colors on your screen produces white - you&amp;rsquo;re adding all wavelengths of light together. It&amp;rsquo;s like pointing red, green, and blue spotlights at the same spot on a black surface. Where they overlap, you get white light.&lt;/p>
&lt;h3 id="subtractive-color-cmyk">Subtractive Color (CMYK)&lt;/h3>
&lt;p>Now flip that concept for physical media. Instead of adding light, we&amp;rsquo;re filtering white light through layers of cyan, magenta, and yellow pigments. Each layer acts like a filter that subtracts certain wavelengths:&lt;/p>
&lt;pre tabindex="0">&lt;code>Cyan = White - Red (blocks red light)
Magenta = White - Green (blocks green light)
Yellow = White - Blue (blocks blue light)
&lt;/code>&lt;/pre>&lt;p>When pigments overlap:&lt;/p>
&lt;pre tabindex="0">&lt;code>Yellow + Magenta = Red (blocks blue and green)
Magenta + Cyan = Blue (blocks green and red)
Cyan + Yellow = Green (blocks red and blue)
All three = Black (blocks everything)
&lt;/code>&lt;/pre>&lt;p>This is why printers use CMYK (Cyan, Magenta, Yellow, and Black). Each color acts as a filter, removing specific wavelengths from white light. Stack them all together, and you&amp;rsquo;re blocking all wavelengths - giving you black.&lt;/p>
&lt;h2 id="hexadecimal-color-representation">Hexadecimal Color Representation&lt;/h2>
&lt;p>A hex color code represents precise RGB color values in a base-16 numeral system. Unlike decimal (base-10) or binary (base-2), hexadecimal uses sixteen distinct symbols: 0-9 for values zero to nine, and A-F for values ten to fifteen. This makes hex particularly efficient for representing color values, as each hex digit perfectly represents four binary digits (bits).&lt;/p>
&lt;p>The structure of a hex color code is straightforward&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-md" data-lang="md">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">#FF69B4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span> | | |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> R G B
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Each pair of characters represents a color channel&amp;rsquo;s intensity, ranging from 00 (0 in decimal) to FF (255 in decimal).&lt;/p>
&lt;h2 id="breaking-down-ff69b4">Breaking Down #FF69B4&lt;/h2>
&lt;p>Let&amp;rsquo;s decode our hot pink example:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Red Channel (FF):&lt;/p>
&lt;ul>
&lt;li>FF in hex = 255 in decimal&lt;/li>
&lt;li>Maximum red intensity&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Green Channel (69):&lt;/p>
&lt;ul>
&lt;li>69 in hex = 105 in decimal&lt;/li>
&lt;li>Moderate green intensity (about 41%)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Blue Channel (B4):&lt;/p>
&lt;ul>
&lt;li>B4 in hex = 180 in decimal&lt;/li>
&lt;li>Substantial blue presence (about 70%)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>This combination yields &lt;code>RGB(255, 105, 180)&lt;/code>, creating that distinctive hot pink color 💖&lt;/p>
&lt;p>Let&amp;rsquo;s visualize this:&lt;/p>
&lt;p>&lt;img alt="color components" src="https://m365princess.com/images/color-components.png">&lt;/p>
&lt;h2 id="fun-facts-to-drop-at-your-next-party">Fun Facts to drop at your next party&lt;/h2>
&lt;ol>
&lt;li>Hot pink isn&amp;rsquo;t real. Well, not in the traditional spectrum of light. It&amp;rsquo;s actually your brain trying to make sense of seeing red and blue at the same time. It&amp;rsquo;s basically a color hallucination we all agree on.&lt;/li>
&lt;li>Hex codes could have been longer! They almost had seven characters instead of six, with an extra digit for transparency. But someone decided to keep things cleaner, and now we have our neat six-character codes. If you want to learn more on how to get transparent hex values: &lt;a href="https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4">https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4&lt;/a>&lt;/li>
&lt;li>Red is boring, so you can also use&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="o">*&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">outline&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#FF69B4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>for debbuging!
4. For &lt;em>some&lt;/em> reason, I also like &lt;a href="https://www.color-hex.com/color/038186">#038186&lt;/a> - Can you guess why?&lt;/p>
&lt;p>And just in case you missed it - Any idea what is the accent color of this blog? Now go forth and color the world! Just remember: when in doubt, hot pink is always a valid choice. 😉&lt;/p></description></item><item><title>Improving a Web Part with PnP.js and React Webhooks</title><link>https://m365princess.com/blogs/spfx-2/</link><pubDate>Sun, 20 Oct 2024 06:43:58 +0000</pubDate><guid>https://m365princess.com/blogs/spfx-2/</guid><description>&lt;p>I promised, I’d improve my initial draft. So I want to address three things in here:&lt;/p>
&lt;ol>
&lt;li>Using PnPjs&lt;/li>
&lt;li>Splitting up a huge component in a parent component and child components&lt;/li>
&lt;li>Leveling up with custom hooks&lt;/li>
&lt;/ol>
&lt;p>💡 This post is part of a series:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/blogs/spfx-1">Building my first SharePoint Web Part with SharePoint Framework Toolkit- From Web App to Web Part&lt;/a>&lt;/li>
&lt;li>📌 you are here: &lt;a href="https://m365princess.com/blogs/spfx-2">Improving a Web Part with PnP.js and React Webhooks&lt;/a>&lt;/li>
&lt;li>Making a Web Part ready for the Sample Solution Gallery - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>First things first - here is an up-to-date screenshot so that you know what we are talking about!&lt;/p>
&lt;p>&lt;img alt="web part" src="https://m365princess.com/images/webpart-sp.png">&lt;/p>
&lt;p>Additionally to introducing a new features that allows users to not only select lists and their columns on the site the Web Part lives on, but also select a site, and then the lists and columns, I focused on improving the code itself.&lt;/p>
&lt;h2 id="using-pnpjs">Using PnPjs&lt;/h2>
&lt;p>Oh boy, I did it the hard way and used native SharePoint REST API to get lists and columns. This made my code unnecessarily convoluted. It took me a while to realize that. I started without PnPjs as I thought it would be easier for me as a an SPFx beginner. I need to admit though, that quite the opposite was the case. So after a friend said &amp;ldquo;Wow I did not even know that one could use SharePoint REST natively as I &lt;strong>always&lt;/strong> use PnPjs&amp;rdquo;, this was the sign for me.&lt;/p>
&lt;blockquote>
&lt;p>Why not using the library everyone and their mom is using?&lt;/p>
&lt;/blockquote>
&lt;p>So &lt;a href="https://pnp.github.io/pnpjs/">PnPjs&lt;/a> to the rescue!&lt;/p>
&lt;p>I think this is the best thing that I discovered so far about SPFx! It&amp;rsquo;s a huge simplifier as URL construction, headers, authentication are handled by PnPjs, so no need to manually craft a URL, set headers etc.&lt;/p>
&lt;p>To transform from a SharePoint REST API call to PnPjs, you will&lt;/p>
&lt;ol>
&lt;li>install PnPjs with&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm install @pnp/sp @pnp/common @pnp/logging @pnp/odata
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>import the modules into your code&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">spfi&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SPFx&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@pnp/sp&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// PnP JS functions
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="s2">&amp;#34;@pnp/sp/webs&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Import the SPWeb methods
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="s2">&amp;#34;@pnp/sp/lists&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Import the List methods
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>initialize PnPjs with SPFx context&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">sp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">spfi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">siteUrl&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">using&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">SPFx&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>[&lt;code>props.siteUrl&lt;/code> is the SharePoint site URL, &lt;code>props.context&lt;/code> is the context provided by SPFx Web Part for authentication]&lt;/p>
&lt;ol start="4">
&lt;li>Replace REST API calls with PnPjs methods and remove headers - example&lt;/li>
&lt;/ol>
&lt;p>This is the native SharePoint REST API implementation&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">siteUrl&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/_api/web/lists?$filter=Hidden eq false`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;GET&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Accept&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json;odata=verbose&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">lists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">results&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is the PnPjs method:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">lists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">sp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">web&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lists&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hidden eq false&amp;#34;&lt;/span>&lt;span class="p">)();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SO. MUCH. CLEANER.&lt;/p>
&lt;p>So rinse and repeat for all the other calls! This is stil a work in progress, but getting there!&lt;/p>
&lt;h2 id="split-up-the-main-component-in-child-components">Split up the main component in child components&lt;/h2>
&lt;p>At first, I only had one very lengthy &lt;code>SvgToJson.tsx&lt;/code> file, which made it hard for me to read code and address issues. So I worked on splitting up this beast into several child components so that my main react component would orchestrate the functionality of the Web Part and takes care of managing state and rendering the child components. In my case, this means, that I have components for&lt;/p>
&lt;ul>
&lt;li>&lt;code>SVGInput.tsx&lt;/code> - Allows users to select an SVG file from a SharePoint library&lt;/li>
&lt;li>&lt;code>SVGOutput.tsx&lt;/code> - Displays the selected SVG content&lt;/li>
&lt;li>&lt;code>ConvertButton.tsx&lt;/code> - Converts the SVG content to JSON format&lt;/li>
&lt;li>&lt;code>ToggleSwitch.tsx&lt;/code> - Toggles visibility of SiteSelector, ListSelector, ColumnSelector, and ApplyButton&lt;/li>
&lt;li>&lt;code>SiteSelector.tsx&lt;/code> - Allows users to select a SharePoint site&lt;/li>
&lt;li>&lt;code>ListSelector.tsx&lt;/code> - Allows users to select a SharePoint list&lt;/li>
&lt;li>&lt;code>ColumnSelector.tsx&lt;/code> - Allows users to select a column from the selected SharePoint list&lt;/li>
&lt;li>&lt;code>ApplyButton.tsx&lt;/code> - Applies the JSON result as column formatting to the selected SharePoint column&lt;/li>
&lt;li>&lt;code>Message.tsx&lt;/code> - Displays messages users&lt;/li>
&lt;li>&lt;code>TeamsSaveCongurations&lt;/code> - Allows users in Microsoft Teams to set the site URL and the library Name where SVGs come from&lt;/li>
&lt;/ul>
&lt;p>To move functionality to a child component, you will&lt;/p>
&lt;ol>
&lt;li>Create a new &lt;code>.tsx&lt;/code> file in the same folder the other &lt;code>.tsx&lt;/code> files live in&lt;/li>
&lt;li>Move the logic from the main component to the new child component&lt;/li>
&lt;li>Define props for the child component&lt;/li>
&lt;li>Pass props from parent (main component) to child component&lt;/li>
&lt;/ol>
&lt;p>This takes care, that I have a lot more files, but that each file has a very dedicated scope which makes it a lot easier to go on bug-hunt 🐛.&lt;/p>
&lt;h2 id="playing-with-react-hooks">Playing with react hooks&lt;/h2>
&lt;p>I learned about custom hooks and thought it would be a great idea to making my components even more maintainable as they get cleaner. I think of them like a function that can use the already existing hooks (like &lt;code>useState&lt;/code> and &lt;code>useEffect&lt;/code>). This means: You write the logic in a separate file like &lt;code>useFetchLists.ts&lt;/code> and call this function from your child component file. So we can now use this custom hook (function) in any component that needs to get lists from a SharePoint site. That means you only need to write (and debug and test) that code once. Also, I find it way cleaner to separate data-fetching-logic from UI logic.&lt;/p>
&lt;p>Here is my &lt;code>useFetchLists&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">useEffect&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">spfi&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">SPFx&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;@pnp/sp&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="s2">&amp;#34;@pnp/sp/webs&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="s2">&amp;#34;@pnp/sp/lists&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">IDropdownOption&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">MessageBarType&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;@fluentui/react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">useFetchLists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">siteUrl&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">lists&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setLists&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">IDropdownOption&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">null&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">messageType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setMessageType&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBarType&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="nx">MessageBarType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">info&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">useEffect&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">fetchLists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">siteUrl&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">sp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">spfi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">siteUrl&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">using&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">SPFx&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">fetchedLists&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">sp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">web&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lists&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// List of titles to exclude
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">excludedTitles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;appdata&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;appfiles&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Composed Looks&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Converted Forms&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Documents&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Form Templates&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;List Template Gallery&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Master Page Gallery&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Site Assets&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Site Pages&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Solution Gallery&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;TaxonomyHiddenList&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Theme Gallery&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;User Information List&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Web Part Gallery&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Web Template Extensions&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">listOptions&lt;/span>: &lt;span class="kt">IDropdownOption&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fetchedLists&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">list&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">excludedTitles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">indexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Title&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">list&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key&lt;/span>: &lt;span class="kt">list.Id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">text&lt;/span>: &lt;span class="kt">list.Title&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">listOptions&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Error fetching lists:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Error fetching lists: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setMessageType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">MessageBarType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fetchLists&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">siteUrl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">lists&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">messageType&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">useFetchLists&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which then reduces my &lt;code>ListSelector.tsx&lt;/code> to a very minimal&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Dropdown&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">MessageBar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">IDropdownOption&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;@fluentui/react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">styles&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./SvgToJson.module.scss&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">useFetchLists&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./useFetchLists&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">ListSelectorProps&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">siteUrl&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">onListChange&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">listId&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">listName&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">void&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">ListSelector&lt;/span>: &lt;span class="kt">React.FC&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ListSelectorProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">siteUrl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">onListChange&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">lists&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">messageType&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useFetchLists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">siteUrl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">handleListChange&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>: &lt;span class="kt">React.FormEvent&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">HTMLDivElement&lt;/span>&lt;span class="p">&amp;gt;,&lt;/span> &lt;span class="nx">option?&lt;/span>: &lt;span class="kt">IDropdownOption&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">option&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">onListChange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">option&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">key&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">option&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">text&lt;/span> &lt;span class="kr">as&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">styles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dropdown&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">message&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">MessageBar&lt;/span> &lt;span class="na">messageBarType&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">messageType&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">MessageBar&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Dropdown&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;Select a list&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">label&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;Lists&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">options&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">lists&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onChange&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleListChange&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">styles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dropdown&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">aria-label&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;Select a list&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">ListSelector&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Full code here: &lt;a href="https://github.com/LuiseFreese/sp-dev-fx-webparts/tree/main/samples/react-svg-to-json-converter">SVG to JSON Web Part&lt;/a>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Took me a while to get behind PnPjs, but once you get the hang of it, you will never want to do it differently. Splitting up my component file into an orchestrator and child components was one of the best ideas for working with the code. No more endless scrolling! What&amp;rsquo;s left? Need to polish the Web Part, work on styling it properly for Microsoft Teams and implement your suggestions as well. This Web Part still needs a lot of work/love. Logic needs to be hardened, it needs to also better be able with users who won&amp;rsquo;t choose the happy path, and there are still some more quirks to be ironed out. What do you think?&lt;/p></description></item><item><title>Building my first SharePoint Web Part with SPFx Toolkit</title><link>https://m365princess.com/blogs/spfx-1/</link><pubDate>Wed, 16 Oct 2024 06:43:58 +0000</pubDate><guid>https://m365princess.com/blogs/spfx-1/</guid><description>&lt;p>I’ll walk you through how I transformed my standalone &lt;a href="https://www.m365princess.com/blogs/svg-to-json/">JavaScript web tool&lt;/a> into a SharePoint Web Part using &lt;a href="https://github.com/pnp/vscode-viva">SharePoint Framework (SPFx) Toolkit&lt;/a>. My original web tool was pretty straightforward. It allowed users to paste in SVG code, click a &lt;strong>Convert and copy to clipboard&lt;/strong> button, and get JSON output formatted for use in SharePoint list column formatting. But it was standalone - it wasn’t integrated into SharePoint itself, which meant users still had to copy the output manually and paste it into SharePoint.&lt;/p>
&lt;blockquote>
&lt;p>Why not create a SharePoint Web Part that does all of this automatically and integrates with SharePoint’s own UI?&lt;/p>
&lt;/blockquote>
&lt;p>💡 This blog post is part of a series:&lt;/p>
&lt;ul>
&lt;li>📌 you are here: &lt;a href="https://m365princess.com/blogs/spfx-1">Building my first SharePoint Web Part with SPFx Toolkit- From Web App to Web Part&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/spfx-2">Improving a Web Part with PnP.js and React Webhooks&lt;/a>&lt;/li>
&lt;li>Making a Web Part ready for the Sample Solution Gallery - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;h2 id="setting-up-sharepoint-framework-toolkit">Setting Up SharePoint Framework Toolkit&lt;/h2>
&lt;p>I chose the &lt;strong>SharePoint Framework Toolkit&lt;/strong>, which is a modern approach to setting up SPFx projects. With the toolkit, everything is more streamlined - it eases a lot of tasks ands it was an easy way for me to get used to SPFx development:&lt;/p>
&lt;ul>
&lt;li>No need to take care of any npm packages/dependencies (other than setting the right node version - I use &lt;a href="https://github.com/nvm-sh/nvm">nvm&lt;/a> for that)&lt;/li>
&lt;li>also no need to create all the files that are needed&lt;/li>
&lt;li>samples where you can &lt;del>steal&lt;/del> get inspired how others solved something&lt;/li>
&lt;li>nifty commands available with &lt;code>CTRL&lt;/code> + &lt;code>SHIFT&lt;/code> + &lt;code>P&lt;/code> - type in &lt;code>SharePoint Framework&lt;/code> (or the first letters) and it will give you all the options&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="command palette in SPFx Toolkit" src="https://m365princess.com/images/commandpallete.png">&lt;/p>
&lt;p>Here’s how I got started:&lt;/p>
&lt;ul>
&lt;li>Install the SharePoint Framework Toolkit by hitting the &lt;strong>Install&lt;/strong> button in the &lt;a href="https://marketplace.visualstudio.com/items?itemName=m365pnp.viva-connections-toolkit">VS Code Markeplace Site&lt;/a>&lt;/li>
&lt;li>I created a new project, specifying the Web Part’s name (&lt;code>SvgToJson&lt;/code>) and the framework (&lt;code>React&lt;/code>) and the toolkit did the entire scaffolding for me.&lt;/li>
&lt;li>I familiarized myself with the projects folders:
&lt;ul>
&lt;li>&lt;code>src/webparts/&lt;/code>: This is where all your Web Part code goes&lt;/li>
&lt;li>&lt;code>components/&lt;/code>: Inside the Web Part folder, this is where React components live&lt;/li>
&lt;li>&lt;code>styles/&lt;/code>: Where your custom styles are written (in my case, this means #ff69b4 &amp;ndash;&amp;gt; that&amp;rsquo;s hot-pink all the way)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>So far, so easy.&lt;/p>
&lt;h2 id="moving-the-web-tool-to-spfx">Moving the Web Tool to SPFx&lt;/h2>
&lt;p>My next task was to move the existing functionality from the web tool into the newly created SPFx project. This involved a couple of things:&lt;/p>
&lt;ul>
&lt;li>JavaScript to TypeScript: SharePoint Framework uses TypeScript, which is JavaScript with type-checking. Lucky me writes TS for her PCF components since quite a while, that helped a lot!&lt;/li>
&lt;li>React Component: The web tool was just Vanilla JavaScript 🍦 - so I needed to make some big adjustments here - but totally worth it! In React, everything is a component, meaning that each piece of UI is isolated and reusable. Instead of using DOM manipulation directly (e.g., &lt;code>document.getElementById&lt;/code>), I used React’s &lt;code>useState&lt;/code> hook to manage the SVG content, the JSON result, and UI feedback (messages, dropdown selections). Not gonna lie, GitHub Copilot helped me here. But after two examples, I got the hang of it and used my digital dev-friend only for debugging (still that dude wasn&amp;rsquo;t exactly totally unemployed 😇)&lt;/li>
&lt;/ul>
&lt;p>My very first draft looked like this (please don&amp;rsquo;t judge me!)&lt;/p>
&lt;p>&lt;img alt="first draft" src="https://m365princess.com/images/spfx-1st-draft.jpg">&lt;/p>
&lt;p>If you want to view your result in the browser, you will need to run &lt;code>gulp build&lt;/code> - which will first build your web part (🤞🤞) - or give you errors that you first need to fix and then run &lt;code>gulp serve&lt;/code> - it will display a URL for your workbench - make sure you open this in the browser profile that points to your dev tenant :-). From now on, you can see all the changes you make directly in the browser, just like having a liveserver. Trust me, the first time it actually builds is magic! ✨&lt;/p>
&lt;h2 id="adding-interactivity-and-sharepoint-integration">Adding Interactivity and SharePoint Integration&lt;/h2>
&lt;p>Now, I wanted to add some features that would make it easy for users to apply the format directly to a column of their choice:&lt;/p>
&lt;ul>
&lt;li>Select a SharePoint list from a dropdown&lt;/li>
&lt;li>Select a specific column where the SVG formatting would be applied&lt;/li>
&lt;li>Apply the SVG format to the column right from the Web Part&lt;/li>
&lt;/ul>
&lt;p>Here’s how I made it happen:&lt;/p>
&lt;h3 id="fetching-sharepoint-lists">Fetching SharePoint Lists&lt;/h3>
&lt;p>To allow the user to select a SharePoint list, we need to interact with the SharePoint REST API and fetch the available lists:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">fetchLists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/_api/web/lists?$filter=Hidden eq false&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">listOptions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">list&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key&lt;/span>: &lt;span class="kt">list.Id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">text&lt;/span>: &lt;span class="kt">list.Title&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">listOptions&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This populates my Fluent UI dropdown with the lists.&lt;/p>
&lt;h3 id="fetching-the-columns">Fetching the columns&lt;/h3>
&lt;p>Once a list is selected, the user can choose which column to apply the formatting to. Again, we use the SharePoint REST API to get the available columns for the selected list:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">fetchColumns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">listId&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/_api/web/lists(guid&amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">listId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39;)/fields`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">columnOptions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">field&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">key&lt;/span>: &lt;span class="kt">field.InternalName&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">text&lt;/span>: &lt;span class="kt">field.Title&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setColumns&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">columnOptions&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="applying-the-svg-format-to-the-column">Applying the SVG Format to the Column&lt;/h3>
&lt;p>Once the user has selected both the list and column, they can apply the generated SVG format directly to the column using another API call:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">applyColumnFormatting&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">void&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/_api/web/lists(guid&amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">selectedList&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39;)/fields/getbyinternalnameortitle(&amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">selectedColumn&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39;)`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;X-HTTP-Method&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;MERGE&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;IF-MATCH&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;*&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">CustomFormatter&lt;/span>: &lt;span class="kt">jsonResult&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ok&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">alert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Column formatting applied successfully!&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also, we open the selected list in a new tab for them. This is how it looks like:&lt;/p>
&lt;p>&lt;img alt="webpart" src="https://m365princess.com/images/webpart.png">&lt;/p>
&lt;p>After that, I got the very valid feedback that I should introduce the property pane so that users can decide where the SVGs come from and let them configure the Web Part with a &lt;code>siteURL&lt;/code> and a &lt;code>libraryName&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">protected&lt;/span> &lt;span class="nx">getPropertyPaneConfiguration&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">IPropertyPaneConfiguration&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">pages&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">header&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">description&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Configure your web part by providing the Site URL and library name you want your SVGs to select from.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">groups&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">groupName&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Settings&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">groupFields&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">PropertyPaneTextField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;siteUrl&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">label&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Site URL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">PropertyPaneTextField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;libraryName&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">label&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Library Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I also added a message to the webpart before configuration has happened:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="kr">class&lt;/span> &lt;span class="nx">SvgToJsonWebPart&lt;/span> &lt;span class="kr">extends&lt;/span> &lt;span class="nx">BaseClientSideWebPart&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ISvgToJsonWebPartProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="nx">render&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">properties&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">siteUrl&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">properties&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">libraryName&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">domElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> Please select the edit icon to configure your Web Part.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sb"> &amp;lt;/div&amp;gt;`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which will then look like this:&lt;/p>
&lt;p>&lt;img alt="Property pane" src="https://m365princess.com/images/propertypane.png">&lt;/p>
&lt;p>This makes this Web Part already so much more flexible! Quite happy with this first milestone, but will make sure to improve the Web Part, documenting becoming better at SPFx is a process.&lt;/p>
&lt;p>&lt;a href="https://github.com/LuiseFreese/sp-dev-fx-webparts/tree/main/samples/react-svg-to-json-converter">You can find the code in here&lt;/a> =&amp;gt; PR tp PnP will be on the way as soon as I am ready for it (and the Web Part is good enough) ✨&lt;/p>
&lt;p>Speaking of which: Here is the start of the list that I want to work on:&lt;/p>
&lt;ul>
&lt;li>Using PnPjs&lt;/li>
&lt;li>Splitting up my extra-long &lt;code>.tsx&lt;/code> file in several smaller components&lt;/li>
&lt;li>Maybe even make this a Teams app&lt;/li>
&lt;/ul>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>It&amp;rsquo;s not much, but it&amp;rsquo;s honest work (Insert the gif that comes to mind here). Some habits, that I already quite adopted, helped me here the most:&lt;/p>
&lt;ul>
&lt;li>Only make small changes at a time&lt;/li>
&lt;li>Read error messages carefully&lt;/li>
&lt;/ul>
&lt;p>Now stay tuned for the next part :-)&lt;/p></description></item><item><title>AI and dinosaurs</title><link>https://m365princess.com/blogs/ai-dino/</link><pubDate>Sun, 06 Oct 2024 06:43:58 +0000</pubDate><guid>https://m365princess.com/blogs/ai-dino/</guid><description>&lt;p>Have you ever noticed how almost every kid goes through that intense dinosaur phase? You know the one. Suddenly, your five-year-old nephew can rattle off facts about the size, speed, and power of a Tyrannosaurus rex or Velociraptor as if he just graduated with a degree in paleontology. It&amp;rsquo;s impressive at first – until you realize he&amp;rsquo;s really just memorizing dinosaur names and random stats from a book or a TV show.&lt;/p>
&lt;p>But in his mind? He’s unstoppable. Armed with this newfound knowledge, he doesn’t just know about dinosaurs; he becomes one, in a way. He projects their strength and power onto himself. It’s like he’s transformed into a mini-dinosaur expert, ready to take on the world. In reality, though, he’s still just scratching the surface – because memorizing cool dinosaur facts is a far cry from the in-depth understanding of a real paleontologist.&lt;/p>
&lt;blockquote>
&lt;p>Funny enough, the same thing is happening right now with AI.&lt;/p>
&lt;/blockquote>
&lt;p>In recent months, it feels like we&amp;rsquo;ve been overrun by a wave of self-proclaimed AI experts. Everyone&amp;rsquo;s suddenly talking about machine learning, algorithms, and—my favorite term of all: &lt;strong>prompt engineering&lt;/strong>. It’s almost like AI is the new T-Rex, and everyone wants to claim they’ve mastered it. People throw around buzzwords, mention how they&amp;rsquo;ve cracked the code on getting chatbots to do what they want, and present themselves as cutting-edge specialists in a field that, let’s be honest, is more complex than most of us realize.&lt;/p>
&lt;p>Here’s where things get interesting: like the kid who knows all the dinosaur names but doesn’t quite understand how fossils are dated or what really makes a species go extinct, many of these so-called AI experts are only skimming the surface. They might know how to ask an AI tool a question or get it to spit out a clever response, but that doesn’t mean they understand how it all works under the hood. They’re not engineers, data scientists, or machine learning specialists who deal with the actual nuts and bolts of AI on a daily basis.&lt;/p>
&lt;p>And yet, much like our dinosaur-obsessed kids, they don’t see it that way. There’s this sense of empowerment – they feel that by wielding these tools, they’ve somehow absorbed the &lt;em>power&lt;/em> of the AI itself. They see themselves as being on the cutting edge, even though what they’ve really done is mastered the equivalent of a surface-level trivia game. Knowing a bunch of facts about dinosaurs doesn’t make you a paleontologist, and knowing how to get an AI tool to generate some interesting content doesn’t make you an AI expert.&lt;/p>
&lt;p>But here’s where it gets tricky: in both cases, that surface-level knowledge is enough to make someone feel competent; sometimes overly competent. The term &lt;strong>prompt engineering&lt;/strong> is a great example of this. It sounds complex and technical, but in many cases, it’s just about phrasing questions in a way that gets the response you’re hoping for. There’s no deep understanding required, but it’s packaged up in a way that makes it seem like a specialized skill. The result? A lot of people walking around feeling like they’ve tapped into the secrets of AI, when really, they’re still hanging out in the sandbox.&lt;/p>
&lt;p>Meanwhile, the real experts—data engineers, machine learning specialists, and those who understand the math and algorithms that drive AI—are doing the equivalent of the hard, paleontological work. They’re the ones handling big data, running complex models, and troubleshooting problems most of us wouldn’t even know where to start with.&lt;/p>
&lt;p>But just like a kid roaring around the living room pretending to be a T-Rex, the self-proclaimed AI experts are having a great time projecting all that AI power onto themselves. And honestly, can you blame them?&lt;/p>
&lt;p>I don&amp;rsquo;t deny that someone needs to explain users how to use tools. But I have two issues here:&lt;/p>
&lt;ol>
&lt;li>The focus on &amp;ldquo;THIS ONE TRICK&amp;rdquo;- posts shifts the responsibility for success to the user, who gets a one-size-fits-all-tool, that they are now encouraged to use for specialized jobs. To master these, they need to tweak the tool and follow lots of &amp;ldquo;tricks&amp;rdquo;, download &amp;ldquo;cheatsheets&amp;rdquo; and read &amp;ldquo;whitepapers&amp;rdquo;. That&amp;rsquo;s now how I envision a modern workplace. Yes, training and adoption are extraordinary important for software success, but what we see right now is going into the wrong direction. WE need to provide users with specialized tools (I talked about this in this post: ) and then teach them when to use what.&lt;/li>
&lt;li>The way we are flooded by self-proclaimed specialists that barely scratch the surface makes it appear as if everyone is an authority in AI. Let me be very clear here: I use and build AI tools (way beyond hooking an LLM into a customer bot) and still I don&amp;rsquo;t claim to be an expert.&lt;/li>
&lt;/ol>
&lt;p>At this point I am bored by all the &lt;del>dinosaur&lt;/del> AI- related LinkedIn posts that promise you that ONE trick that will make chatGPT transform your work. In the meantime, you just suffer through a tuesday full of back-to-back meetings (but luckily, the double bookings are now recorded, transcribed and tasks have been jotted down by Copilot so there is no excuse anymore to not catch up) and wait for the transformation.&lt;/p>
&lt;p>What&amp;rsquo;s your take?&lt;/p></description></item><item><title>Introducing the SVG to JSON for SharePoint List formatter</title><link>https://m365princess.com/blogs/svg-to-json/</link><pubDate>Sun, 06 Oct 2024 06:43:58 +0000</pubDate><guid>https://m365princess.com/blogs/svg-to-json/</guid><description>&lt;p>In my latest blog posts, I played a lot of SVGs in SharePoint lists. For everyone who isn&amp;rsquo;t aware - Unlike other image formats like &lt;code>.png&lt;/code> or &lt;code>.jpg&lt;/code>, &lt;code>.svg&lt;/code> are vectors - which can be expressed as code. If you open an SVG image in your browser, you can hit the F12 key on your Keyboard to inspect this code. You will see, that it contains lots of paths - the more complex the image is, the more paths you will find. If you only have a few paths, you can just copy them &lt;a href="https://m365princess.com/blogs/sp-plant">like I showed in this blog post&lt;/a>, but at some point, this is a rather tedious task. So I asked myself, if there was a better way - and for sure, there is!&lt;/p>
&lt;p>&lt;img alt="SVG to JSON for SharePoint List formatter" src="https://m365princess.com/images/svgtosp-screenshot.png">&lt;/p>
&lt;h2 id="the-minimal-viable-product">The minimal viable product&lt;/h2>
&lt;p>I created a super simple JavaScript file that would take an &lt;code>data.svg&lt;/code>, extract each &lt;code>d&lt;/code> attribute and &lt;code>fill&lt;/code> and then output an array ob objects like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;d&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;path goes here&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fill&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;hex value of fill color goes here&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once that worked on my machine I thought about how I could make this better&lt;/p>
&lt;h2 id="the-minimal-lovable--product">The minimal lovable 💖 product&lt;/h2>
&lt;p>To take this from a something that is more or less viable to something that users can fall in love with, I had two main ideas:&lt;/p>
&lt;h3 id="make-it-work-as-a-public-tool">Make it work as a public tool&lt;/h3>
&lt;p>To make this public as fast as possible (this was a sunday morning project), I decided to deploy with Netlify - this already works nice for this blog, so I already had an account. Netlify will use the code you have on GitHub and deploy it to a static site for you. You will need an account at Netlify, once you have that and are logged in&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Add new site&lt;/strong> &amp;gt; &lt;strong>Import an existing project&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>GitHub&lt;/strong>&lt;/li>
&lt;li>You will now authorize Netlify to read your GitHub repositories&lt;/li>
&lt;li>Select the repo that your code sits in&lt;/li>
&lt;li>Enter a &lt;strong>Site Name&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Deploy &lt;repo name>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>From now on, every push to &lt;code>main&lt;/code> will trigger a new deployment. You can adjust this to your needs.&lt;/p>
&lt;h3 id="provide-the-entire-json-that-is-needed-to-paste-into-a-sharepoint-list-column">Provide the entire json that is needed to paste into a SharePoint List column&lt;/h3>
&lt;h4 id="the-core">The core&lt;/h4>
&lt;p>The core functionality of the tool sits in the &lt;code>app.js&lt;/code> file, which&lt;/p>
&lt;ul>
&lt;li>takes as input the provided code (needs to be either pasted in or an SVG file can be dragged&amp;rsquo;n&amp;rsquo;dropped to the inputbox reading its file content)&lt;/li>
&lt;li>builds the initial JSON structure for the column&lt;/li>
&lt;li>extracts all the &lt;code>d&lt;/code> and &lt;code>fill&lt;/code> attributes from all paths&lt;/li>
&lt;li>ingests these attributes into the object as shown above&lt;/li>
&lt;li>initiates download of the result&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;DOMContentLoaded&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">svgInput&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;svgInput&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">convertBtn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;convertBtn&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dragover&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">preventDefault&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// Prevent default to allow drop
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">classList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dragover-active&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// Add the highlight class
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dragleave&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">classList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dragover-active&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// Remove highlight when drag leaves
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;drop&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">preventDefault&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// Prevent default to stop file from opening in a new tab
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">classList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dragover-active&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// Remove the highlight class on drop
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dataTransfer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">files&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">files&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">files&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">reader&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">FileReader&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">onload&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Set the content to the textarea
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">readAsText&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">convertBtn&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;click&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processSVGContent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">function&lt;/span> &lt;span class="nx">processSVGContent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DOMParser&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">svgDoc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">parser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parseFromString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;image/svg+xml&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">svgDoc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;path&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;$schema&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;span&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;@currentField&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;svg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;viewBox&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">svgDoc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">documentElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;viewBox&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Process each &amp;lt;path&amp;gt; element and add it to the JSON structure
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">paths&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">path&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">pathObj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;d&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;d&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;fill&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fill&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="s2">&amp;#34;#000000&amp;#34;&lt;/span> &lt;span class="c1">// Default to black if no fill is provided
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">children&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">children&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pathObj&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Convert the result object to JSON and start the download
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">jsonString&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">blob&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Blob&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="nx">jsonString&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">link&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">link&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">href&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">URL&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createObjectURL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">blob&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">link&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">download&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;svg-to-sharepoint-json.json&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">link&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">click&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Clear the input field
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">svgInput&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="ui">UI&lt;/h4>
&lt;p>Now obviously, we would need some interface to let people drag&amp;rsquo;n&amp;rsquo;drop their SVG image or paste in their SVG code. I created a basic HTML page with an inputbox and a button and a corresponding &lt;code>style.css&lt;/code> that would take care of making this a bit more 💖 pink and more visually pleasing. Additionally, I took care of giving users visual feedback when hovering with their drag-n-drop file over the dropzone.&lt;/p>
&lt;h2 id="see-the-tool-in-action">See the tool in action&lt;/h2>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/TLIQQY0sjB8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div>
&lt;p>If you want to use this, you can choose between either drag&amp;rsquo;n&amp;rsquo;drop an SVG image onto the inputbox or pasting the SVG code:&lt;/p>
&lt;h3 id="for-dragndrop">For drag&amp;rsquo;n&amp;rsquo;drop&lt;/h3>
&lt;ol>
&lt;li>Open &lt;a href="https://svgconverter.netlify.app/">SVG to JSON for SharePoint List Converter&lt;/a>&lt;/li>
&lt;li>Drag&amp;rsquo;n&amp;rsquo;drop an SVG file to the input box (it will highlight)&lt;/li>
&lt;li>Select the &lt;strong>Convert and Download JSON&lt;/strong> button&lt;/li>
&lt;/ol>
&lt;h3 id="for-pasting-the-svg-code">For pasting the SVG code&lt;/h3>
&lt;ol>
&lt;li>Open an SVG in your browser&lt;/li>
&lt;li>Hit F12 on your keyboard&lt;/li>
&lt;li>In the first line of the code, right-click and select &lt;strong>Edit as HTML&lt;/strong>&lt;/li>
&lt;li>Select the entire code and copy it&lt;/li>
&lt;li>Open &lt;a href="https://svgconverter.netlify.app/">SVG to JSON for SharePoint List Converter&lt;/a>&lt;/li>
&lt;li>Paste in your code&lt;/li>
&lt;li>Select the &lt;strong>Convert and Download JSON&lt;/strong> button&lt;/li>
&lt;/ol>
&lt;h2 id="format-your-sharepoint-lists-column">Format your SharePoint List&amp;rsquo;s column&lt;/h2>
&lt;p>After you did that, a &lt;code>.json&lt;/code> file is being downloaded. Now head over to the SharePoint list that you want to prettify!&lt;/p>
&lt;ol>
&lt;li>Select your field &amp;gt; &lt;strong>Column settings&lt;/strong> &amp;gt; &lt;strong>Format this column&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Advanced mode&lt;/strong>&lt;/li>
&lt;li>Delete the code in the box and paste the code that you just downloaded&lt;/li>
&lt;li>Select &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Now obviously, you will need to find the cool usecases for displaying SVG images in your List - most of them will make sense with some conditional formatting - like displaying an image only if a date column displays a certain date or month or if a choice column changes to a specific value. I hope my tool made the process of prettifying your apps a bit easier!&lt;/p></description></item><item><title>Display SVGs with multiple paths in a SharePoint list</title><link>https://m365princess.com/blogs/sp-plant/</link><pubDate>Sat, 05 Oct 2024 06:36:00 +0000</pubDate><guid>https://m365princess.com/blogs/sp-plant/</guid><description>&lt;p>I had this neat idea that depending on a value in a number column, the field would show a plant - a very small one for value 1, and then increasingly growing with more leaves up to value 4. To get you an idea on how to achieve this, here is the end result:&lt;/p>
&lt;p>&lt;img alt="SharePoint-plant sample" src="https://m365princess.com/images/plants.png">&lt;/p>
&lt;h2 id="the-svgs-">The SVGs 🪴&lt;/h2>
&lt;p>What we need for that is of course four similar SVGs. This is how I did that:&lt;/p>
&lt;ul>
&lt;li>I found mine on &lt;a href="https://www.canva.com/">Canva&lt;/a>&lt;/li>
&lt;li>separated them into individual SVGs,&lt;/li>
&lt;li>downloaded the files&lt;/li>
&lt;li>opened them in Edge&lt;/li>
&lt;li>hit the &lt;strong>F12&lt;/strong> key on my keyborad to inspect them&lt;/li>
&lt;li>extracted all the paths &lt;code>d&lt;/code> and &lt;code>fill-color&lt;/code> values&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="SVG in Edge" src="https://m365princess.com/images/svg-overview.png">&lt;/p>
&lt;h2 id="the-list">The List&lt;/h2>
&lt;p>Now let&amp;rsquo;s head over to SharePoint and&lt;/p>
&lt;ul>
&lt;li>Create a number column in a list - mine is called &lt;strong>Progress&lt;/strong>&lt;/li>
&lt;li>If you want to, you can limit values to from &lt;code>1&lt;/code> to &lt;code>4&lt;/code>&lt;/li>
&lt;li>Select your field &amp;gt; &lt;strong>Column settings&lt;/strong> &amp;gt; &lt;strong>Format this column&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Advanced mode&lt;/strong>&lt;/li>
&lt;li>Delete the code in the box and paste the code you can find in &lt;a href="https://github.com/LuiseFreese/sp-dev-list-formatting/blob/contributingguidance/column-samples/number-data-plant/number-data-plant.json">this file&lt;/a> (it&amp;rsquo;s nearly 800 lines, I will not make everyone scroll in here).&lt;/li>
&lt;/ul>
&lt;h2 id="how-the-magic-works">How the magic works&lt;/h2>
&lt;p>Let me explain what I did here:&lt;/p>
&lt;p>I defined 4 &lt;code>svg&lt;/code> elements, each of them carrying multiple paths of the SVG, being displayed under the condition of which value the field &lt;strong>Progress&lt;/strong> shows. Each child item contains then a path with &lt;code>attributes&lt;/code>: data (&lt;code>d&lt;/code>) and a &lt;code>style&lt;/code> with a &lt;code>fill&lt;/code> property:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;d&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;M 367.652344 905.644531 C 367.652344 905.644531 403.539062 879.605469 423.945312 855.675781 C 423.945312 855.675781 426.054688 856.378906 423.945312 859.898438 C 415.5 872.566406 390.871094 907.050781 371.871094 928.164062 C 371.167969 928.164062 366.242188 913.382812 367.652344 905.644531 Z M 367.652344 905.644531&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fill&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#9cc23c&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We need such a child item for each path of the SVG that we want to display. Then it is just a question of copy-pasting the data attribute and the color into the respective fields - Boom - done!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Takes a bit of perseverance, but the result is rather stunning!&lt;/p></description></item><item><title>How to apply modern card design in a SharePoint list with listformatting</title><link>https://m365princess.com/blogs/sp-card/</link><pubDate>Fri, 04 Oct 2024 15:43:58 +0000</pubDate><guid>https://m365princess.com/blogs/sp-card/</guid><description>&lt;p>I wanted to explore if I could implement a nice overview card in a SharePoint list column, that would display at a glance for example customer feedback, whom this is assigned to and also display to buttons which would initiate calls, emails, etc.&lt;/p>
&lt;p>The end result looks like this:&lt;/p>
&lt;p>&lt;img alt="Card design in SharePoint list" src="https://m365princess.com/images/sp-card.png">&lt;/p>
&lt;p>To achieve this we will first prep our list:&lt;/p>
&lt;ul>
&lt;li>Create columns as per your liking, I chose &lt;strong>Comment&lt;/strong> (Multiple lines of text), &lt;strong>Assigned to&lt;/strong> (Person) and &lt;strong>Product&lt;/strong> (Single line of text)&lt;/li>
&lt;li>Fill in a few rows that you actually see a difference once we play with code ✅&lt;/li>
&lt;li>Select the &lt;strong>Title&lt;/strong> column &amp;gt; &lt;strong>Column settings&lt;/strong> &amp;gt; &lt;strong>Format this column&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Advanced mode&lt;/strong>&lt;/li>
&lt;li>Delete the code in the box and paste the code:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;$schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;box-sizing&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;border-box&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px 0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;height&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;100%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;flex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;align-items&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;flex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;flex-direction&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;column&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#ffffff&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1px solid #edebe9&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border-radius&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;box-shadow&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0 2px 8px rgba(255, 105, 180, 0.3)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;max-width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;300px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;max-height&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;280px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;overflow&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;hidden&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;600&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;14px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-bottom&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#ff69b4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;[$Title]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-bottom&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#605e5c&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=&amp;#39;Product: &amp;#39; + [$Product]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-bottom&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#605e5c&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;max-height&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;80px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;overflow&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=&amp;#39;Comment: &amp;#39; + if([$Comment], [$Comment], &amp;#39;No comment&amp;#39;)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;flex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;align-items&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-bottom&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;100%&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;img&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;32px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;height&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;32px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border-radius&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;50%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-right&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;src&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$Assignedto.email], &amp;#39;/_layouts/15/userphoto.aspx?size=S&amp;amp;accountname=&amp;#39; + [$Assignedto.email], &amp;#39;&amp;lt;URL of your fallback image&amp;gt;&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=[$Assignedto.title]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;12px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#605e5c&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=&amp;#39;Assigned to: &amp;#39; + [$Assignedto.title]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;flex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;justify-content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;space-between&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-top&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;button&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px 8px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#ff69b4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#ffffff&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;none&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border-radius&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;cursor&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;pointer&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;margin-right&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;8px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;600&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;11px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;transition&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;background-color 0.2s ease&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;white-space&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nowrap&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;class&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;ms-fontWeight-semibold&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Celebrate with team&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customRowAction&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;executeFlow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;actionParams&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{\&amp;#34;id\&amp;#34;: \&amp;#34;&amp;lt;flow 1 id goes in here&amp;gt;\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;button&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px 8px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#40e0d0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;#ffffff&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;none&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;border-radius&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;cursor&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;pointer&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;600&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;font-size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;11px&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;transition&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;background-color 0.2s ease&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;white-space&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nowrap&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;attributes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;class&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;ms-fontWeight-semibold&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Set up emergency meeting&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customRowAction&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;executeFlow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;actionParams&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{\&amp;#34;id\&amp;#34;: \&amp;#34;&amp;lt;flow 2 id goes here&amp;gt;\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="how-the-magic-works">How the magic works&lt;/h2>
&lt;p>This code turns a boring SharePoint list item into an elegant card with a dynamic title, product info, comments, and even the assignee&amp;rsquo;s profile picture. It’s styled to look good with borders, shadows, and action buttons that can trigger Power Automate flows, like celebrating with the team or setting up a meeting. When I showed this to someone who never did listformatting so far, they asked me to explain this very thoroughly and in detail in this blog post - so here we go:&lt;/p>
&lt;h3 id="general-structure">General structure&lt;/h3>
&lt;ul>
&lt;li>The parent &lt;code>div&lt;/code> is like a container that holds other elements (like text, images, or buttons)&lt;/li>
&lt;li>The &lt;code>style&lt;/code> block applies styling to the container, like padding, height, and alignment. In this case, it ensures that the container takes up 100% of the height, aligns everything neatly in the center, and gives it some padding.&lt;/li>
&lt;/ul>
&lt;h3 id="child-elements-whats-inside-the-card">Child elements (what&amp;rsquo;s inside the card)&lt;/h3>
&lt;p>Now let&amp;rsquo;s look at what’s inside the card (the children array):&lt;/p>
&lt;ul>
&lt;li>Card Container: another &lt;code>div&lt;/code> inside with a border, and rounded corners to make it look like a neat card. It also has a max size and a shadow effect.&lt;/li>
&lt;li>&lt;code>Title&lt;/code> is styled as a bold, hot-pink (&lt;code>#ff69b4&lt;/code>) title. It displays the value from the Title column of your SharePoint list &lt;code>(with [$Title])&lt;/code>.&lt;/li>
&lt;li>&lt;code>Product&lt;/code> is a text block that displays the product associated with the item. It uses a formula &lt;code>(=&amp;quot;Product: &amp;quot; + [$Product])&lt;/code> to prepend the word &amp;ldquo;Product&amp;rdquo; before showing the value from the Product column&lt;/li>
&lt;li>&lt;code>Comment&lt;/code>: Here’s where the comment shows up. If there’s no comment, it will display &amp;ldquo;No comment&amp;rdquo; instead of leaving it blank. This is controlled by a simple if statement: &lt;code>if([$Comment], [$Comment], 'No comment')&lt;/code>.&lt;/li>
&lt;li>&lt;code>Assigned to&lt;/code> (Profile Picture + Name): displays a profile image and name of the person assigned to the task. It shows a small circular image (profile picture), and if no picture is available, it falls back to a generic person icon. The email is used to fetch the image &lt;code>(/userphoto.aspx?size=S&amp;amp;accountname=)&lt;/code>, and it also shows the person’s name next to it.&lt;/li>
&lt;li>Action buttons: At the bottom of the card, you’ve got two buttons:
&lt;ul>
&lt;li>Celebrate with team: This button when clicked, it runs a Power Automate flow with an ID specified in the &lt;code>actionParams&lt;/code>. It can do something like sending an email to the team or scheduling a meeting.&lt;/li>
&lt;li>Set up emergency meeting: This button triggers a different flow, maybe to create an emergency meeting or some other urgent task. Again, the flow ID is defined in &lt;code>actionParams&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="lets-take-action-the-power-automate-flow">Let&amp;rsquo;s take action: The Power Automate flow&lt;/h2>
&lt;p>Obviously, if we want something to happen on the click of one of the buttons (I know, they really look great already without any functionality, but still 😇), we will need to trigger a Power Automate flow.&lt;/p>
&lt;ul>
&lt;li>Create a new instant cloud flow in your default environment&lt;/li>
&lt;li>Add a &lt;strong>Create a Meeting&lt;/strong> action from the &lt;strong>Microsoft Teams&lt;/strong> connector&lt;/li>
&lt;li>Fill in the details as per your needs&lt;/li>
&lt;li>Save the flow&lt;/li>
&lt;li>Once the flow is saved, copy the flow id from the URL. The full URL will look like this: &lt;code>https://make.powerautomate.com/environments/Default-&amp;lt;your Azure Tenant-id&amp;gt;/flows/&amp;lt;this is your flow id&amp;gt;/details&lt;/code>&lt;/li>
&lt;li>Copy paste your flow ids into the json snippet - Boom - done!&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Flow overview" src="https://m365princess.com/images/flow-meeting.png">&lt;/p>
&lt;h3 id="a-few-remarks-though">A few remarks though&lt;/h3>
&lt;p>Mind, that my very simple flows shall just illustrate the idea to run a sequence of events on the click of a button in such an elegant card design in list. Here are a few suggestions:&lt;/p>
&lt;ul>
&lt;li>if you need context of the selected list item (and for instance get the customer name, their feedback or the assigned person from it), change the trigger to the SharePoint&amp;rsquo;s &lt;strong>For selected item&lt;/strong> trigger&lt;/li>
&lt;li>if you try to schedule a meeting so that everyone is actually free, you can utilze Microsoft Graph API&amp;rsquo;s &lt;a href="https://learn.microsoft.com/graph/api/user-findmeetingtimes?view=graph-rest-1.0&amp;tabs=javascript">user: findMeetingTimes&lt;/a>, it will need an array of users and of course a meeting duration. Once you got the slot, you can schedule the meeting easily. (Let me know if you want me to write a blog post on this.)&lt;/li>
&lt;/ul>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>A list, 2 flows and a json snippet is all you need for this beautiful and functional card layout! What are your use cases?&lt;/p></description></item><item><title>Populate SharePoint List multiple choice column with Microsoft 365 Groups-and add some List formatting</title><link>https://m365princess.com/blogs/sp-groups/</link><pubDate>Wed, 02 Oct 2024 06:36:00 +0000</pubDate><guid>https://m365princess.com/blogs/sp-groups/</guid><description>&lt;p>Recently, a customer asked me if I could automagically populate Microsoft 365 Group Names to a multiple choice column in a SharePoint list. Of course I told them that one can create an ootb Person field and allow group selection, but that was not quite what they wanted. So I built a simple Power Automate flow that would populate the choice column and applied some nice list formatting to it.&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>Have a SharePoint list&lt;/li>
&lt;li>Create a Choice column (I named mine &lt;code>Group&lt;/code>)&lt;/li>
&lt;li>Under &lt;strong>More options&lt;/strong> flip the switch &lt;strong>Allow multiple selections&lt;/strong> to &lt;code>Yes&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="the-power-automate-flow">The Power Automate flow&lt;/h2>
&lt;p>&lt;img alt="flow in full" src="https://m365princess.com/images/flow-full.png">&lt;/p>
&lt;p>We will use non-Premium connectors, first leveraging the Azure AD Group connector (&lt;em>time for a rename @Microsoft, huh?&lt;/em>) to get all the groups, then use the SharePoint connector to send a request via SharePoint REST API. Let&amp;rsquo;s get started!&lt;/p>
&lt;h3 id="get-your-groups">Get your groups&lt;/h3>
&lt;ul>
&lt;li>We trigger the flow as per our needs (manually, if there is not a lot of change in groups) or on a schedule (if there is a lot of changes in groups)&lt;/li>
&lt;li>we then initialize a variable (I used string, but you can also do an array) for the &lt;code>groupNames&lt;/code>&lt;/li>
&lt;li>then we use the Azure AD Groups - List Groups action to list all groups. Filter if needed!&lt;/li>
&lt;li>Then we append the &lt;code>Name&lt;/code> (and a comma) in a loop to our &lt;code>groupNames&lt;/code> variable&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="flow first part" src="https://m365princess.com/images/flow-groups.png">&lt;/p>
&lt;h3 id="obtain-listguid-and-fieldguid">Obtain listguid and fieldguid&lt;/h3>
&lt;p>Now we need to obtain 2 things&lt;/p>
&lt;ol>
&lt;li>the &lt;strong>listguid&lt;/strong> of our SharePoint list: Select the &lt;strong>Settings&lt;/strong> gear ⚙️ on your list, &lt;strong>List Settings&lt;/strong>, you will find it in the URL. The List guid sits right in between &lt;code>%7B&lt;/code> and&lt;code>%7D&lt;/code> (Of course there are a gazillion of other ways to obtain this, but that is a super easy one). If you like to, write this value into a variable - it looks just more neat and tidy!&lt;/li>
&lt;li>the &lt;strong>fieldguid&lt;/strong> of the field that we are trying to populate: You can either follow &lt;a href="https://medium.com/@hadimahmood777/sharepoint-essentials-getting-to-know-your-lists-column-guid-a7b46bdf7596">this post here&lt;/a> or stick with me and:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Use &lt;strong>Send an HTTP request to SharePoint&lt;/strong>
&lt;ul>
&lt;li>Method: Get&lt;/li>
&lt;li>URI:&lt;code>_api/web/Lists/GetById('@{variables('listGuid')}')/Fields&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>This will return a massive json object from which you can now find the &lt;code>id&lt;/code> of the field you are interested in. Again, use a variable if you like to!&lt;/p>
&lt;p>If you need this for several multiple choice columns, I suggest that you filter down your &lt;code>results&lt;/code> array to the column that you are interested in and then select the &lt;code>id&lt;/code> from that.&lt;/p>
&lt;h3 id="patch-the-choice-column">Patch the choice column&lt;/h3>
&lt;p>As a last step, we just patch our choice column with the groupNames. As we comma-separated the variable, we need to make sure that we remove that comma now:&lt;/p>
&lt;p>&lt;img alt="send http to SP" src="https://m365princess.com/images/flow-sp.png">&lt;/p>
&lt;ul>
&lt;li>Use &lt;strong>Send an HTTP request to SharePoint&lt;/strong>
&lt;ul>
&lt;li>&lt;strong>Method&lt;/strong>: &lt;code>Patch&lt;/code>&lt;/li>
&lt;li>&lt;strong>URI&lt;/strong>:&lt;code>_api/Web/Lists(guid'listGuid')/Fields(guid'fieldguid')&lt;/code>&lt;/li>
&lt;li>&lt;strong>Headers&lt;/strong>:
&lt;ul>
&lt;li>&lt;strong>Accept&lt;/strong>: &lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;li>&lt;strong>Content-type&lt;/strong>: &lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Body&lt;/strong>:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;__metadata&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;SP.FieldMultiChoice&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;FieldTypeKind&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;Choices&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">&amp;#39;results&amp;#39;:[@{substring(variables(&amp;#39;groupNames&amp;#39;),0,&lt;/span> &lt;span class="err">sub(length(variables(&amp;#39;groupNames&amp;#39;)),1))&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">]&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we now check in SharePoint, this already looks good, but I want to improve the experience with some colors.&lt;/p>
&lt;h2 id="some-list-formatting">Some List Formatting&lt;/h2>
&lt;p>What I want is a refection of which department is selected - remember that this is a multiple choice field? Let&amp;rsquo;s make that happen!
&lt;img alt="choice column" src="https://m365princess.com/images/sp-choice.png">&lt;/p>
&lt;ul>
&lt;li>Go tou your list&lt;/li>
&lt;li>Select the multiple choice field &amp;gt; &lt;strong>Column settings&lt;/strong> &amp;gt; &lt;strong>Format this column&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Advanced mode&lt;/strong>&lt;/li>
&lt;li>Delete the code in the box&lt;/li>
&lt;li>Paste in this snippet and select &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;$schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-Finance&amp;#39;) &amp;gt;= 0, &amp;#39;Dept-Finance&amp;#39;, &amp;#39;&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;inline-block&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;25%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-Finance&amp;#39;) &amp;gt;= 0, &amp;#39;#ffcc99&amp;#39;, &amp;#39;transparent&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;text-align&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;5px&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-IT&amp;#39;) &amp;gt;= 0, &amp;#39;Dept-IT&amp;#39;, &amp;#39;&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;inline-block&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;25%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-IT&amp;#39;) &amp;gt;= 0, &amp;#39;#ccffff&amp;#39;, &amp;#39;transparent&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;text-align&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;5px&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-HR&amp;#39;) &amp;gt;= 0, &amp;#39;Dept-HR&amp;#39;, &amp;#39;&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;inline-block&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;25%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-HR&amp;#39;) &amp;gt;= 0, &amp;#39;#ff9999&amp;#39;, &amp;#39;transparent&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;text-align&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;5px&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-Marketing&amp;#39;) &amp;gt;= 0, &amp;#39;Dept-Marketing&amp;#39;, &amp;#39;&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;inline-block&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;25%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;background-color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if(indexOf(@currentField, &amp;#39;Dept-Marketing&amp;#39;) &amp;gt;= 0, &amp;#39;#b3ffcc&amp;#39;, &amp;#39;transparent&amp;#39;)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;color&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;text-align&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;center&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;padding&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;5px&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="how-does-this-list-formatting-work">How does this list formatting work?&lt;/h3>
&lt;p>We separate into the number of &lt;code>&amp;lt;div&amp;gt;&lt;/code> we need (note this is just an example, there were way more groups to select from) and that set the &lt;code>width&lt;/code> to the percentage the &lt;code>&amp;lt;div&amp;gt;&lt;/code> may consume. The colors are applied dynamically using &lt;code>indexOf(@currentField, 'Dept-Name') &amp;gt;= 0&lt;/code>, which checks if the department is selected in the column. Each part of the cell shows the department name if it is selected. If the department is not selected, the &lt;code>&amp;lt;div&amp;gt;&lt;/code> will be empty and have no background color (&lt;code>transparent&lt;/code>).&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Good old &lt;strong>Send and HTTP request to SharePoint&lt;/strong> saved the day again! No need for app registrations, but a super straight forward flow that populates the multiple choice colum to our needs. Of course you don&amp;rsquo;t need to do groups, but can apply any array that you fits your use case!&lt;/p></description></item><item><title>Resource naming reloaded: Azure Policy and Bicep for the winner!</title><link>https://m365princess.com/blogs/resource-naming/</link><pubDate>Tue, 01 Oct 2024 06:36:00 +0000</pubDate><guid>https://m365princess.com/blogs/resource-naming/</guid><description>&lt;h2 id="lets-solidify-naming-conventions-with-azure-policy">Let’s solidify naming conventions with Azure Policy&lt;/h2>
&lt;p>In one of my last blog posts, &lt;a href="https://m365princess.com/blogs/devops-policy">How to use Azure Policy to enforce resource naming conventions in your DevOps pipelines&lt;/a> I explained how one could leverage Azure Policy to standardize naming of your Azure resources. But it would be too easy, if it was that easy. We have a few issues with that:&lt;/p>
&lt;ul>
&lt;li>some resources can&amp;rsquo;t handle the pattern that Microsoft suggests - for example Storage Accounts can only have lowercase letters and numbers in the name and only up to 24 characters&lt;/li>
&lt;li>if we want people to stick to a certain pattern it&amp;rsquo;s easier if they can choose from a list of valid values&lt;/li>
&lt;/ul>
&lt;p>So we will parameterize the resource name with in Organization Unit, the appName that the requester provides us with, the location, the appType, the environment and the instance. This is how that looks like in Bicep:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span> &lt;span class="n">orgUnit&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;Contoso&amp;#39;&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Organization&lt;/span> &lt;span class="n">Unit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span> &lt;span class="n">appName&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="n">provided&lt;/span> &lt;span class="n">by&lt;/span> &lt;span class="n">the&lt;/span> &lt;span class="n">requester&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">@allowed&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;weu&amp;#39;&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Short&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">West&lt;/span> &lt;span class="n">Europe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;eus&amp;#39;&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Short&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">East&lt;/span> &lt;span class="n">US&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span> &lt;span class="n">shortLocation&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Selectable&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">naming&lt;/span> &lt;span class="n">purposes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">@allowed&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;storageaccount&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;webapp&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;functionapp&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;logicapp&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;sqlserver&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;cosmosdb&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span> &lt;span class="n">appType&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Selectable&lt;/span> &lt;span class="n">app&lt;/span> &lt;span class="nb">type &lt;/span>&lt;span class="n">from&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="n">predefined&lt;/span> &lt;span class="n">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">@allowed&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Dev&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Demo&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Test&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Prod&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span> &lt;span class="n">environment&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Environment&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">g&lt;/span>&lt;span class="p">.,&lt;/span> &lt;span class="s1">&amp;#39;Prod&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Dev&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">etc&lt;/span>&lt;span class="p">.)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">@allowed&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;01&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;02&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;03&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;04&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;05&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span> &lt;span class="n">instanceNumber&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Instance&lt;/span> &lt;span class="n">number&lt;/span> &lt;span class="n">or&lt;/span> &lt;span class="n">other&lt;/span> &lt;span class="n">identifier&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Mapping&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">short&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">codes&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">region&lt;/span> &lt;span class="n">names&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">var&lt;/span> &lt;span class="n">locationMapping&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">weu&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;westeurope&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eus&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;eastus&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Get&lt;/span> &lt;span class="n">the&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">var&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">locationMapping&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="no">shortLocation&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Base&lt;/span> &lt;span class="n">resource&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="n">using&lt;/span> &lt;span class="n">short&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">var&lt;/span> &lt;span class="n">baseResourceName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;${orgUnit}-${appName}-${shortLocation}-${appType}-${environment}-${instanceNumber}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Exception&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">storage&lt;/span> &lt;span class="n">account&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">var&lt;/span> &lt;span class="n">storageAccountName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">toLower&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;${appName}${environment}${instanceNumber}&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">var&lt;/span> &lt;span class="n">truncatedStorageAccountName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">length&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">storageAccountName&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="mf">24&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">storageAccountName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">24&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="err">:&lt;/span> &lt;span class="n">storageAccountName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Final&lt;/span> &lt;span class="n">resource&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="n">with&lt;/span> &lt;span class="n">exception&lt;/span> &lt;span class="n">handling&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">var&lt;/span> &lt;span class="n">resourceName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;storageaccount&amp;#39;&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="n">truncatedStorageAccountName&lt;/span> &lt;span class="err">:&lt;/span> &lt;span class="n">baseResourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Resource&lt;/span> &lt;span class="n">declaration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">Storage&lt;/span> &lt;span class="n">Account&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resource&lt;/span> &lt;span class="n">storageAccount&lt;/span> &lt;span class="s1">&amp;#39;Microsoft.Storage/storageAccounts@2021-04-01&amp;#39;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;storageaccount&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">location&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Use&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sku&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;Standard_LRS&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">kind&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;StorageV2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Resource&lt;/span> &lt;span class="n">declaration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">Web&lt;/span> &lt;span class="n">App&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resource&lt;/span> &lt;span class="n">webApp&lt;/span> &lt;span class="s1">&amp;#39;Microsoft.Web/sites@2021-02-01&amp;#39;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;webapp&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">location&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Use&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">kind&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;app&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">properties&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">serverFarmId&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;your-app-service-plan-id&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Resource&lt;/span> &lt;span class="n">declaration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="kd">Function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">App&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resource&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="nb">App&lt;/span> &lt;span class="s1">&amp;#39;Microsoft.Web/sites@2021-02-01&amp;#39;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;functionapp&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">location&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Use&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">kind&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;functionapp&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">properties&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">serverFarmId&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;your-app-service-plan-id&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Resource&lt;/span> &lt;span class="n">declaration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">Logic&lt;/span> &lt;span class="n">App&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resource&lt;/span> &lt;span class="n">logicApp&lt;/span> &lt;span class="s1">&amp;#39;Microsoft.Logic/workflows@2019-05-01&amp;#39;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;logicapp&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">location&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Use&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">properties&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">definition&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;$schema&amp;#39;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">actions&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">contentVersion&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;1.0.0.0&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">outputs&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">parameters&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">triggers&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">parameters&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{}&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Add&lt;/span> &lt;span class="n">your&lt;/span> &lt;span class="n">logic&lt;/span> &lt;span class="n">app&lt;/span> &lt;span class="n">parameters&lt;/span> &lt;span class="n">here&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Resource&lt;/span> &lt;span class="n">declaration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">SQL&lt;/span> &lt;span class="n">Server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resource&lt;/span> &lt;span class="n">sqlServer&lt;/span> &lt;span class="s1">&amp;#39;Microsoft.Sql/servers@2021-05-01-preview&amp;#39;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;sqlserver&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">location&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Use&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">properties&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">administratorLogin&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;your-admin-login&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">administratorLoginPassword&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;your-admin-password&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Resource&lt;/span> &lt;span class="n">declaration&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">an&lt;/span> &lt;span class="n">Azure&lt;/span> &lt;span class="n">Cosmos&lt;/span> &lt;span class="n">DB&lt;/span> &lt;span class="n">Account&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resource&lt;/span> &lt;span class="n">cosmosDb&lt;/span> &lt;span class="s1">&amp;#39;Microsoft.DocumentDB/databaseAccounts@2021-04-15&amp;#39;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">appType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s1">&amp;#39;cosmosdb&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">location&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="p">//&lt;/span> &lt;span class="n">Use&lt;/span> &lt;span class="n">full&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">deployment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">kind&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;GlobalDocumentDB&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">properties&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">databaseAccountOfferType&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s1">&amp;#39;Standard&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">locations&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="n">fullLocation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">//&lt;/span> &lt;span class="n">Output&lt;/span> &lt;span class="n">the&lt;/span> &lt;span class="n">generated&lt;/span> &lt;span class="n">resource&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="n">and&lt;/span> &lt;span class="n">location&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">verification&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">output&lt;/span> &lt;span class="n">resourceName&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">resourceName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">output&lt;/span> &lt;span class="n">fullLocation&lt;/span> &lt;span class="n">string&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fullLocation&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save this as &lt;code>main.bicep&lt;/code>, We can now deploy this using Azure CLI:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$resourceGroup&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your resourcegroup&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">az&lt;/span> &lt;span class="n">deployment&lt;/span> &lt;span class="nb">group &lt;/span>&lt;span class="n">create&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-resource-group&lt;/span> &lt;span class="nv">$resourceGroup&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-template&lt;/span>&lt;span class="o">-file&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">bicep&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you run the CLI command, you will notice that you now get prompted for the parameters and for those that only allow a choice of values, you can choose but not use any free text. The template will now craft the resourceName out of the provided values:&lt;/p>
&lt;p>As a result, we get nicely named resources:&lt;/p>
&lt;p>&lt;img alt="Logic app in Azure Portal" src="https://m365princess.com/images/named-resource.png">&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Don&amp;rsquo;t get me wrong though! Having Azure Policy in place ensures, that regardless which way someone chooses to create resources, they need to stick to the naming pattern that you defined! So while this template makes it easy to deploy resources via Bicep, people could still use the Azure Portal and do what they want - if there is no policy in place! So in fact, this is yet another better together scenario! So in case you don&amp;rsquo;t have a policy so far, please go back to my &lt;a href="https://m365princess.com/blogs/devops-policy">previous blog post&lt;/a> and set up a policy that fulfills your needs of consistent naming.&lt;/p></description></item><item><title>How to rotate secrets with Azure Logic Apps, Key Vault and Managed Identity</title><link>https://m365princess.com/blogs/secret-rotation/</link><pubDate>Mon, 30 Sep 2024 06:36:00 +0000</pubDate><guid>https://m365princess.com/blogs/secret-rotation/</guid><description>&lt;h2 id="do-we-really-need-to-rotate-secrets">Do we REALLY need to rotate secrets?&lt;/h2>
&lt;p>Ever wonder why we’re always harping on about rotating secrets in Microsoft Entra App Registrations? Rotating secrets is kind of like changing the locks on your house. Sure, you might trust your neighbors, but what if someone made a copy of your key without you knowing? Yikes, right?&lt;/p>
&lt;p>That&amp;rsquo;s pretty much what we&amp;rsquo;re dealing with when it comes to secrets in the cloud. Unfortunately, sometimes these secrets can accidentally end up in places they shouldn&amp;rsquo;t, like that one time you left your house key under the doormat (we&amp;rsquo;ve all been there, no judgment). So, rotating these secrets is our way of staying one step ahead of the bad people. But hold up - is it always necessary? In case you can use &lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview">Managed Identities&lt;/a>, it is not - as then Microsoft manages secrets for you and you don&amp;rsquo;t even have access to the secrets. But that is not always possible and lots of people asked me how one could rotate secrets. Happy to explain! So in case you ever wondered how you could rotate secret automagically 🦄, then this blog post is for you.&lt;/p>
&lt;h2 id="create-a-logic-app-that-will-rotate-secrets">Create a Logic App that will rotate secrets&lt;/h2>
&lt;p>We will do it as simple and as secure as possible: We will use a Managed Identity in a Logic App that will create a new secret and store in a Key Vault. Why? For me everything starts with identity - so if we needed an app registration to change an app registrations secret, that would be a chicken/egg problem, as this app registration&amp;rsquo;s secret needed to be rotated as well. So a good way to prevent this secret-ception would be a Managed Identity. If we want to use a Managed Identity and still stay low-code, the easiest way to do that, is to call Power Automate&amp;rsquo;s big sister - &lt;a href="https://learn.microsoft.com/azure/logic-apps/logic-apps-overview">Azure Logic Apps&lt;/a> and leverage two HTTP actions to create and store secrets securely.&lt;/p>
&lt;p>Our Logic App will look like this&lt;/p>
&lt;p>&lt;img alt="Logic App overview" src="https://m365princess.com/images/logicapp-overview.png">&lt;/p>
&lt;p>You can create for now an empty placeholder Logic App in the portal, with Bicep, or right away with the definition I provided a bit further down in this post. Once you did that, let&amp;rsquo;s create our Managed Identity.&lt;/p>
&lt;h2 id="create-a-managed-identity">Create a Managed Identity&lt;/h2>
&lt;p>I chose a user-assigned Managed Identity here:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Define our variables&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ResourceGroupName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your resource group&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$Location&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your preferred location&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$LogicAppName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;name of your Logic App&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$UserAssignedIdentityName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;Name of the managed Identity&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SubscriptionId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;Id of your Azure Subscription&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$GraphAppId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;00000003-0000-0000-c000-000000000000&amp;#34;&lt;/span> &lt;span class="c"># don&amp;#39;t change! &lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$KeyVaultName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;name of the Key Vault&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Create the user-assigned managed identity&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">az&lt;/span> &lt;span class="n">identity&lt;/span> &lt;span class="n">create&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-resource-group&lt;/span> &lt;span class="nv">$ResourceGroupName&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-name&lt;/span> &lt;span class="nv">$UserAssignedIdentityName&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-location&lt;/span> &lt;span class="nv">$Location&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Get the User-Assigned Managed Identity ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$UserAssignedIdentityId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">az&lt;/span> &lt;span class="n">identity&lt;/span> &lt;span class="n">show&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-resource-group&lt;/span> &lt;span class="nv">$ResourceGroupName&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-name&lt;/span> &lt;span class="nv">$UserAssignedIdentityName&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-query&lt;/span> &lt;span class="n">id&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-output&lt;/span> &lt;span class="n">tsv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Assign the User-Assigned Managed Identity to the Logic App&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">az&lt;/span> &lt;span class="n">logic&lt;/span> &lt;span class="kd">workflow&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">identity&lt;/span> &lt;span class="n">assign&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-resource-group&lt;/span> &lt;span class="nv">$ResourceGroupName&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-name&lt;/span> &lt;span class="nv">$LogicAppName&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-user-assigned&lt;/span> &lt;span class="nv">$UserAssignedIdentityId&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="create-an-azure-key-vault">Create an Azure Key Vault&lt;/h2>
&lt;p>Next up, we will create our Azure Key Vault&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$KeyVault&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">New-AzKeyVault&lt;/span> &lt;span class="n">-ResourceGroupName&lt;/span> &lt;span class="nv">$ResourceGroupName&lt;/span> &lt;span class="n">-VaultName&lt;/span> &lt;span class="nv">$KeyVaultName&lt;/span> &lt;span class="n">-Location&lt;/span> &lt;span class="nv">$Location&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="grant-access-to-graph-api-and-the-key-vault-to-a-managed-identity">Grant access to Graph API and the Key Vault to a Managed Identity&lt;/h3>
&lt;p>Now let&amp;rsquo;s solve how we can access our resources! We want two things to be assigned to the Managed Identity:&lt;/p>
&lt;ol>
&lt;li>Microsoft Graph permissions &lt;code>Application.ReadWrite.All&lt;/code> (this will allow us to add secrets to an App registration)&lt;/li>
&lt;li>Azure Role assignment (RBAC) &lt;code>Key Vault Administrator&lt;/code> and &lt;code>Key Vault Secrets User&lt;/code> (this will allow us to read from and push secrets to the Key Vault)&lt;/li>
&lt;/ol>
&lt;h4 id="assign-microsoft-graph-permissions-on-a-managed-identity">Assign Microsoft Graph permissions on a Managed Identity&lt;/h4>
&lt;p>This is the script that does this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Connect-MgGraph&lt;/span> &lt;span class="n">-TenantId&lt;/span> &lt;span class="nv">$DestinationTenantId&lt;/span> &lt;span class="n">-Scopes&lt;/span> &lt;span class="nv">$MgRequiredScopes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$AssignPermissions&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Application.ReadWrite.All&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$MgRequiredScopes&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Application.Read.All&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;AppRoleAssignment.ReadWrite.All&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Directory.Read.All&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ManagedIdentityObjectId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-MgServicePrincipal&lt;/span> &lt;span class="n">-Filter&lt;/span> &lt;span class="s2">&amp;#34;displayName eq &amp;#39;&lt;/span>&lt;span class="nv">$UserAssignedIdentityName&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ServicePrincipal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-MgServicePrincipal&lt;/span> &lt;span class="n">-Filter&lt;/span> &lt;span class="s2">&amp;#34;appId eq &amp;#39;&lt;/span>&lt;span class="nv">$GraphAppId&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$AppRole&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$ServicePrincipal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">AppRoles&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Where-Object&lt;/span> &lt;span class="p">{(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Value&lt;/span> &lt;span class="n">-in&lt;/span> &lt;span class="nv">$AssignPermissions&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">AllowedMemberTypes&lt;/span> &lt;span class="o">-contains&lt;/span> &lt;span class="s2">&amp;#34;Application&amp;#34;&lt;/span>&lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$AppRole&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nv">$AppRole&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$AppRoleAssignment&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;PrincipalId&amp;#34;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$ManagedIdentityObjectId&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ResourceId&amp;#34;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$ServicePrincipal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;AppRoleId&amp;#34;&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$AppRole&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">New-MgServicePrincipalAppRoleAssignment&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">-ServicePrincipalId&lt;/span> &lt;span class="nv">$AppRoleAssignment&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">PrincipalId&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">-BodyParameter&lt;/span> &lt;span class="nv">$AppRoleAssignment&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">-Verbose&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="assign-azure-role-assignments-to-a-managed-identity">Assign Azure Role assignments to a Managed Identity&lt;/h4>
&lt;p>Now we want to assign the &lt;code>Key Vault Secrets User&lt;/code> and the &lt;code>Key Vault Administrator&lt;/code> role to our Managed Identity:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Assign the managed identity the Key Vault Secrets User role&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-AzRoleAssignment&lt;/span> &lt;span class="n">-ObjectId&lt;/span> &lt;span class="nv">$ManagedIdentityObjectId&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Id&lt;/span> &lt;span class="n">-RoleDefinitionName&lt;/span> &lt;span class="s2">&amp;#34;Key Vault Secrets User&amp;#34;&lt;/span> &lt;span class="n">-Scope&lt;/span> &lt;span class="nv">$KeyVault&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">ResourceId&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Assign the managed identity the Key Vault Administrator role&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-AzRoleAssignment&lt;/span> &lt;span class="n">-ObjectId&lt;/span> &lt;span class="nv">$ManagedIdentityObjectId&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Id&lt;/span> &lt;span class="n">-RoleDefinitionName&lt;/span> &lt;span class="s2">&amp;#34;Key Vault Administrator&amp;#34;&lt;/span> &lt;span class="n">-Scope&lt;/span> &lt;span class="nv">$KeyVault&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">ResourceId&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once we have that, let&amp;rsquo;s take care of our Logic app. In case you did not already create the entire definition, you can now update it. Make sure that - if you use the Designer - you select the correct managed Identity for authentication.&lt;/p>
&lt;p>💡 You can find the json definition in this &lt;a href="https://gist.github.com/LuiseFreese/3cbe51ac00fd4ff1212e257b0074838f">gist&lt;/a>.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>What this does is exactly what we wanted: Every 30 days (you can change that to any other value) it will create a new secret and store it in the Azure Key Vault using a Managed Identity. If you like to, you can also delete the old secret. But as this automatically expires, there is no need for this :-) Now I&amp;rsquo;m curios: How do you rotate secrets?&lt;/p></description></item><item><title>How to use Azure Policy to enforce resource naming conventions in your DevOps pipelines</title><link>https://m365princess.com/blogs/devops-policy/</link><pubDate>Wed, 25 Sep 2024 06:36:00 +0000</pubDate><guid>https://m365princess.com/blogs/devops-policy/</guid><description>&lt;h2 id="lets-talk-about-azure-naming-conventions">Lets talk about Azure naming conventions&lt;/h2>
&lt;p>I know, I know, you are probably thinking, &amp;ldquo;Seriously? We&amp;rsquo;re gonna talk about names?&amp;rdquo; But trust me, this stuff matters.&lt;/p>
&lt;p>&lt;img alt="dilbert-naming" src="https://m365princess.com/images/dilbert-name.png">&lt;/p>
&lt;h2 id="why-should-you-care-about-names">Why Should You Care About Names?&lt;/h2>
&lt;p>Think about it like this: imagine you&amp;rsquo;ve got a massive garage full of tools, but nothing&amp;rsquo;s labeled. Nightmare, right? That&amp;rsquo;s kinda what it&amp;rsquo;s like when you&amp;rsquo;ve got a ton of Azure resources with random names. It&amp;rsquo;s a mess, and nobody wants to deal with that.&lt;/p>
&lt;p>Good names make life easier. You can tell at a glance what something is, where it belongs, and what it&amp;rsquo;s for. Plus, when it comes to tracking costs or making sure you&amp;rsquo;re following the rules, good names are a lifesaver.&lt;/p>
&lt;h2 id="what-microsoft-says-about-it">What Microsoft Says About It&lt;/h2>
&lt;p>Microsoft&amp;rsquo;s got some ideas about &lt;a href="https://learn.microsoft.com/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming">how to name stuff in Azure&lt;/a>. They suggest something like this:&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;lt;org&amp;gt;-&amp;lt;proj&amp;gt;-&amp;lt;env&amp;gt;-&amp;lt;region&amp;gt;-&amp;lt;resource-type&amp;gt;-&amp;lt;instance&amp;gt;
&lt;/code>&lt;/pre>&lt;p>So, if you&amp;rsquo;re working on a web app for Contoso, it might look like:&lt;/p>
&lt;pre tabindex="0">&lt;code>contoso-webapp-prod-eastus-app-01
&lt;/code>&lt;/pre>&lt;p>Not too shabby, right? You can tell it&amp;rsquo;s for Contoso, it&amp;rsquo;s a web app, it&amp;rsquo;s in production, sitting in East US, and it&amp;rsquo;s an app service. The &amp;lsquo;01&amp;rsquo; at the end is just in case you need more than one.&lt;/p>
&lt;h2 id="the-problem-with-doing-this-manually">The problem with doing this manually&lt;/h2>
&lt;p>Trying to make sure everyone follows these rules manually is a pain in the butt. People forget, they get lazy, or they just don&amp;rsquo;t care. And then you&amp;rsquo;ve got a mess on your hands.&lt;/p>
&lt;p>Plus, going through and checking all the names by hand? Boring. And fixing mistakes after the fact? Even worse.&lt;/p>
&lt;h2 id="how-to-make-it-suck-less">How to make it suck less&lt;/h2>
&lt;p>This is where the cool part comes in. We can use Azure CLI and Azure Policy to do the heavy lifting for us. Basically, we set up a system that checks the names before anything gets created. If the name&amp;rsquo;s wrong, it doesn&amp;rsquo;t let it through.&lt;/p>
&lt;p>Here&amp;rsquo;s the general idea:&lt;/p>
&lt;ol>
&lt;li>Figure out what pattern you want your names to follow - welcome to Regex hell!&lt;/li>
&lt;li>Write a little script that checks if a name matches that pattern&lt;/li>
&lt;li>Hook that script in your Azure DevOps pipeline&lt;/li>
&lt;/ol>
&lt;p>Now, every time someone tries to create something, it has to pass the name check first. Pretty slick, huh? But if we just wrote the script in DevOps, then it would only apply if someone tries to deploy resources via DevOps. It wouldn&amp;rsquo;t catch non compliant resources that are created for example in the Azure Portal. Our hero? It&amp;rsquo;s once again Azure Policy! Azure Policy makes sure that regardless how a resource is being created, it needs to comply to the policy that you set.&lt;/p>
&lt;h3 id="use-azure-policy">Use Azure Policy&lt;/h3>
&lt;p>We have several options now&amp;hellip; either create the policy with Azure CLI or deploy the policy definition and policy assignment with Bicep.&lt;/p>
&lt;p>I&amp;rsquo;ll cover how to use Bicep for this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">targetScope&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;subscription&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">policyDefinition&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.Authorization/policyDefinitions@2021-06-01&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;enforce-resource-naming&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Enforce resource naming pattern&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">mode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;All&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">policyRule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">field&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">notMatch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;^[a-zA-Z0-9]{1,}-[a-zA-Z0-9]{1,}-[a-zA-Z0-9]{1,}-[a-zA-Z0-9]{1,}-[a-zA-Z0-9]{1,}-[a-zA-Z0-9]{1,}$&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">then&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">effect&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;deny&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">parameters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">category&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Naming&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">policyAssignment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.Authorization/policyAssignments@2021-06-01&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;enforce-resource-naming-assignment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Enforce resource naming pattern assignment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">policyDefinitionId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">policyDefinition&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">scope&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">subscription&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we now deploy a resource:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">storageAccount&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.Storage/storageAccounts@2021-02-01&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;contoso-webapp-prod-weu-stg-01&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// This name must comply with the policy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;westeurope&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">sku&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Standard_LRS&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;StorageV2&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">accessTier&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Hot&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Azure policy will check if the name complies. If not, it will block the deployment due to the &lt;strong>deny&lt;/strong> in the policy.&lt;/p>
&lt;p>You can also check your previously created resources with&lt;/p>
&lt;p>&lt;code>az policy state list --query &amp;quot;[?complianceState=='NonCompliant']&amp;quot;&lt;/code>&lt;/p>
&lt;h2 id="make-it-work-in-your-devops-pipeline">Make it work in your DevOps pipeline&lt;/h2>
&lt;p>And the best part? You can hook it right into your DevOps pipeline. So before you deploy anything, you can double-check that it&amp;rsquo;s all the way you wanted it in the first place. It runs the &lt;code>az policy state list&lt;/code> command to check whether any resources are non-compliant. If violations are detected, the pipeline exits with a non zero status which causes the pipeline to fail.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yml" data-lang="yml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">trigger&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="l">main&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">pool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">vmImage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;ubuntu-latest&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">variables&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">azureSubscription&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Your-Service-Connection&amp;gt;&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resourceGroupName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Your resource group name&amp;gt;&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;westeurope&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Install Azure CLI and Bicep&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AzureCLI@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">azureSubscription&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$(azureSubscription)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;bash&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;inlineScript&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inlineScript&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> # Install Azure CLI Bicep extension
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> az bicep install&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Install Bicep CLI&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Deploy Bicep File to Azure&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AzureCLI@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">azureSubscription&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$(azureSubscription)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;bash&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;inlineScript&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inlineScript&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;Deploying Bicep template...&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> az group create --name $(resourceGroupName) --location $(location)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> # Deploy Bicep file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> az deployment group create \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --resource-group $(resourceGroupName) \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --template-file main.bicep&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Deploy Bicep Template&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Check Azure Policy Compliance&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AzureCLI@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">azureSubscription&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$(azureSubscription)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;bash&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;inlineScript&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inlineScript&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;Checking Azure Policy compliance...&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> # Check for any non-compliant resources in the resource group
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> compliance_results=$(az policy state list --resource-group $(resourceGroupName) --query &amp;#34;[?complianceState==&amp;#39;NonCompliant&amp;#39;].{resourceId:resourceId, policyAssignmentName:policyAssignment}&amp;#34; --output table)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> if [ -n &amp;#34;$compliance_results&amp;#34; ]; then
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;Non-compliant resources detected:&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;$compliance_results&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> exit 1 # Fail the pipeline if there are non-compliant resources
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> else
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;All resources are compliant with Azure Policy.&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> fi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Check Policy Compliance&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Naming conventions aren&amp;rsquo;t exactly the most thrilling topic. But trust me, getting this stuff right can save you a ton of headaches down the road. It&amp;rsquo;s like organizing your garage – it&amp;rsquo;s a pain to do, but once it&amp;rsquo;s done, life gets so much easier. Azure Policy does the heavy lifting here for us and hooking that approach into your Azure DevOps pipeline will just make sure that you&amp;rsquo;ll never ever have to think about naming again. You are welcome!&lt;/p></description></item><item><title>How to automate vulnerability scans in Azure DevOps with Microsoft Defender for Cloud</title><link>https://m365princess.com/blogs/devops-defender/</link><pubDate>Tue, 24 Sep 2024 08:25:03 +0000</pubDate><guid>https://m365princess.com/blogs/devops-defender/</guid><description>&lt;hr>
&lt;p>You know how it goes. You are working on a project, pushing code left and right, and then someone asks, &amp;ldquo;But is it secure?&amp;rdquo; Cue the collective groan. Well, what if I told you there&amp;rsquo;s a way to bake security right into your development process without slowing everything down to a crawl?&lt;/p>
&lt;p>Enter &lt;a href="https://learn.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction">Microsoft Defender for Cloud&lt;/a> and Azure DevOps. Together, they&amp;rsquo;re like the dream team of cloud security. We can integrate Microsoft Defender for Cloud into our Azure DevOps pipeline, which means that we can perform vulnerability scans as part of our CI/CD process so that every deployment is scanned for security issues before it goes live.&lt;/p>
&lt;p>That means:&lt;/p>
&lt;ul>
&lt;li>no more &amp;ldquo;oops&amp;rdquo; moments (remember my blog about how to get from Dev?! Oops! to DevOps?)&lt;/li>
&lt;li>consistency as every build gets the same treatment and it doesn&amp;rsquo;t depend on the someone&amp;rsquo;s mood, how close lunch break is or other very human factors what gets scanned&lt;/li>
&lt;li>speeding things up as automated scans are way faster than Bob from IT manually checking everything (Sorry Bob!)&lt;/li>
&lt;/ul>
&lt;p>I see too often that security scans are an afterthought or that scans are performed after a deployment hits production. So they just hope for the best.&lt;/p>
&lt;h2 id="what-is-defender-for-cloud">What is Defender for Cloud&lt;/h2>
&lt;p>(and why would I care?)&lt;/p>
&lt;p>Microsoft Defender for Cloud is a robust security solution that&amp;rsquo;s becoming increasingly important in the Azure ecosystem. It acts as a comprehensive monitoring system for your cloud resources, continuously scanning for potential vulnerabilities and threats. It doesn&amp;rsquo;t just alert you to issues, but provides actionable recommendations to improve your security.&lt;/p>
&lt;h2 id="a-bit-of-prep-work">A bit of prep work&lt;/h2>
&lt;p>Before we can leverage this approach, we need to check some prerequisites&lt;/p>
&lt;ul>
&lt;li>Azure subscription with Microsoft Defender for Cloud turned on&lt;/li>
&lt;li>A Azure DevOps account and project with some code in a repo&lt;/li>
&lt;li>The right permissions (you&amp;rsquo;ll need to be a &lt;strong>Contributor&lt;/strong> or &lt;strong>Owner&lt;/strong>)&lt;/li>
&lt;li>Microsoft Defender for Cloud plan that covers your resources (there is a free trial)&lt;/li>
&lt;li>A Log Analytics Workspace (If you don&amp;rsquo;t know how to do this, &lt;a href="https://learn.microsoft.com/training/modules/create-log-analytics-workspace-microsoft-defender-cloud/">here is a tutorial by Microsoft Learn&lt;/a>)&lt;/li>
&lt;li>A way for Azure DevOps to talk to Azure (like a Service Principal or Managed Identity)&lt;/li>
&lt;/ul>
&lt;h2 id="defender-for-cloud-setup">Defender for Cloud Setup&lt;/h2>
&lt;p>First things first, let&amp;rsquo;s turn on Microsoft Defender for Cloud:&lt;/p>
&lt;ul>
&lt;li>Open &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a> and find &lt;strong>Microsoft Defender for Cloud&lt;/strong>&lt;/li>
&lt;li>Switch on the Defender plans for the services you care about (e.g., VMs, App Services, Key Vaults, SQL database)&lt;/li>
&lt;/ul>
&lt;p>Now, let&amp;rsquo;s set up Continuous Export:&lt;/p>
&lt;ul>
&lt;li>Find &lt;strong>Environment Settings&lt;/strong> and then &lt;strong>Continuous export&lt;/strong>&lt;/li>
&lt;li>Enable the export of security recommendations and alerts to a Log Analytics workspace.&lt;/li>
&lt;/ul>
&lt;h2 id="devops-pipeline-magic">DevOps Pipeline Magic&lt;/h2>
&lt;p>Now for the fun part – setting up the pipeline that&amp;rsquo;ll do our security scans:&lt;/p>
&lt;p>We need to give our pipeline a way to talk to Azure:&lt;/p>
&lt;ul>
&lt;li>Use either a Service Principal or a Managed Identity to create a Service Connection&lt;/li>
&lt;li>Create a new pipeline (or edit an existing one) with this yaml snippet:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yml" data-lang="yml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">trigger&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="l">main&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">pool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">vmImage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;ubuntu-latest&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">variables&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">group&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AzureSecurityVariables&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">AzureCLI@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">azureSubscription&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;AzureSecurityConnection&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;bash&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scriptLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;inlineScript&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inlineScript&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;Starting vulnerability scan with Microsoft Defender for Cloud&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> # Query the Log Analytics workspace for any critical security recommendations
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> result=$(az monitor log-analytics query \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --workspace $(logAnalyticsWorkspaceId) \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --analytics-query &amp;#34;SecurityRecommendation | where Severity == &amp;#39;High&amp;#39;&amp;#34; \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --timespan P1D \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --output tsv)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> if [[ -n &amp;#34;$result&amp;#34; ]]; then
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;High-severity security vulnerabilities found:&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;$result&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;##vso[task.complete result=Failed;]Security vulnerabilities detected.&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> else
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;No critical vulnerabilities found!&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> fi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>💡 Make sure that you create a variable for your &lt;code>logAnalyticsWorkspaceId&lt;/code> - you can obtain it from the Azure Portal&lt;/p>
&lt;p>Our pipeline does a few cool things:&lt;/p>
&lt;ul>
&lt;li>It logs into Azure using your Service Principal (you know I love managed Identities for a variety of good reasons, but I wanted to keep this blog post focused on DevOps and Defender for Cloud so let&amp;rsquo;s cover DevOps and Managed Identity in a future post)&lt;/li>
&lt;li>It checks your Log Analytics workspace for any high-severity security recommendations from the last day&lt;/li>
&lt;li>If it finds anything scary (Severity &lt;code>High&lt;/code>), it&amp;rsquo;ll fail the pipeline and show you what it found&lt;/li>
&lt;/ul>
&lt;p>Run your pipeline, if it tells that &amp;ldquo;No critical vulnerabilities found&amp;rdquo; you are good to go, otherwise, it will stop the pipeline and exactly tell you what it found.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Scans do not need to be performed after deployment, with everyone getting hectic or rolling back to previous versions. Microsoft Defender for Cloud got your back and you can integrate it into your DevOps scenario.&lt;/p></description></item><item><title>Go Go governance! Enforcing Azure Policies with Azure CLI</title><link>https://m365princess.com/blogs/azure-policy/</link><pubDate>Sun, 22 Sep 2024 08:25:03 +0000</pubDate><guid>https://m365princess.com/blogs/azure-policy/</guid><description>&lt;hr>
&lt;p>This post is part of a series about Deployments, Role Assignments and more!&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/blogs/loganalytics">How to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/azure-container-registry">How to use Azure Container Registry to standardize deployments using Bicep across your organization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/secure-azure-rbac">How to secure access to an Azure Container Registry with RBAC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/secure-managed-identity">How to secure access to an Azure Container Registry with a Managed Identity&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/rbac-abac">Azure RBAC is so 2023! Let’s get ABAC to the rescue!&lt;/a>&lt;/li>
&lt;li>📍 &lt;strong>you are here&lt;/strong> - &lt;a href="https://m365princess.com/blogs/azure-policy">Go Go governance! Enforcing Azure Policies with Azure CLI&lt;/a>&lt;/li>
&lt;li>How to utilize the Azure Container Registry in your Azure DevOps pipeline - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>In my last post about ABAC I showed that we can assign a role to resources that are tagged with for example with &lt;code>Department == Finance&lt;/code> or &lt;code>Project == DeathStar&lt;/code>? Well that only makes sense if noone forgets to tag resources correctly, right?&lt;/p>
&lt;p>If you&amp;rsquo;ve worked with Azure for any amount of time, you know how easy it is for things to get out of control. Different teams deploying resources, some in the wrong regions, others without proper tags—before you know it, your cloud setup looks like a mess. That&amp;rsquo;s where &lt;a href="https://learn.microsoft.com/azure/governance/policy/overview">&lt;strong>Azure Policies&lt;/strong>&lt;/a> come in to save the day.&lt;/p>
&lt;p>Think of Azure Policies as guardrails that help you keep everything in line. They make sure that everyone follows the rules you set. No more resources popping up in unapproved regions or missing critical information like cost tags.&lt;/p>
&lt;h3 id="the-workaround-and-why-it-doesnt-work">The Workaround (and Why It Doesn&amp;rsquo;t Work)&lt;/h3>
&lt;p>Manually enforcing these rules by trusting teams to follow best practices or having endless spreadsheets and documents for tracking resource usage, doesn&amp;rsquo;t work (Ask me how I know 😇). Without policies, you&amp;rsquo;d rely on documentation, hope (🤞🤞), and good intentions. Maybe you&amp;rsquo;d have a workaround where people manually check if resources are in the right region or tag everything properly. Sorry to break it to you, but that ain&amp;rsquo;t a proper process. Azure Policies automate all of that, so you don’t have to chase people down. Once you set a policy, it just works. If someone tries to break the rules, the policy will stop them. No manual enforcement needed. The policy stops non-compliant resources from being created in the first place.&lt;/p>
&lt;blockquote>
&lt;p>It&amp;rsquo;s not a trick, it&amp;rsquo;s an Azure policy&lt;/p>
&lt;/blockquote>
&lt;h3 id="what-well-do-here">What We&amp;rsquo;ll Do Here&lt;/h3>
&lt;p>Today, we&amp;rsquo;re going to enforce two simple, but super effective policies:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://m365princess.com/blogs/azure-policy/#lets-enforce-a-location-policy-for-the-entire-subscription">Everything in your subscription has to be deployed in West Europe&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/azure-policy/#enforcing-a-tagging-policy-for-a-specific-resource-group">All resources in a specific resource group need a &lt;code>CostCenter&lt;/code> tag (because tracking cloud costs is important, right?).&lt;/a>&lt;/li>
&lt;/ol>
&lt;h3 id="lets-enforce-a-location-policy-for-the-entire-subscription">Let’s Enforce a Location Policy for the Entire Subscription&lt;/h3>
&lt;p>First up, we&amp;rsquo;ll make sure everything in your Azure subscription is deployed in &lt;code>westeurope&lt;/code>.&lt;/p>
&lt;h4 id="create-the-location-policy">Create the Location Policy&lt;/h4>
&lt;p>The Azure CLI makes this process easy. First, we’ll create a policy that only allows resources to be deployed in &lt;code>westeurope&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create the policy definition in Azure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">az policy definition create &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --name &lt;span class="s2">&amp;#34;AllowedLocations&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --display-name &lt;span class="s2">&amp;#34;Enforce West Europe Location&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --description &lt;span class="s2">&amp;#34;Ensures that resources are only deployed in West Europe.&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --rules &lt;span class="s1">&amp;#39;{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;if&amp;#34;: {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;field&amp;#34;: &amp;#34;location&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;notEquals&amp;#34;: &amp;#34;westeurope&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> },
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;then&amp;#34;: {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;effect&amp;#34;: &amp;#34;Deny&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">}&amp;#39;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --mode All
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This policy checks the location of any new resource, and if it’s not set to &lt;code>westeurope&lt;/code>, the creation gets blocked.&lt;/p>
&lt;h4 id="assign-the-policy-to-your-subscription">Assign the Policy to Your Subscription&lt;/h4>
&lt;p>Now that we&amp;rsquo;ve created the policy, we need to assign it to the whole subscription to make sure it applies everywhere.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Get your subscription ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SUBSCRIPTION_ID&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>az account show --query &lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span> -o tsv&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Assign the policy to the subscription&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">az policy assignment create &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --name &lt;span class="s2">&amp;#34;RequireWestEurope&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --policy &lt;span class="s2">&amp;#34;AllowedLocations&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --scope &lt;span class="s2">&amp;#34;/subscriptions/&lt;/span>&lt;span class="nv">$SUBSCRIPTION_ID&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --display-name &lt;span class="s2">&amp;#34;Enforce West Europe Location&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With this in place, no matter how someone tries to create a resource (CLI, portal, etc.), if it&amp;rsquo;s not in West Europe, it won&amp;rsquo;t happen. 🎤💧⬇️&lt;/p>
&lt;h3 id="enforcing-a-tagging-policy-for-a-specific-resource-group">Enforcing a Tagging Policy for a Specific Resource Group&lt;/h3>
&lt;p>Now let’s focus on enforcing tags for a particular resource group. Let’s say you have a resource group where every resource needs a &lt;code>CostCenter&lt;/code> tag, so you can track cloud spending effectively.&lt;/p>
&lt;h4 id="create-the-tagging-policy">Create the Tagging Policy&lt;/h4>
&lt;p>Here’s the policy that checks if the &lt;code>CostCenter&lt;/code> tag is present. If it’s not, the policy blocks the creation of that resource.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Create the policy definition in Azure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">az policy definition create --name &lt;span class="s2">&amp;#34;EnforceCostcenterTag&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--rules &lt;span class="s1">&amp;#39;{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;if&amp;#34;:{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;field&amp;#34;:&amp;#34;tags.Costcenter&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;exists&amp;#34;:&amp;#34;false&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> },
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;then&amp;#34;: {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> &amp;#34;effect&amp;#34;:&amp;#34;Deny&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> }&amp;#39;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--mode All &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--display-name &lt;span class="s2">&amp;#34;Enforce Costcenter Tag on All Resources&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This policy checks each resource, and if it’s missing the &lt;code>CostCenter&lt;/code> tag, the policy kicks in and denies its creation.&lt;/p>
&lt;h4 id="assign-the-tagging-policy-to-a-resource-group">Assign the Tagging Policy to a Resource Group&lt;/h4>
&lt;p>Now, assign this policy to a specific resource group where you need to enforce tagging.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define the resource group name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">RESOURCE_GROUP_NAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;YourResourceGroup&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SUBSCRIPTION_ID&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>az account show --query &lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span> -o tsv&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SCOPE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/subscriptions/&lt;/span>&lt;span class="nv">$SUBSCRIPTION_ID&lt;/span>&lt;span class="s2">/resourceGroups/&lt;/span>&lt;span class="nv">$RESOURCE_GROUP_NAME&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Assign the policy to the resource group&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">az policy assignment create &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --name &lt;span class="s2">&amp;#34;RequireCostCenterTag&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --policy &lt;span class="s2">&amp;#34;EnforceCostCenterTag&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --scope &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$SCOPE&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --display-name &lt;span class="s2">&amp;#34;Require CostCenter Tag on Resources&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From now on, if someone tries to create a resource in this group without a &lt;code>CostCenter&lt;/code> tag, they’ll get stopped right away.&lt;/p>
&lt;h3 id="why-this-works-for-every-deployment-method">Why This Works for Every Deployment Method&lt;/h3>
&lt;p>What makes Azure Policies powerful is that they work no matter &lt;strong>how&lt;/strong> you deploy resources&lt;/p>
&lt;ul>
&lt;li>If someone uses the &lt;strong>Azure Portal&lt;/strong>, they’ll be blocked if they try to create a non-compliant resource&lt;/li>
&lt;li>Using &lt;strong>Azure CLI&lt;/strong>? The policy still applies!&lt;/li>
&lt;li>Even if you’re using &lt;strong>Infrastructure as Code&lt;/strong> (like Bicep or Terraform), the policies will ensure that every resource meets your standards.&lt;/li>
&lt;/ul>
&lt;h3 id="sneak-peek-into-azure-devops-pipelines">Sneak Peek into Azure DevOps pipelines&lt;/h3>
&lt;p>In a future post, I&amp;rsquo;ll explain how these policies play an important role when integrating Azure DevOps pipelines into your deployment workflows. Imagine automating your entire deployment process, and still having these policies enforce compliance on every resource that gets created. No more manual checks, just smooth, policy-driven deployments.&lt;/p>
&lt;p>Stay tuned 🤖&lt;/p></description></item><item><title>Azure RBAC is so 2023! Let’s get ABAC to the rescue!</title><link>https://m365princess.com/blogs/rbac-abac/</link><pubDate>Sat, 21 Sep 2024 08:25:03 +0000</pubDate><guid>https://m365princess.com/blogs/rbac-abac/</guid><description>&lt;hr>
&lt;p>This post is part of a series about Deployments, Role Assignments and more!&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/loganalytics">How to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/azure-container-registry">How to use Azure Container Registry to standardize deployments using Bicep across your organization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/secure-azure-rbac">How to secure access to an Azure Container Registry with RBAC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/secure-managed-identity">How to secure access to an Azure Container Registry with a Managed Identity&lt;/a>&lt;/li>
&lt;li>📍 &lt;strong>you are here&lt;/strong> - &lt;a href="https://m365princess.com/rbac-abac">Azure RBAC is so 2023! Let’s get ABAC to the rescue!&lt;/a>&lt;/li>
&lt;li>How to utilize the Azure Container Registry in your Azure DevOps pipeline - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>In the last post, I showed you how assigning an RBAC role to a Managed Identity saves you some password rotation related tasks, and I promised to write the next post about &lt;a href="https://learn.microsoft.com/azure/role-based-access-control/conditions-overview">ABAC&lt;/a>. So here we go:&lt;/p>
&lt;h2 id="quick-recap-rbac">Quick recap: RBAC&lt;/h2>
&lt;p>Azure Role-Based Access Control (RBAC) lets admins assign roles to users, groups, and services to control access to Azure resources. With roles like &lt;strong>Owner&lt;/strong>, &lt;strong>Contributor&lt;/strong> and &lt;strong>Reader&lt;/strong>, RBAC makes sure everyone has just the right permissions to get their jobs done, which cuts down on admin headaches.&lt;/p>
&lt;p>But as businesses (and their cloud maturity) grow, so do their access control needs. If you’ve been managing permissions with RBAC you know it’s fabulous for broad, static access. But what happens when your access requirements start getting more complex? Enter ABAC (Attribute-Based Access Control): It’s the next level of fine-grained, dynamic access control.&lt;/p>
&lt;blockquote>
&lt;p>ABAC adds context.&lt;/p>
&lt;/blockquote>
&lt;p>Instead of just assigning permissions based on a user’s role, you can now use attributes like department, project, or even location to grant access to specific resources. Think of it as RBAC with superpowers.&lt;/p>
&lt;h2 id="a-real-world-scenario-using-rbac-and-abac-together">A Real-World Scenario: Using RBAC and ABAC Together&lt;/h2>
&lt;p>Let’s say you’re running a multi-tenant SaaS app for different departments like Finance, Sales, and HR, and you need to limit access within each department. RBAC helps you set up basic roles (like &lt;strong>Storage Contributor&lt;/strong> or &lt;strong>Reader&lt;/strong>), but it doesn’t go far enough when users or apps only need access to specific resources, like data tagged with &lt;code>Finance&lt;/code> or &lt;code>Project Deathstar&lt;/code>.&lt;/p>
&lt;p>That’s where ABAC comes in. You can assign broad roles with RBAC, and then fine-tune access with ABAC policies. Here’s how that works:&lt;/p>
&lt;ul>
&lt;li>RBAC Role Assignment: John from Finance gets the &lt;strong>Storage Contributor&lt;/strong> role, allowing him to manage all storage accounts in the Finance resource group&lt;/li>
&lt;li>ABAC Policy: We add a rule that says John can only access resources with &lt;code>project=Deathstar&lt;/code> tags. So even though his role allows him broader access, ABAC restricts him to only what&amp;rsquo;s relevant to his project.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>That is exactly what principle of least privilege means!&lt;/p>
&lt;/blockquote>
&lt;p>Often times, security ad convenience are perceived as a sea-saw. The more secure convenient something is for a user (or an admin, or a developer), the less convenient it is. The more convenient something feels, the more e can expect it to be insecure. With ABAC we balance the two approaches - making access secure AND convenient at the very same time.&lt;/p>
&lt;p>ABAC allows you to adapt permissions on the fly based on user and resource attributes, making it perfect for dynamic environments where things change frequently – like employees working across regions or switching between projects.&lt;/p>
&lt;h2 id="assigning-abac-roles-with-azure-cli">Assigning ABAC Roles with Azure CLI&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli"># Variables
$subscriptionId = &amp;#34;&amp;lt;your subscription id&amp;gt;&amp;#34;
$resourceGroupName = &amp;#34;rg-building-blocks&amp;#34;
$userEmail = &amp;#34;&amp;lt;email of the user&amp;gt;&amp;#34;
$roleName = &amp;#34;Storage Blob Data Contributor&amp;#34;
$storageAccountName = &amp;#34;&amp;lt;name of the storage account&amp;gt;&amp;#34;
# Get the user object ID using the user email
$userObjectId = $(az ad user show --id $userEmail --query &amp;#34;id&amp;#34; --output tsv)
# Define the scope
$scope = &amp;#34;/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName&amp;#34;
# Define the condition
$condition = &amp;#34;@Resource[Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags:Project&amp;lt;`$key_case_sensitive`$&amp;gt;] StringEquals &amp;#39;Deathstar&amp;#39;&amp;#34;
$conditionVersion = &amp;#34;2.0&amp;#34;
# Assign the role with the condition
az role assignment create --assignee $userObjectId --role $roleName --scope $scope --condition $condition --condition-version &amp;#34;2.0&amp;#34;
&lt;/code>&lt;/pre>&lt;p>We can check in the Azure Portal that this worked:&lt;/p>
&lt;p>&lt;img alt="role condition" src="https://m365princess.com/images/role-condition.png">&lt;/p>
&lt;p>This ABAC policy ensures that our user only gets access if the tag is &lt;code>Project == Deathstar&lt;/code>.&lt;/p>
&lt;p>💡 If you want t try this out, make sure you do in fact have such a tag! (Ask me how I know 😇)&lt;/p>
&lt;h2 id="conclusion-better-together">Conclusion: Better Together&lt;/h2>
&lt;p>Instead of asking yourself now if RBAC or ABAC is better: RBAC and ABAC aren’t competitors – they’re complementary. RBAC handles the heavy lifting of defining broad access, while ABAC gives you the flexibility to refine access based on real-world conditions. Think of ABAC as a precision tool you can use alongside RBAC when things get a little more complex.&lt;/p></description></item><item><title>Why you shouldn’t say 'please' or 'thank you' to AI (and why it matters)</title><link>https://m365princess.com/blogs/ai-please/</link><pubDate>Fri, 20 Sep 2024 08:25:03 +0000</pubDate><guid>https://m365princess.com/blogs/ai-please/</guid><description>&lt;p>We’ve all been there: asking ChatGPT, Copilot or whatever AI, for something and instinctively saying “please” or “thank you.” It feels polite, right? But AI doesn’t care. Talking to it like it’s a person isn’t helping anyone, and it might actually harm how we think about these systems.&lt;/p>
&lt;p>We humans love to project human traits onto things that aren’t human - a concept called &amp;ldquo;anthropomorphism&amp;rdquo;. We name our cars, swear at our laptops when they freeze, and now, we talk to AI as if it has thoughts and feelings. AI, however, doesn’t think, feel, or care about your mood. When we say “please” or “thank you” to it, we’re blending the lines between what’s human and what’s not.&lt;/p>
&lt;p>This tendency to anthropomorphize AI is everywhere. We refer to AI as “learning”, &amp;ldquo;reasoning&amp;rdquo; or even “understanding”, but in reality, these systems aren’t experiencing anything like a human would. We talk about AI this way because it helps us make sense of the complex tech behind it. But in doing so, we risk overestimating its capabilities and assuming it has qualities - like empathy or understanding - that it simply doesn’t.
This leads to problems:&lt;/p>
&lt;ol>
&lt;li>it creates false expectations: We’re subtly reinforcing the idea that it understands us in the same way a human would. It doesn’t. AI processes data and performs tasks. It’s not reading between the lines, and it doesn’t “feel” appreciated when you’re polite.&lt;/li>
&lt;li>it distorts what AI can really do; anthropomorphizing AI can make us think it’s more capable than it is. We might assume AI can “learn” like a human or make decisions with some form of understanding. But that’s not the case.&lt;/li>
&lt;li>it shifts responsibility away from us. The more we treat AI like it’s human, the more we might let go of our own responsibility. AI can’t be held accountable for its actions or biases - we can.&lt;/li>
&lt;/ol>
&lt;p>Oh yeah, how could I forget “Being Nice to the Future Overlords”? Some of us joke about being polite to AI “just in case they become our future robot overlords.” The idea is that if AI takes over, those of us who were nice to them will get a better deal. It’s a funny thought, but it also fuels this whole Terminator-style narrative where AI is this malevolent force waiting to exploit us. That’s not what AI is about - treating it like a potential overlord makes it harder to understand what AI really is: a tool designed by humans, for humans. When we treat AI like it&amp;rsquo;s something with power or agency, we buy into this dystopian idea of AI taking control, which really just distracts us from thinking about AI responsibly. The killer robot storyline harms our ability to grasp the real issues around AI, like biases, privacy, and ethical usage.&lt;/p>
&lt;p>If we want to use AI responsibly, we need to understand it better. It&amp;rsquo;s not enough to not anthropomorphize it - We need to know how to interact with it to advance society.&lt;/p></description></item><item><title>How to secure access to an Azure Container registry with a Managed Identity and RBAC</title><link>https://m365princess.com/blogs/secure-managed-identity/</link><pubDate>Tue, 17 Sep 2024 08:25:03 +0000</pubDate><guid>https://m365princess.com/blogs/secure-managed-identity/</guid><description>&lt;hr>
&lt;p>This post is part of a series&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/loganalytics">How to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/azure-container-registry">How to use Azure Container Registry to standardize deployments using Bicep across your organization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/secure-azure-rbac">How to secure access to an Azure Container Registry with RBAC&lt;/a>&lt;/li>
&lt;li>📍 &lt;strong>you are here&lt;/strong> - &lt;a href="https://m365princess.com/secure-managed-identity">How to secure access to an Azure Container Registry with a Managed Identity&lt;/a>&lt;/li>
&lt;li>How to utilize the Azure Container Registry in your Azure DevOps pipeline - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>I like to deploy my Azure resources using Bicep - If you never heard about it, I blogged a while ago on &lt;a href="https://www.m365princess.com/blogs/start-deploying-azure-resources-bicep/">how to get started with Bicep&lt;/a> - please catch up first!&lt;/p>
&lt;hr>
&lt;p>In the last post, I showed you how assigning an RBAC role to a service principle can be used to make sure that someone can only pull images from an Azure Container Registry. This post will cover how to secure your Azure Container Registry with a Managed Identity.&lt;/p>
&lt;h2 id="what-is-a-managed-identity">What is a Managed Identity?&lt;/h2>
&lt;p>If you don&amp;rsquo;t know about Managed Identities yet: a &lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview">Managed Identity&lt;/a> gives your app an identity in the cloud without the need to register an app in Microsoft Entra (the artist formerly known as Azure Active Directory). This means, that you don&amp;rsquo;t need to create secrets or certificates and you also don&amp;rsquo;t need to store secrets or rotate them - Microsoft manages that for you. Because if we are all being honest: Yes, we know that we &lt;em>should&lt;/em> rotate secrets. But not enough organizations do this. Managed Identities come in 2 flavors: system assigned
and user assigned.&lt;/p>
&lt;ul>
&lt;li>System assigned means that they belong to a certain Azure resource and share its lifecycle&lt;/li>
&lt;li>User-assigned means, that they are resource on their own and can be assigned to one or more other resources.&lt;/li>
&lt;/ul>
&lt;p>I covered more about this &lt;a href="https://www.m365princess.com/blogs/rid-key-vault-making-good/">in this blog post&lt;/a>&lt;/p>
&lt;p>Now let&amp;rsquo;s get started:&lt;/p>
&lt;h2 id="a-little-bit-of-prep-work">A little bit of prep work&lt;/h2>
&lt;p>To assign a Managed Identity to an Azure Container Registry we need have an Azure Container Registry in the first place. If you followed the blog post series - this should already be done. If not - please catch up now.&lt;/p>
&lt;p>After that, we create the user-assigned Managed Identity itself:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">$Managed_Identity = &amp;#34;&amp;lt;name of your managed identity&amp;gt;&amp;#34;
az identity create --resource-group $RESOURCE_GROUP --name $Managed_Identity
&lt;/code>&lt;/pre>&lt;p>You can check in the Azure portal that it does in fact exist:&lt;/p>
&lt;p>&lt;img alt="managed identity" src="https://m365princess.com/images/acr-managed-identity.png">&lt;/p>
&lt;h3 id="assign-managed-identity-to-acr">Assign Managed Identity to ACR&lt;/h3>
&lt;p>Now let&amp;rsquo;s assign our Managed Identity to the Azure Container Registry&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
az acr identity assign --name $ACR_NAME --identities $Managed_Identity --resource-group $RESOURCE_GROUP
&lt;/code>&lt;/pre>&lt;h3 id="create-the-role-assignment">Create the role assignment&lt;/h3>
&lt;p>As a last step, we need to create the RBAC in our identity. To do this, we need the &lt;strong>principalId&lt;/strong> property (aka the Object ID of our Managed Identity)&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">$PRINCIPAL_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $Managed_Identity --query &amp;#34;principalId&amp;#34; --output tsv)
&lt;/code>&lt;/pre>&lt;p>Now we will use this to create the role assignment:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">az role assignment create --assignee $PRINCIPAL_ID --role &amp;#34;AcrPull&amp;#34; --scope &amp;#34;/subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP&amp;#34;
&lt;/code>&lt;/pre>&lt;p>That&amp;rsquo;s it! Let&amp;rsquo;s quickly check in the Azure portal that it worked and we are done:&lt;/p>
&lt;p>&lt;img alt="rbac assignment in Managed Identity" src="https://m365princess.com/images/identity-rbac.png">&lt;/p>
&lt;h2 id="the-elephant-in-the-room">The elephant in the room&lt;/h2>
&lt;p>You might now ask - if I am such a fan of IaC, why wouldn&amp;rsquo;t we then deploy the Managed Identity with a bicep file (preferably published to an Azure Container Registry for easy use by all developers in the organization)? I&amp;rsquo;m so glad you asked! 😇&lt;/p>
&lt;p>I love to have more than just one tool under my belt. For quick demos or to try out things during first phase of development, I usually use Azure CLI to get started. It&amp;rsquo;s the easiest way to create and modify the resources I need.&lt;/p>
&lt;p>If my solution then scales, I like to have proper Bicep files in place, so that deployments are so repeat and we can easily track what exactly we deployed via source control.
So if you want to create a Bicep file for a Managed Identity, here is the module that I would use:&lt;/p>
&lt;p>Let&amp;rsquo;s first get the role id for &lt;code>AcrPull&lt;/code>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
$ACRPULL_ROLE_ID=$(az role definition list --name acrpull --query &amp;#34;[0].id&amp;#34; --output tsv)
$roleId = (az role definition list --name &amp;#34;AcrPull&amp;#34; --query &amp;#34;[0].id&amp;#34; --output tsv)
$ACRPULL_ROLE_ID = $roleId.Split(&amp;#39;/&amp;#39;)[-1]
&lt;/code>&lt;/pre>&lt;h3 id="main-bicep-file-mainbicep">Main Bicep file (main.bicep)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceGroupName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">subscriptionId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">resourceGroup&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrpullRoleId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityModule&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;managedIdentity.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;managedIdentityDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">roleAssignmentModule&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;roleAssignment.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;roleAssignmentDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityPrincipalId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityModule&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">outputs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">principalId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">acrName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">subscriptionId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">subscriptionId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">acrpullRoleId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrpullRoleId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="managed-identity-module-managedidentitybicep">Managed Identity Module (managedIdentity.bicep)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">resourceGroup&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentity&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="role-assignment-module">Role Assignment module&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityPrincipalId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">subscriptionId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrpullRoleId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">var&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">resourceId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">subscriptionId&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.ContainerRegistry/registries&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">roleAssignment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.Authorization/roleAssignments@2020-04-01-preview&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">guid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">acrId&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityPrincipalId&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;AcrPull&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">roleDefinitionId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;/subscriptions/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">subscriptionId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s">/providers/Microsoft.Authorization/roleDefinitions/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">acrpullRoleId&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s">&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">principalId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityPrincipalId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">scope&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">acrId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can now either deploy this from here (locally) with Azure CLI or you publish these modules to an Azure Container Registry so that others can consume them from there easily. I described &lt;a href="https://m365princess.com/azure-container-registry">earlier in this series&lt;/a> how the latter works; here is the Azure CLI command:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">$ACRPULL_ROLE_ID = (az role definition list --name &amp;#34;AcrPull&amp;#34; --query &amp;#34;[0].id&amp;#34; --output tsv).Split(&amp;#39;/&amp;#39;)[-1]
az deployment group create --resource-group $RESOURCE_GROUP --template-file main.bicep --parameters resourceGroupName=$RESOURCE_GROUP subscriptionId=$subscriptionId managedIdentityName=$Managed_Identity acrName=$ACR location=$location acrpullRoleId=$ACRPULL_ROLE_ID
&lt;/code>&lt;/pre>&lt;p>Boom - done ✅&lt;/p>
&lt;p>You now have two ways to automatically create a Managed Identity, and assign the RBAC to it.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>We can now use the Managed identity instead of our service principal and don&amp;rsquo;t need to worry anymore that we really don&amp;rsquo;t care about secret rotation. Azure Key Vault is already good to store credentials, but following best practices we would want to regularly rotate them. This is now automagically being take care of by Microsoft :-)&lt;/p>
&lt;h2 id="ps">PS&lt;/h2>
&lt;p>When I mentioned &amp;ldquo;Writing ablog post about Bicep and RBAC&amp;rdquo;, &lt;a href="https://www.linkedin.com/in/jan-fellien/">Janek Fellien&lt;/a> very casually told me that RBAC is so 2023 and that &lt;a href="https://learn.microsoft.com/azure/role-based-access-control/conditions-overview">ABAC&lt;/a> is the hottest shit. Let me know if I should find a good scenario for that and write about it!&lt;/p></description></item><item><title>Hasta la vista?! About how we picture AI</title><link>https://m365princess.com/blogs/ai-terminator/</link><pubDate>Sun, 15 Sep 2024 08:25:03 +0000</pubDate><guid>https://m365princess.com/blogs/ai-terminator/</guid><description>&lt;p>Ever searched for “AI” on a stock image site or even used an AI tool to generate an image of “AI”? I can’t help but noticing three recurring themes:&lt;/p>
&lt;ul>
&lt;li>robots that look like the Terminator&lt;/li>
&lt;li>hypersexualized “sexy” robots&lt;/li>
&lt;li>cute, cartoonish androids&lt;/li>
&lt;/ul>
&lt;p>It’s like we’re stuck in a loop, constantly cycling through the same old clichés. And it’s not just stock images; even AI-generated visuals tend to follow these patterns.&lt;/p>
&lt;p>There are some deeper socio-cultural reasons behind the depiction of AI - capitalism’s love for familiar ideas, the way tech infantilizes users, and even outdated sexism. All of this is rooted in people’s fear of AI taking over, a fear that The Terminator helped cement. But now, with AI tools becoming accessible to nearly everyone, there’s an even bigger conversation around the kind of AI we’re building - models that threaten to change the workforce as we know it.&lt;/p>
&lt;p>Visuals of the Terminator-style robot are instantly recognizable and pack a punch. The red-eyed robot is a symbol of the danger of AI and has become a kind of visual shorthand for artificial intelligence. This touches on a real fear: the idea that AI could surpass human control and, like in The Terminator, threaten humanity. While we’re not necessarily expecting killer robots to come for us (🫰🫰🫰), that image sticks with us, and it sells.&lt;/p>
&lt;p>Even AI image generators, which should theoretically offer something new, fall back on these familiar tropes because they’re learning from the data we’ve already created. They scrape through millions of images online, and since our media and advertising spaces are already full of these familiar robots, they just reinforce them. AI reflects our existing biases and sells us back the same stories we’ve been telling for decades.&lt;/p>
&lt;p>On the other side of the spectrum, we see another type of AI imagery - robots that are almost too cute. Big eyes, soft colors, and friendly, toy-like appearances. These images are designed to make AI seem approachable and harmless, a far cry from the intimidating robots of dystopian movies.&lt;/p>
&lt;p>This is part of a bigger trend where tech companies work hard to present AI as a friendly, easy-to-use tool. By making AI look cute and non-threatening, they make the tech feel accessible, something anyone can use without needing to worry about the complexities behind it. This approach infantilizes users, making it seem like we don’t need to think too hard about what’s going on under the hood.&lt;/p>
&lt;p>But AI isn’t a toy. It’s a powerful technology that’s already affecting jobs, privacy, and decision-making in ways that matter. When we constantly see AI depicted as a cute, harmless assistant, we stop asking the important questions about ethics, control, and long-term impacts. And AI-generated images, learning from these same visual cues, end up reinforcing this &amp;ldquo;friendly robot&amp;rdquo; narrative, even when the reality is far more complicated.&lt;/p>
&lt;p>One of the most frustrating trends in AI visuals is the hypersexualized “sexy” robot. These robots are usually female-presenting, sleek, and designed with exaggerated features. You see this type of imagery not only in stock image libraries but also in AI-generated visuals, regardless which tools you use.&lt;/p>
&lt;p>This societal issue persists in and reflects on tech. When AI is depicted as a hypersexualized female robot, it reinforces harmful ideas that women (or, in this case, feminized robots) exist to serve or entertain.&lt;/p>
&lt;p>Even in our most futuristic visions, the “sexy robot” trope reflects a culture that still struggles to see women as equals in tech and innovation. Instead, they’re often depicted as objects of desire or subservience, reinforcing sexist stereotypes even in a space that should be about pushing boundaries and imagining new possibilities.&lt;/p>
&lt;p>While most of us don’t actually expect robot overlords anytime soon, there’s a real fear behind this: that AI could grow beyond our control.&lt;/p>
&lt;p>And now, we’re living in a time when that fear is starting to feel a little too close for comfort. AI isn’t just a cool futuristic idea anymore; it’s here, and it’s becoming more powerful every day. We now have large language models (LLMs), large action models (LAMs), and supposedly &amp;ldquo;reasoning&amp;rdquo; models that can automate processes we thought only humans could handle. These tools can write, code, create, analyze, and even make decisions - essentially doing more and more of what we thought only people could do.&lt;/p>
&lt;p>This is where things get tricky. As AI becomes more accessible, available for nearly everyone to use and experiment with, we start to feel the pressure. Companies are adopting these tools at breakneck speed, and some of these models are already threatening to replace human jobs. AI is no longer just the future; it’s the present, and it’s here to stay.&lt;/p>
&lt;p>The original fear—that AI could take over—might not look exactly like The Terminator, but it’s real. AI tools are already reshaping industries, changing the workforce, and shifting how we think about the future of work. And when you have AI creating images of AI, it’s no wonder we keep seeing these fear-based visuals pop up—they’re reflecting our very real concerns about AI’s role in society.&lt;/p>
&lt;p>So, what’s the solution? It starts with how we think about and visualize AI. We need to break out of this loop and start demanding images that reflect the full complexity of AI - not just the dystopian, overly simplified, or hypersexualized versions we’ve been fed for decades. At least: stop using these images to to perpetuate the false narrative even further.&lt;/p></description></item><item><title>AI can now REASON?! tl;dr: no, it cant!</title><link>https://m365princess.com/blogs/devops-ai-reason/</link><pubDate>Thu, 12 Sep 2024 06:36:00 +0000</pubDate><guid>https://m365princess.com/blogs/devops-ai-reason/</guid><description>&lt;p>Another day, another AI model drop! This time, it’s the &lt;a href="https://openai.com/o1/">OpenAI o1 series&lt;/a>, and wow, the hype is all over my feed 🙄 Is this the breakthrough in reasoning we&amp;rsquo;ve all been waiting for? 🤔&lt;/p>
&lt;p>OpenAI claims these models are designed for coding, math, and science. Supposedly, they’re better at reasoning through tough problems. But wait, what?! They aren’t actually reasoning. What they are doing is mimicking reasoning using a technique called &amp;ldquo;chain-of-thought&amp;rdquo; processing. How this works? Instead of jumping straight to an answer, these models break down a problem step-by-step, processing more data (tokens) and taking longer to respond. This mimics how humans reason through problems, but at the end of the day, it’s still advanced pattern recognition – the model is just generating text based on its training, not &amp;ldquo;thinking&amp;rdquo; like we are 💡&lt;/p>
&lt;p>In theory, this should lead to more accurate outputs in areas like coding and math, where getting things wrong is a big issue. But there are trade-offs:&lt;/p>
&lt;ul>
&lt;li>Reduced context windows due to the model using tokens for &amp;ldquo;reasoning steps&amp;rdquo;&lt;/li>
&lt;li>Longer wait times for responses (something between few seconds to few minutes)&lt;/li>
&lt;li>And yes, more money – these models are far more expensive to run&lt;/li>
&lt;/ul>
&lt;p>For now, they’re only in preview (with very strict usage limits) and limited to text-based tasks.&lt;/p>
&lt;p>To me, the bigger takeaway is the shift toward specialized models. Instead of trying to make one model do everything, OpenAI is focusing on building models for specific, high-demand tasks like coding, math, and science. Maybe this specialization is the real innovation here.&lt;/p>
&lt;p>What do you think?&lt;/p></description></item><item><title>How to secure access to an Azure Container registry with RBAC</title><link>https://m365princess.com/blogs/secure-azure-rbac/</link><pubDate>Mon, 02 Sep 2024 07:13:02 +0000</pubDate><guid>https://m365princess.com/blogs/secure-azure-rbac/</guid><description>&lt;hr>
&lt;p>This post is part of a series&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/loganalytics">How to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/azure-container-registry">How to use Azure Container Registry to standardize deployments using Bicep across your organization&lt;/a>&lt;/li>
&lt;li>📍 &lt;strong>you are here&lt;/strong> - &lt;a href="https://m365princess.com/secure-azure-rbac">How to secure access to an Azure Container Registry with RBAC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/cure-managed-identity">How to secure access to an Azure Container Registry with a Managed Identity&lt;/a>&lt;/li>
&lt;li>How to utilize the Azure Container Registry in your Azure DevOps pipeline - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>I like to deploy my Azure resources using Bicep - If you never heard about it, I blogged a while ago on &lt;a href="https://www.m365princess.com/blogs/start-deploying-azure-resources-bicep/">how to get started with Bicep&lt;/a> - please catch up first!&lt;/p>
&lt;hr>
&lt;h2 id="how-to-secure-access-to-an-azure-container-registry-with-rbac">How to Secure Access to an Azure Container Registry with RBAC&lt;/h2>
&lt;p>We discussed in the previous post how an Azure Container Registry may help you standardize and ease the work with deployment files. &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7235169620112920577?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7235169620112920577%2C7235369215749214208%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287235369215749214208%2Curn%3Ali%3Aactivity%3A7235169620112920577%29">One of the questions I received&lt;/a> (thanks &lt;a href="https://www.linkedin.com/in/christian-forjahn/">Christian&lt;/a> 👏) and wanted to address in this post was why not just use git (for example in Azure repos)? The answer is, that while git is amazing for during development, enabling teamwork, version control, traceability, images published to Azure Container Registry are immutable, so that they can&amp;rsquo;t be modified anymore. This ensures, that the files we work with are not being altered anymore. In this post, we&amp;rsquo;ll explore how to now secure access to an Azure Container Registry (ACR) using Role-Based Access Control (RBAC).&lt;/p>
&lt;h2 id="before-we-start">Before we start&lt;/h2>
&lt;p>Hate to break it to you, but there is a standard setting in Azure Container Registry, that I very much dislike.&lt;/p>
&lt;p>It&amp;rsquo;s called &lt;strong>Azure Container Registry Admin Access&lt;/strong> and allows accessing the entire ACR as an admin. Sounds super convenient, but pretty scary to me. And as if that wasn&amp;rsquo;t scary enough, for everyone who like to read audit logs as good night stories: If this stays enabled (because yes, this is ON by default 😱), it&amp;rsquo;s always the admin&amp;rsquo;s username appearing in the log files. That will make traceability of any issue really hard. So do yourself and me a favor and turn this off.&lt;/p>
&lt;p>&lt;img alt="acr admin access" src="https://m365princess.com/images/acr-admin-access.png">&lt;/p>
&lt;p>Ok, I&amp;rsquo;m glad we can finally proceed :-)&lt;/p>
&lt;h3 id="understanding-azure-rbac-for-container-registry">Understanding Azure RBAC for Container Registry&lt;/h3>
&lt;p>Azure Container Registry (ACR) supports a set of built-in Azure roles that provide different levels of permissions. These roles can be assigned to users, service principals, or other identities to control access to the registry. The primary roles include:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Owner&lt;/strong>: Full access to all resources&lt;/li>
&lt;li>&lt;strong>Contributor&lt;/strong>: Can manage all resources but cannot grant access to others&lt;/li>
&lt;li>&lt;strong>Reader&lt;/strong>: Can view all resources but cannot make changes&lt;/li>
&lt;li>&lt;strong>AcrPush&lt;/strong>: Can push container images to the registry&lt;/li>
&lt;li>&lt;strong>AcrPull&lt;/strong>: Can pull container images from the registry&lt;/li>
&lt;/ul>
&lt;p>Using Azure RBAC, you can assign these roles to ensure that only authorized identities can interact with your registry, whether it&amp;rsquo;s for pulling or pushing container images. For example, someone would be the person to exclusively push images (for examples the bicep deployment files from the last blog post) to ACR, but a set of developers should be able to consume (pull) the imaged from ACR.&lt;/p>
&lt;h3 id="creating-a-service-principal">Creating a Service Principal&lt;/h3>
&lt;p>To automate interactions with your ACR, you can create a service principal and assign it the necessary RBAC roles. I&amp;rsquo;d like to use a Key Vault then to securely store the credentials. Let&amp;rsquo;s go:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
# Variables
$vaultName = &amp;#34;&amp;lt;name of your key vault&amp;gt;&amp;#34;
$spName = &amp;#34;&amp;lt;name of the service principal&amp;gt;&amp;#34;
$subscriptionId = &amp;#34;&amp;lt;your Azure subscription id&amp;gt;&amp;#34;
$resourceGroup = &amp;#34;name of the resource group&amp;gt;&amp;#34;
$acrName = &amp;#34;&amp;lt;name of your Azure Container Registry&amp;gt;&amp;#34;
$location = &amp;#34;&amp;lt;name of the location&amp;gt;&amp;#34;
$userObjectId = &amp;#34;&amp;lt;object id of our user&amp;gt;&amp;#34;
# Assign `Owner` to your user so that you have enough privileges to create service principal
az role assignment create --assignee $userObjectId --role &amp;#34;Owner&amp;#34; --scope /subscriptions/&amp;lt;your-subscription-id&amp;gt;
# Create a service principal and capture the output
$spOutput = az ad sp create-for-rbac --name $spName --role acrpull --scopes &amp;#34;/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.ContainerRegistry/registries/$acrName&amp;#34; --query &amp;#34;{appId: appId, password: password, tenant: tenant}&amp;#34; -o json
# Parse the JSON output to extract appId, password, and tenant
$spDetails = $spOutput | ConvertFrom-Json
$appId = $spDetails.appId
$password = $spDetails.password
$tenantId = $spDetails.tenant
&lt;/code>&lt;/pre>&lt;h3 id="store-the-values-in-azure-key-vault">Store the values in Azure Key Vault&lt;/h3>
&lt;p>Now we want to store the values in an Azure Key Vault. If you don&amp;rsquo;t already have one (and permissions to write to it), let&amp;rsquo;s create one first:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
az keyvault create --name $vaultName --resource-group $resourceGroup --location $location
&lt;/code>&lt;/pre>&lt;p>To be able to push values into the Key Vault, we need to assign an RBAC role &lt;code>Key Vault Secrets Officer&lt;/code>. So let&amp;rsquo;s get that done as well:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli"># Assign the Key Vault Secrets Officer role to your user
az role assignment create --assignee $userObjectId --role &amp;#34;Key Vault Secrets Officer&amp;#34; --scope &amp;#34;/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.KeyVault/vaults/$vaultName&amp;#34;
&lt;/code>&lt;/pre>&lt;p>💡RBAC in Key Vault can be a bit tricky, as sometimes we will need to allow a bit more of propagation time for the role assignment to be fully effective. We can solidify our little script like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
# Wait for role assignment to propagate
$maxRetries = 10
$retryCount = 0
$roleAssigned = $false
while (-not $roleAssigned -and $retryCount -lt $maxRetries) {
Start-Sleep -Seconds 10
$roleAssignment = az role assignment list --assignee $userObjectId --scope &amp;#34;/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.KeyVault/vaults/$vaultName&amp;#34; --query &amp;#34;[?roleDefinitionName==&amp;#39;Key Vault Secrets Officer&amp;#39;]&amp;#34; -o json | ConvertFrom-Json
if ($roleAssignment -ne $null) {
$roleAssigned = $true
} else {
$retryCount++
Write-Output &amp;#34;Waiting for role assignment to propagate... ($retryCount/$maxRetries)&amp;#34;
}
}
if (-not $roleAssigned) {
Write-Error &amp;#34;Role assignment failed or has not propagated yet. Please try again after some time.&amp;#34;
exit 1
}
&lt;/code>&lt;/pre>&lt;p>Once that is completed successfully, we will push our credentials into the Key Vault&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
az keyvault secret set --vault-name $vaultName --name acr-sp-id --value $appId
az keyvault secret set --vault-name $vaultName --name acr-sp-password --value $password
az keyvault secret set --vault-name $vaultName --name acr-tenant-id --value $tenantId
Write-Output &amp;#34;Service principal credentials stored in Key Vault successfully ✅.&amp;#34;
&lt;/code>&lt;/pre>&lt;p>Lets briefly check this in the Azure portal:&lt;/p>
&lt;ul>
&lt;li>Select the resource group in which the ACR resides&lt;/li>
&lt;li>Select the ACR&lt;/li>
&lt;li>Select &lt;strong>Access Control (IAM)&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Check access&lt;/strong>&lt;/li>
&lt;li>Search for the name of your service principal&lt;/li>
&lt;li>Select it - You can now see an overview of the roles of the service principal. In our case, it&amp;rsquo;s the &lt;code>acr-pull&lt;/code> role. You can select this now again and view the permissions associated to this role.&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="acr-role assignment" src="https://m365princess.com/images/acr-role.png">&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>We created a service principal, stored its credentials securely in an Azure Key Vault and assigned an RBAC role to it which will only grant pull permissions to the Azure Container Registry. This ensures, that using this service principal, no new images can&amp;rsquo;t be published to the ACR. The Azure Container Registry stores our published Bicep 💪 files and makes them ready to deploy, reducing the time it takes to write the Infrastructure as Code significantly.&lt;/p>
&lt;p>Stay tuned for the next part of this series, where I&amp;rsquo;ll guide you through using the same concept but instead of a service principal with a Managed Identity (and why I prefer this even more).&lt;/p>
&lt;p>Questions? Let me know!&lt;/p></description></item><item><title>How to use Azure Container registry to standardize deployments using Bicep across your organization</title><link>https://m365princess.com/blogs/azure-container-registry/</link><pubDate>Thu, 29 Aug 2024 08:26:34 +0000</pubDate><guid>https://m365princess.com/blogs/azure-container-registry/</guid><description>&lt;hr>
&lt;p>This post is part of a series&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/loganalytics">How to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a>&lt;/li>
&lt;li>📍 &lt;strong>you are here&lt;/strong> - &lt;a href="https://m365princess.com/azure-container-registry">How to use Azure Container registry to standardize deployments using Bicep across your organization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/secure-azure-rbac">How to secure access to an Azure Container Registry with RBAC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/secure-managed-identity">How to secure access to an Azure Container Registry with a Managed Identity and RBAC&lt;/a>&lt;/li>
&lt;li>How to utilize the Azure Container Registry in your Azure DevOps pipeline - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>I like to deploy my Azure resources using Bicep - If you never heard about it, I blogged a while ago on &lt;a href="https://www.m365princess.com/blogs/start-deploying-azure-resources-bicep/">how to get started with Bicep&lt;/a> - please catch up first!&lt;/p>
&lt;p>You might now know, that I am a big fan of infrastructure as code&lt;/p>
&lt;ul>
&lt;li>no more manual clickety-clackety in the Azure portal&lt;/li>
&lt;li>it&amp;rsquo;s just less prone to human error&lt;/li>
&lt;li>our deployment files can be logged into source control&lt;/li>
&lt;/ul>
&lt;p>I did not realize that many fellow developers still copy/paste code from their previous Bicep files to their current files. Or, even worse, they need to ask other developers in the organization how they use to deploy a certain resource. Infrastructure as Code is good, but wouldn&amp;rsquo;t it be better if we had a centralized repository across our organization with ready-to-use deployment files?&lt;/p>
&lt;blockquote>
&lt;p>Azure Container Registry to the rescue!&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-is-azure-container-registry">What is Azure Container Registry?&lt;/h2>
&lt;p>Azure Container Registry (ACR) is a powerful tool for managing Docker container images, and it comes with several practical benefits:&lt;/p>
&lt;ul>
&lt;li>ACR works effortlessly with other Azure services like Azure DevOps (more in this in a later part of this series). This makes it easier to set up continuous integration and deployment (CI/CD) pipelines&lt;/li>
&lt;li>ACR provides a private, secure registry for your container images - You can control access with Microsoft Entra Id&lt;/li>
&lt;li>It supports both Linux and Windows containers&lt;/li>
&lt;/ul>
&lt;p>Using ACR for our Bicep files will help with managing, versioning, and deploying our IaC as efficiently as possible. (And who wouldn&amp;rsquo;t want to spend less time on deployments 🤭)&lt;/p>
&lt;h2 id="how-to-create-an-azure-container-registry">How to create an Azure Container Registry&lt;/h2>
&lt;p>⚠️ If you don&amp;rsquo;t already have &lt;a href="https://www.docker.com/products/docker-desktop/">Docker&lt;/a> installed, please go ahead and do this! If you are unsure, you can verify the installation in your terminal with&lt;/p>
&lt;p>&lt;code>docker --version&lt;/code>, ot should return something like this: &lt;code>Docker version 27.1.1, build 6312585&lt;/code> - if your version number is significantly lower, an update can&amp;rsquo;t hurt!&lt;/p>
&lt;p>Once this is done, in your terminal using Azure CLI&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli"># Set variables
$ACR_NAME=&amp;#34;building-blocks&amp;#34;
$RESOURCE_GROUP=&amp;#34;building-blocks-rg&amp;#34;
$LOCATION=&amp;#34;westeurope&amp;#34;
# Create the ACR
az acr create --name $ACR_NAME --resource-group $RESOURCE_GROUP --location $LOCATION --sku Basic
&lt;/code>&lt;/pre>&lt;p>(You can choose a name and a location to your liking!)&lt;/p>
&lt;h2 id="build-and-publish-your-bicep-modules-to-the-acr">Build and publish your Bicep modules to the ACR&lt;/h2>
&lt;p>We will use the bicep files that we created in the last blog post of this series - if you did not follow that one, please do so now:&lt;a href="https://m365princess.com/loganalytics">How to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a> and publish them to the ACR.&lt;/p>
&lt;p>In your terminal using Azure CLI:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli"># Log in to the ACR
az acr login --name $ACR_NAME
# Publish the Log Analytics workspace module
bicep publish --file ./loganalyticsworkspace.bicep --target br:$ACR_NAME.azurecr.io/bicep/modules/loganalyticsworkspace:1.0.0
# Publish the Application Insights module
bicep publish --file ./appinsights.bicep --target br:$ACR_NAME.azurecr.io/bicep/modules/appinsights:1.0.0
&lt;/code>&lt;/pre>&lt;p>If you gave your modules names in camelCase, you will get an error - only lower case letters are allows &amp;ndash;&amp;gt; ask me how I know 🙄&lt;/p>
&lt;h2 id="update-your-main-bicep-file">Update your &lt;code>main&lt;/code> Bicep File&lt;/h2>
&lt;p>As a last step, we will update our &lt;code>main&lt;/code> Bicep file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">resourceGroup&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;web&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// Link the Log Analytics workspace module from ACR&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalytics&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;br:&amp;lt;yourACRName&amp;gt;.azurecr.io/bicep/modules/loganalyticsworkspace:1.0.0&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;logAnalyticsDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// Link the Application Insights module from ACR&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsights&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;br:&amp;lt;yourACRName&amp;gt;.azurecr.io/bicep/modules/appinsights:1.0.0&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;appInsightsDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalytics&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">outputs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">logAnalyticsWorkspaceId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="verify-that-the-modules-got-published">Verify that the modules got published&lt;/h2>
&lt;p>Now we want to see that things worked, right? Easy enough, you can run &lt;code>az acr repository list --name $ACR_NAME --output table&lt;/code>, which will return&lt;/p>
&lt;p>&lt;img alt="bicep modules in registry" src="https://m365princess.com/images/bidep-modules.png">&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>With ACR, you can now setup templates on how you want to deploy resources across the organization. This is especially helpful if you have resources with lots of properties like VMs :-). In the next blog post, I will cover how to utilize this approach in in Azure DevOps- Stay tuned!&lt;/p></description></item><item><title>How to use Bicep to deploy Azure LogAnalytics Workspace and link Application Insights to it</title><link>https://m365princess.com/blogs/loganalytics/</link><pubDate>Wed, 28 Aug 2024 08:26:34 +0000</pubDate><guid>https://m365princess.com/blogs/loganalytics/</guid><description>&lt;hr>
&lt;p>This post is part of a series&lt;/p>
&lt;ul>
&lt;li>📍 &lt;strong>you are here&lt;/strong> - &lt;a href="https://m365princess.com/loganalytics">How to use Bicep to deploy Azure LogAnalytics Workspace and link Application Insights to it&lt;/a>&lt;/li>
&lt;li>How to use Azure Container registry to standardize deployments using Bicep across your organization - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;li>How to utilize the Azure Container Registry in your Azure DevOps pipeline - &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>I like to deploy my Azure resources using Bicep - If you never heard about it, I blogged a while ago on &lt;a href="https://www.m365princess.com/blogs/start-deploying-azure-resources-bicep/">how to get started with Bicep&lt;/a> - please catch up first if you are not yet familiar with Bicep!&lt;/p>
&lt;p>I strongly believe that we can&amp;rsquo;t meaningfully improve what we don&amp;rsquo;t measure. Especially though when it comes to improving apps, user feedback alone is sometimes not as reliable as we wanted it to be. This is why I like to monitor performance and track user behavior with Azure Application Insights.&lt;/p>
&lt;h2 id="azure-application-insights">Azure Application Insights&lt;/h2>
&lt;p>Application Insights is a tool in Azure that helps you keep an eye on your app’s performance and user activity. It tracks things like response times, errors, and failures so you can spot problems before users raise lots of tickets. You can get alerts, if something is off and of course as it is an Azure service, it works well with all kin of other Azure services.&lt;/p>
&lt;p>For the longest time, Application Insights could store its data independently, but a couple of months ago Microsoft changed this and Application Insights now requires you to use a Log Analytics Workspace as the backend for storing telemetry data. This raises obviously the question: What is a Log Analytics workspace?&lt;/p>
&lt;h2 id="azure-log-analytics-workspace">Azure Log Analytics Workspace&lt;/h2>
&lt;p>An Azure Log Analytics Workspace is like a central hub where you collect and analyze log data from various sources, including your Power Apps and Azure services.&lt;/p>
&lt;p>When you use Application Insights to monitor your app’s performance, connecting it to a Log Analytics Workspace supercharges your capabilities:&lt;/p>
&lt;ul>
&lt;li>It lets you run advanced queries to dig into your data and uncover insights that basic Application Insights might miss&lt;/li>
&lt;li>If you&amp;rsquo;re tracking multiple apps or resources, a Log Analytics Workspace pulls all the data together in one place, making it easier to spot patterns and issues&lt;/li>
&lt;/ul>
&lt;h2 id="how-do-we-create-now-the-log-analytics-workspace-and-application-insights">How do we create now the Log Analytics Workspace and Application Insights?&lt;/h2>
&lt;p>Please make sure, that you installed the &lt;a href="https://learn.microsoft.com/azure/azure-resource-manager/bicep/install">Bicep tools&lt;/a>&lt;/p>
&lt;p>We will work through this in modules. If you are not familiar with this approach yet, using modules in Bicep is all about making your infrastructure code more organized and reusable. Instead of having one big, messy file, you can break things down into smaller, more focused pieces. For this deployment, we will need 3 files. 1 for the Log Analytics Workspace, 1 for the Application Insights and 1 &lt;code>main&lt;/code>, that will &lt;em>call&lt;/em> the other ones. Let&amp;rsquo;s get started!&lt;/p>
&lt;ol>
&lt;li>Create a new bicep file, name it &lt;code>loganalyticsworkspace.bicep&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspace&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.OperationalInsights/workspaces@2020-08-01&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">sku&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;PerGB2018&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">retentionInDays&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">30&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>Create a new bicep file, name it &lt;code>appinsights.bicep&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;web&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceId&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsights&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.Insights/components@2020-02-02&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">properties&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">Application_Type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">WorkspaceResourceId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>Create a new Bicep file, name in &lt;code>main.bicep&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">resourceGroup&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;web&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// Deploy Log Analytics workspace&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalytics&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;logAnalyticsWorkspace.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;logAnalyticsDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// Deploy Application Insights and link to Log Analytics workspace&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsights&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;appInsights.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;appInsightsDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">appInsightsName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">applicationType&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalyticsWorkspaceId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">logAnalytics&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">outputs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">logAnalyticsWorkspaceId&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nv">4&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">Now&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">deploy&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">this&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">using&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">Azure&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">CLI&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">```&lt;/span>&lt;span class="nv">azurecli&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">#&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">Set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">variables&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="nv">RESOURCE_GROUP&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="nv">yourResourceGroupName&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="nv">LOCATION&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="nv">yourLocation&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="nv">LOG_ANALYTICS_WORKSPACE_NAME&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="nv">yourLogAnalyticsWorkspaceName&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="nv">APP_INSIGHTS_NAME&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="nv">yourAppInsightsName&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">#&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">Create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">group&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">it&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">doesn&lt;/span>&lt;span class="s">&amp;#39;t exist
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">az group create --name $RESOURCE_GROUP --location $LOCATION
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"># Deploy the main Bicep file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">az deployment group create &lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> --resource-group $RESOURCE_GROUP &lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> --template-file main.bicep &lt;/span>&lt;span class="err">\&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> --parameters logAnalyticsWorkspaceName=$LOG_ANALYTICS_WORKSPACE_NAME appInsightsName=$APP_INSIGHTS_NAME location=$LOCATION
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>💡 please make sure that you put actual values into the placeholders&lt;/p>
&lt;p>As a result, we can now see&lt;/p>
&lt;p>&lt;img alt="deployment worked" src="https://m365princess.com/images/rg-ok.png">&lt;/p>
&lt;p>that the deployment worked like a charm! We can even see, that in fact, we have 4 deployments - One of them being &lt;a href="https://learn.microsoft.com/azure/azure-monitor/alerts/proactive-failure-diagnostics">Smart detection - Failure Anomalies&lt;/a>, that gets automatically deployed. The other 3 are exactly the files that we created!&lt;/p>
&lt;p>&lt;img alt="deployments" src="https://m365princess.com/images/deploy.png">&lt;/p>
&lt;h2 id="bonus-for-power-apps-people">Bonus for Power Apps people&lt;/h2>
&lt;p>You can now link your Application Insight Instrumentation Key to a Canvas app an track its performance! OPen your app in &lt;strong>EDIT&lt;/strong> mode, open the &lt;strong>APP&lt;/strong> object and find the Instrumentation Key property on the right pane:&lt;/p>
&lt;p>&lt;img alt="alt text" src="https://m365princess.com/images/app-insights-canvas.png">&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Bicep modules make it very easy to deploy Azure resources - If you&amp;rsquo;d like to take that one or two levels up, then stay tuned for next parts of this series&amp;hellip; Where we cover how to utilize a Docker container to store our Bicep files org wide, and how to use this then in automated Azure DevOps pipeline!&lt;/p></description></item><item><title>How to get from Dev? Ooops! 🤭 to proper (Azure) DevOps for Power Platform</title><link>https://m365princess.com/blogs/alm-power-platform-azure-devops/</link><pubDate>Mon, 26 Aug 2024 08:26:34 +0000</pubDate><guid>https://m365princess.com/blogs/alm-power-platform-azure-devops/</guid><description>&lt;hr>
&lt;p>Watch out - this is part 2 of a series around very good practices in Power Platform&lt;/p>
&lt;ul>
&lt;li>Part 1: &lt;a href="https://www.m365princess.com/blogs/yolo-deploy-friday/">Yolo! Let&amp;rsquo;s deploy Friday?!&lt;/a>&lt;/li>
&lt;li>📍 &lt;strong>You are here&lt;/strong> &amp;ndash;&amp;gt; Part 2: &lt;a href="https://www.m365princess.com/blogs/alm-power-platform-azure-devops">How to get from Dev? Ooops! 🤭 to proper (Azure) DevOps for Power Platform&lt;/a>&lt;/li>
&lt;li>Part 3: More options in Azure DevOps to manage Power Platform: &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;li>Part 4: How to monitor Power Platform solutions with Azure Application Insights : &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;li>Part 5: How to test Power Platform solutions : &lt;em>to be published soon&lt;/em>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="why-would-we-care">Why would we care?&lt;/h2>
&lt;p>There are Power Platform solutions, that are purely meant to be for personal productivity. They only impact the user who built them and it&amp;rsquo;s ok (for me) to have them as an unmanaged solution in the default environment.&lt;/p>
&lt;p>For solutions though, that impact more people or that even drive mission-critical processes, it&amp;rsquo;s a good idea to&lt;/p>
&lt;ul>
&lt;li>develop them in a different environment than they are used in&lt;/li>
&lt;li>log the changes into a source control system&lt;/li>
&lt;li>publish the solution as a managed solution for users&lt;/li>
&lt;li>automate this process&lt;/li>
&lt;/ul>
&lt;p>If we don&amp;rsquo;t do that, it leads to solutions being subjects to change for end-users all the time - so that they can&amp;rsquo;t rely on a feature to be tested and working as planned. It also means that developers can&amp;rsquo;t revert/undo changes and it eventually leads to lots of manual and chaotic ad-hoc work, which puts unneccesary stress on admins and developers:&lt;/p>
&lt;ul>
&lt;li>We operate at open heart when developing solutions while users keep using them&lt;/li>
&lt;li>We can&amp;rsquo;t roll back to previous versions&lt;/li>
&lt;li>Code only exists as opaque zip files in an environment&lt;/li>
&lt;/ul>
&lt;h2 id="how-do-we-care">How do we care?&lt;/h2>
&lt;h3 id="basics">Basics&lt;/h3>
&lt;p>Let&amp;rsquo;s first cover some basics so that everyone is on the same page: All Power Platform components are packaged and distributed across environments using solutions
There are 2 types of solutions, unmanaged and managed solutions. Unmanaged solutions are a collection of references to components, there are no restrictions on what can be added, removed, or modified, and they are recommended only during development of the solution. Managed solution&amp;rsquo;s components can&amp;rsquo;t be added or removed or modified. They are recommended when a solution is not actively being customized. Source control should be your source of truth for storing and collaborating on your components.&lt;/p>
&lt;p>How do we do this? There are several options:&lt;/p>
&lt;ul>
&lt;li>Power Platform Pipelines (built-in, super easy to use, super limited as well.)&lt;/li>
&lt;li>GitHub actions&lt;/li>
&lt;li>Power Platform Build Tools for Azure DevOps&lt;/li>
&lt;/ul>
&lt;p>I will explain how this works for Azure DevOps, as I use this in nearly all my projects at my customers.&lt;/p>
&lt;h3 id="what-is-azure-devops-build-tools-for-power-platform">What is Azure DevOps Build Tools for Power Platform&lt;/h3>
&lt;ul>
&lt;li>Azure DevOps Pipelines automatically build code projects and ship them to any target&lt;/li>
&lt;li>They support both GitHub and Azure repos (but there is no explicit value for me to store the codein GitHub when I use Azure DevOps)&lt;/li>
&lt;li>Azure DevOps Build Tools for Power Platform are a collection of build and release tasks related to Power Platform&lt;/li>
&lt;/ul>
&lt;h3 id="prerequisites--little-bit-of-prep-work">Prerequisites / little bit of prep work&lt;/h3>
&lt;p>As we do not want to develop the solution in the same environment as we want users to use, we&amp;rsquo;d need at least 2 environments.&lt;/p>
&lt;blockquote>
&lt;p>I&amp;rsquo;m a big fan of a 4 environment setup&lt;/p>
&lt;/blockquote>
&lt;h4 id="overview-of-environments">Overview of environments&lt;/h4>
&lt;ul>
&lt;li>DEV - only for development purposes, only developers and maintainers have access to it&lt;/li>
&lt;li>BUILD - only used to store the exported unmanaged solution and export it as managed solution (this separates the solution from the solution that is currently and ongoing developed in DEV). I want a separate (and new!) environment to build the solutions so that no &lt;em>Gremlins&lt;/em> (coined by J. Rapp) could interfere (connection references, environment variables, etc.)&lt;/li>
&lt;li>TEST - only for User Acceptance Testing - A subset of endusers can test functionality of this release.&lt;/li>
&lt;li>PROD - production use of apps in the organization&lt;/li>
&lt;/ul>
&lt;p>💡 Obviously, if there is a requirement for a new environment in the organization, it&amp;rsquo;s a good idea to then have this &lt;em>set&lt;/em> of environments.&lt;/p>
&lt;h4 id="process-of-building-and-releasing-versions">Process of building and releasing versions&lt;/h4>
&lt;p>Every new feature, all bug fixes are implemented in DEV. Once the release passed the technical tests in DEV, it is build in BUILD and then finally released to TEST, where end users can try out and test the release. If they approve it, the release gets published to PROD. If they don&amp;rsquo;t approve it, development starts again in DEV and bug fixes/features get implemented in the next sprint.&lt;/p>
&lt;p>&lt;img alt="pipeline overview" src="https://m365princess.com/images/alm_pp.png">&lt;/p>
&lt;h3 id="setup-instructions">Setup instructions&lt;/h3>
&lt;p>You will obviously need to have an Azure DevOps organization, and within it a new project. In this project&lt;/p>
&lt;ul>
&lt;li>Create a repository to hold your code&lt;/li>
&lt;li>Optional, but a good idea: Clone this repo so you can work locally in VS Code&lt;/li>
&lt;li>Optional, but also a good idea: invite co-workers to the project&lt;/li>
&lt;li>Open &lt;strong>Project Settings&lt;/strong> (lower left corner) &amp;ndash;&amp;gt; &lt;strong>Repositories&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>Security&lt;/strong> and select Contribute permissions for both (!)
&lt;ul>
&lt;li>Project Collection Service Accounts permissions&lt;/li>
&lt;li>Build Service permissions&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="create-environments">Create environments&lt;/h4>
&lt;p>As a first step, we need to ensure that we have all environments in place:&lt;/p>
&lt;ul>
&lt;li>Open aka.ms/ppac&lt;/li>
&lt;li>Create environments for DEV, BUILD, TEST, PROD - make sure all of them have a Dataverse database&lt;/li>
&lt;/ul>
&lt;h4 id="authentication">Authentication&lt;/h4>
&lt;p>To authenticate in Azure DevOps against actions in Dataverse, you will need to&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/blogs/alm-power-platform-azure-devops/#create-an-app-registration">create an app registration in Microsoft Entra ID&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/alm-power-platform-azure-devops/#create-an-application-user">link this app registration to an app user in the respective Dataverse environment&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/alm-power-platform-azure-devops/#create-service-connections-in-azure-devops">create a service connection in Azure DevOps&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s get our hands dirty.&lt;/p>
&lt;h5 id="create-an-app-registration">Create an app registration&lt;/h5>
&lt;p>You can either create the app registration with a single line command in &lt;a href="https://pnp.github.io/cli-microsoft365/">CLI for Microsoft 365&lt;/a>. I posted a &lt;a href="https://www.m365princess.com/blogs/cli-microsoft-365-power-platform/">detailed blog post here&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">m365 aad app add --name &lt;span class="s1">&amp;#39;myApp001&amp;#39;&lt;/span> --withSecret
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or create it in the UI:&lt;/p>
&lt;ul>
&lt;li>Open portal.azure.com&lt;/li>
&lt;li>Select &lt;strong>Microsoft Entra ID&lt;/strong>&lt;/li>
&lt;li>Select App registrations &amp;ndash;&amp;gt; New app registration&lt;/li>
&lt;li>Give it a name, select &lt;strong>Register&lt;/strong>&lt;/li>
&lt;li>Select Certificates and secrets, create a secret, take note of it. (No I mean for real - you will not be able to se the value again if you navigate away.)&lt;/li>
&lt;li>Also copy the values of the App id and Tenant id - we need them later.&lt;/li>
&lt;/ul>
&lt;p>💡 If you think that you should grant API permissions for &lt;code>Dynamics CRM -&amp;gt; user_impersonation&lt;/code>, please don&amp;rsquo;t be fooled. This is not necessary, as access to Dataverse exclusively handled by Security Roles. You can read more about it in this blog post:&lt;a href="https://www.m365princess.com/blogs/2022-07-25-why-your-service-principal-doesnt-need-a-dynamics-user_impersonation-scope/">Why your Power Platform service principal doesn&amp;rsquo;t need a Dynamics user_impersonation scope&lt;/a>&lt;/p>
&lt;h5 id="create-an-application-user">Create an Application user&lt;/h5>
&lt;p>Now we need to make sure that we create an Application user in all of our 4 environments.&lt;/p>
&lt;ul>
&lt;li>Open aka.ms/ppac&lt;/li>
&lt;li>Select your environment&lt;/li>
&lt;li>Select &lt;strong>Settings&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Application users&lt;/strong> under &lt;strong>Users &amp;amp; permissions&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>new app user&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>add an app&lt;/strong>&lt;/li>
&lt;li>Select the app registration from Microsoft Entra Id&lt;/li>
&lt;li>Select the business unit&lt;/li>
&lt;li>Select the pen icon next to &lt;strong>Security roles&lt;/strong> and add the &lt;code>System Administrator role&lt;/code>&lt;/li>
&lt;li>Select Save&lt;/li>
&lt;/ul>
&lt;p>💡Remember to do this for all environments!&lt;/p>
&lt;h5 id="create-service-connections-in-azure-devops">Create Service connections in Azure DevOps&lt;/h5>
&lt;p>To be able to use the Power Platform Build Tools in Azure DevOps, we first need to install them. &lt;a href="https://marketplace.visualstudio.com/items?itemName=microsoft-IsvExpTools.PowerPlatform-BuildTools">You can find the download here&lt;/a>&lt;/p>
&lt;ul>
&lt;li>Return to Azure DevOps, select &lt;strong>Project settings&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>Pipelines&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>Service connections&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>new service Connection&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Power Platform&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Application Id and secret&lt;/strong> as authentication method&lt;/li>
&lt;li>Obtain the &lt;strong>Instance URL&lt;/strong> from your DEV environment ( Open make.powerapps.com, Select the DEV environment, Select the gear icon ⚙️ in the top right corner, Select &lt;strong>Settings&lt;/strong>, copy the Instance URL)&lt;/li>
&lt;li>Paste this value to Server URL&lt;/li>
&lt;li>Paste Tenant id, App id, and App secret from your app registration into the respective fields&lt;/li>
&lt;li>Save the connection under name DEV Service Principal&lt;/li>
&lt;li>Repeat this steps for BUILD, TEST, and PROD&lt;/li>
&lt;/ul>
&lt;h3 id="create-build-pipeline-1---export-from-dev">Create Build pipeline 1 - Export from DEV&lt;/h3>
&lt;p>This Build Pipelines objective is to export a solution from DEV into source control (Azure DevOps repos)&lt;/p>
&lt;p>It&amp;rsquo;s a good practice to write your pipelines in YAML - This way you can also log the code that describes your pipeline into source control. Here is the snippet:&lt;/p>
&lt;p>Please note that&lt;/p>
&lt;ul>
&lt;li>I have a variable called &lt;code>SolutionName&lt;/code> so that I can reuse this pipeline for any other Power Platform solution&lt;/li>
&lt;li>I like to have my zip file in the &lt;code>solution&lt;/code> folder and the unpacked files in the &lt;code>sourcecode&lt;/code> folder&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yml" data-lang="yml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">pool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Azure Pipelines&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c">#Your build pipeline references the ‘SolutionName’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.tool-installer.PowerPlatformToolInstaller@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Tool Installer &amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.export-solution.PowerPlatformExportSolution@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Export Solution - unmanaged&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">authenticationType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PowerPlatformSPN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PowerPlatformSPN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;DEV Service Principal&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(SolutionName)&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionOutputFile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;solution/$(SolutionName).zip&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.unpack-solution.PowerPlatformUnpackSolution@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Unpack Solution &amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionInputFile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;solution/$(SolutionName).zip&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionTargetFolder&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(Build.SourcesDirectory)\sourcecode&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ProcessCanvasApps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo commit all changes
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git config user.email &amp;#34;&amp;lt;my email&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git config user.name &amp;#34;&amp;lt;my name&amp;gt;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git checkout -B main
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git add --all
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git commit -m &amp;#34;adds source code files from DEV&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git push --set-upstream origin main&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Command Line Script&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you prefer to build the pipeline in a visual editor (and later on copy/paste the yaml), this is how it works: First, enable classic editor in the projects settings.Then,&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Pipelines&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>New Pipeline&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>Use the Classic Editor&lt;/strong>&lt;/li>
&lt;li>Select the Source as Azure Repos Git, select your Project, Repository and Branch and select &lt;strong>continue&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Start with empty job&lt;/strong>&lt;/li>
&lt;li>Select Agent Job 1 and check &lt;strong>Allow Scripts to access OAuth token&lt;/strong>&lt;/li>
&lt;li>Add task Power Platform Tool Installer&lt;/li>
&lt;li>Add task Power Platform Export Solution&lt;/li>
&lt;li>As Service Connection select &lt;code>Dev Service Principal&lt;/code>&lt;/li>
&lt;li>Provide the solution name (not the displayName) and the folder for the output - select the &amp;hellip; menu for that. You can also use variable for that.&lt;/li>
&lt;li>Uncheck the Export as Managed solution checkmark&lt;/li>
&lt;li>Add task Power Platform Unpack solution&lt;/li>
&lt;li>Specify Input file (its the output file location from previous task)&lt;/li>
&lt;li>Specify target folder to output th unpacked files (can sit at the root of the project - use the &amp;hellip; menu)&lt;/li>
&lt;li>Add a task &lt;strong>Command Line script&lt;/strong>, and paste the below script&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-git" data-lang="git">
echo commit all changes
git config user.email “&amp;lt;your git email&amp;gt;”
git config user.name &amp;#34;&amp;lt;your git user name&amp;gt;&amp;#34;
git checkout -B main
git add --all
git commit -m &amp;#34;code commit&amp;#34;
git push --set-upstream origin main
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Save and queue the pipeline and wait for it to run successfully&lt;/li>
&lt;/ul>
&lt;h3 id="create-build-pipeline-2---build-managed-solution">Create Build pipeline 2 - Build Managed Solution&lt;/h3>
&lt;p>Now onto building pipeline 2&lt;/p>
&lt;p>&lt;img alt="pipeline overview" src="https://m365princess.com/images/alm_pp.png">&lt;/p>
&lt;p>Objective here is to deploy the unmanaged solution to our &lt;strong>BUILD&lt;/strong> environment, so that we can still continue to modify code in &lt;strong>DEV&lt;/strong>.&lt;/p>
&lt;p>&lt;img alt="pipeline 2" src="https://m365princess.com/images/alm_pp_pl2-overview.png">&lt;/p>
&lt;p>Here is the yaml view:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yml" data-lang="yml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">pool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Azure Pipelines&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">variables&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(SolutionName)&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.tool-installer.PowerPlatformToolInstaller@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Tool Installer &amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.pack-solution.PowerPlatformPackSolution@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Pack Solution &amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionSourceFolder&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sourcecode&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionOutputFile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(Build.ArtifactStagingDirectory)\$(SolutionName).zip&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.import-solution.PowerPlatformImportSolution@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Import Solution &amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">authenticationType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PowerPlatformSPN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PowerPlatformSPN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;BUILD Service Principal&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionInputFile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(Build.ArtifactStagingDirectory)\$(SolutionName).zip&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">microsoft-IsvExpTools.PowerPlatform-BuildTools.export-solution.PowerPlatformExportSolution@2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;Power Platform Export Solution - managed&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">authenticationType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PowerPlatformSPN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PowerPlatformSPN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;BUILD Service Principal&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(SolutionName)&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SolutionOutputFile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(Build.ArtifactStagingDirectory)/$(SolutionName)_managed.zip&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">Managed&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">OverwriteLocalSolution&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">task&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">PublishBuildArtifacts@1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">displayName: &amp;#39;Publish Artifact&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">drop&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">inputs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PathtoPublish&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;$(Build.ArtifactStagingDirectory)\&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For the classic editor, use the following&lt;/p>
&lt;p>Now onto building pipeline 2&lt;/p>
&lt;ul>
&lt;li>Add tasks
&lt;ul>
&lt;li>Power Platform Tool Installer&lt;/li>
&lt;li>Power Platform Pack Solution&lt;/li>
&lt;li>Power Platform Import Solution&lt;/li>
&lt;li>Power Platform Export Solution&lt;/li>
&lt;li>Publish Artifact drop&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="power-platform-pack-solution">Power Platform Pack Solution&lt;/h3>
&lt;p>This task will pack the unpacked files that are currently sitting in &lt;strong>DEV&lt;/strong> as an unmanaged solution:&lt;/p>
&lt;p>&lt;img alt="Build pipeline 2 - pack" src="https://m365princess.com/images/alm_pp_pl2-pack.png">&lt;/p>
&lt;h3 id="power-platform-import-solution">Power Platform Import Solution&lt;/h3>
&lt;p>This task will now import the unmanaged solution into our &lt;strong>BUILD&lt;/strong> environment.&lt;/p>
&lt;p>&lt;img alt="Build pipeline 2- import" src="https://m365princess.com/images/alm_pp_pl2-import.png">&lt;/p>
&lt;h3 id="power-platform-export-solution">Power Platform Export Solution&lt;/h3>
&lt;p>This task exports the solution as a managed solution:&lt;/p>
&lt;p>&lt;img alt="Build pipeline 2 - export" src="https://m365princess.com/images/alm_pp_pl2-export.png">&lt;/p>
&lt;h3 id="publish-artifact-drop">Publish Artifact Drop&lt;/h3>
&lt;p>As a last step, we will now publish the build artifacts&lt;/p>
&lt;p>&lt;img alt="Build pipeline 2" src="https://m365princess.com/images/alm_pp_pl2-drop.png">&lt;/p>
&lt;h2 id="release-pipeline-to-test">Release pipeline to TEST&lt;/h2>
&lt;p>Now that we have our managed solution in the &lt;strong>BUILD&lt;/strong> environment, we will want to release it to &lt;strong>TEST&lt;/strong> or to &lt;strong>PROD&lt;/strong> (depends on requirements of User Acceptance Testing)&lt;/p>
&lt;p>&lt;img alt="ALM overview" src="https://m365princess.com/images/alm_pp.png">&lt;/p>
&lt;ul>
&lt;li>Under &lt;strong>Pipelines&lt;/strong>, select &lt;strong>Releases&lt;/strong>&lt;/li>
&lt;li>Under Artifacts, select your project and the source (its the Build pipeline 2 - Build Managed Solution)&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Release Overview" src="https://m365princess.com/images/alm_pp-release_artifact.png">&lt;/p>
&lt;ul>
&lt;li>For Stage 1, add 2 tasks
&lt;ul>
&lt;li>Power Platform Tool Installer&lt;/li>
&lt;li>Power Platform Import Solution&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Release import to TEST" src="https://m365princess.com/images/alm_pp-release_import.png">&lt;/p>
&lt;ul>
&lt;li>Create a Release&lt;/li>
&lt;li>Deploy the Release&lt;/li>
&lt;/ul>
&lt;h2 id="release-pipeline-to-prod">Release pipeline to PROD&lt;/h2>
&lt;p>Create another Release pipeline for &lt;strong>PROD&lt;/strong>&lt;/p>
&lt;h2 id="process-of-cicd">Process of CI/CD&lt;/h2>
&lt;p>The process of Continuos Improvement/Continuos Deployment (CI/CD) describes how we can now iterate in a secure way to improve our app, while making sure that users are not affected by changes happening in &lt;strong>DEV&lt;/strong>, have the chance to exactly test in &lt;strong>TEST&lt;/strong> what they will get in &lt;strong>PROD&lt;/strong> and developers can feel at ease as all code is stored in source control and commits can be easily tracked.&lt;/p>
&lt;p>For our &lt;strong>Build pipeline 2 - Build Managed Solution&lt;/strong> we can turn on a setting &lt;strong>enable continuos integration&lt;/strong> which means, that every time, we push code to our main branch this job runs and builds a Managed solution in &lt;strong>BUILD&lt;/strong>.&lt;/p>
&lt;p>We do push code to our main branch by letting our &lt;strong>Build pipeline 1 - Export from DEV&lt;/strong> run 💡 - which we trigger manually in Azure Devops.&lt;/p>
&lt;p>&lt;img alt="continuos integration" src="https://m365princess.com/images/alm_pp_pl2-ci.png">&lt;/p>
&lt;p>For the Release pipeline, there is a similar setting:&lt;/p>
&lt;p>&lt;img alt="continuos integration in release" src="https://m365princess.com/images/alm_pp-release_ci.png">&lt;/p>
&lt;p>Once it is enabled, it triggers automatically, if a new Build is available, aka when our &lt;strong>Build pipeline 2 - Build Managed Solution&lt;/strong> ran successfully. This means, that with these settings being switched on, we start a chain reaction every time, we run &lt;strong>Build pipeline 1 - Export from DEV&lt;/strong> - and it results in a new release of a managed solution in TEST.&lt;/p>
&lt;p>Users can now do the user acceptance training, log experienced issues or change requests for the next release and then we can manually trigger the &lt;strong>Release to PROD&lt;/strong> pipeline so that we export the solution to the &lt;strong>PROD&lt;/strong> environment. In case of severe issues with the release, which only get discovered during user acceptance testing, release to &lt;strong>PROD&lt;/strong> will be deferred, changes to the app are being addressed in &lt;strong>DEV&lt;/strong>, and then the pipelines are triggered again manually to run the entire process again.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>With this basic setup, we solve the issues described at the beginning of the post - and deployments now don&amp;rsquo;t take up human time anymore - as noone needs to manually import and export zip files. Also we safely separated developing solutions from using them and took care that the solution in the PROD environment is a managed one.&lt;/p>
&lt;p>From here, you can now expand your pipeline. I will cover some examples in one of my next blog posts - stay tuned!&lt;/p></description></item><item><title>Yolo! Let's deploy Friday?!</title><link>https://m365princess.com/blogs/yolo-deploy-friday/</link><pubDate>Fri, 23 Aug 2024 09:49:21 +0000</pubDate><guid>https://m365princess.com/blogs/yolo-deploy-friday/</guid><description>&lt;h2 id="we-do-not-deploy-on-fridays-amirite">We do not deploy on Fridays, AmIRite?&lt;/h2>
&lt;p>Ah, the infamous &lt;em>Friday Freeze&lt;/em> – a term that became somewhat of running joke among developers. The idea is simple: &lt;em>Don&amp;rsquo;t deploy on Fridays&lt;/em>. Why? Because if something goes wrong, your weekend plans might just go up in smoke as you scramble to fix the emergency. So instead of putting energy and effort into developing and fostering a deployment process we can trust, we focus on avoiding potential disasters. Sounds like a stressful environment if you ask me.&lt;/p>
&lt;blockquote>
&lt;p>It&amp;rsquo;s not about Friday. It&amp;rsquo;s about fear.&lt;/p>
&lt;/blockquote>
&lt;p>We don&amp;rsquo;t deploy on fridays, if we don&amp;rsquo;t trust our software and/or our process. So, what if, instead of hiding behind programmer-memes and perpetuating the false narrative of &lt;em>No friday deploys&lt;/em> we fix the underlying issue and deploy software, that is proven and tested and deployed in an automated manner?&lt;/p>
&lt;p>I know, strange thinking here 🤭&lt;/p>
&lt;p>But let&amp;rsquo;s be real for a moment: Is the &lt;em>Friday Freeze&lt;/em> really a good practice? Spoiler alert: It is not. In fact, it can be quite counterproductive.&lt;/p>
&lt;h3 id="disadvantages-of-friday-freeze">Disadvantages of Friday Freeze&lt;/h3>
&lt;p>First off, avoiding deployments on Fridays will inevitably create a bottleneck. If everyone is tries to deploy their changes earlier in the week, it can lead to rushed and poorly (if even) tested deployments. This rush can actually increase the likelihood of errors (both in a broken software and as in impacting other systems), which is exactly what we&amp;rsquo;re trying to avoid in the first place. Plus, if something does go wrong mid-week, it can still spill over into the weekend. So, the Friday Freeze doesn&amp;rsquo;t really save us from weekend work; it just shifts the risk to other days.&lt;/p>
&lt;p>&lt;img alt="goosebumps meme" src="https://m365princess.com/images/goosebumps.png">&lt;/p>
&lt;p>And then there&amp;rsquo;s the &lt;em>YOLO-deploy&lt;/em>. You know, the &amp;ldquo;You Only Live Once&amp;rdquo; approach where you throw caution to the wind and deploy without giving any f*cks. (While there are days, where I seriously run out of all the f *cks I could give, it&amp;rsquo;s still not exactly the best strategy for maintaining a stable production environment.)YOLO-deploys can lead to unexpected issues and lots of firefighting. (And firefighting during the weekend means that I don&amp;rsquo;t get the rest I need 🙄).&lt;/p>
&lt;p>Having developers literally throw their software over a fence and then keep fingers cross to hope for the best has never been a valid approach, and still I hear lots of things at customers that make me &lt;del>scratch my head&lt;/del> wanna cry:&lt;/p>
&lt;blockquote>
&lt;p>Deployments? I thought this is low-code! We just &lt;em>right-click and publish&lt;/em>.&lt;/p>
&lt;/blockquote>
&lt;p>Or even:&lt;/p>
&lt;blockquote>
&lt;p>Testing? Our end users will mention if something doesn&amp;rsquo;t work 🤞&lt;/p>
&lt;/blockquote>
&lt;p>&lt;img alt="this is fine" src="https://m365princess.com/images/thisisfine.png">&lt;/p>
&lt;p>While these beliefs might seem convenient, they are far from good practice.&lt;/p>
&lt;p>Just because it&amp;rsquo;s low-code, doesn&amp;rsquo;t mean we can skip proper deployment habits. Low-code platforms like Power Platform still require structured deployment pipelines to ensure stability and consistency. The infamous argument: &lt;em>Yeah we only do this for small changes!&lt;/em> - Trust me - there are in fact no small changes, so please do yourself and your team a favor and don&amp;rsquo;t even &lt;em>think&lt;/em> about publishing what you deem a small change without pushing the change through the pipeline.&lt;/p>
&lt;p>Relying on end users to catch issues? That&amp;rsquo;s a recipe for disaster. End users are great at providing feedback, but they shouldn&amp;rsquo;t be your primary line of defense. Proper testing, both automated and manual, is crucial to catch issues before they reach production. Automated tests help ensure that your application works as expected. But even if you covered your bases in terms of automated tests: Manual exploratory testing is also important to catch those unknown unknowns that automated tests might miss.&lt;/p>
&lt;p>&lt;img alt="testing" src="https://m365princess.com/images/softwaretester-joke.png">&lt;/p>
&lt;p>So, shall we revisit how we think about deployments instead of a rigorous &lt;em>Don&amp;rsquo;t deploy on Fridays&lt;/em> or anyday YOLO deploys? I think it&amp;rsquo;s about time to do so! Here are a few tips:&lt;/p>
&lt;h3 id="automated-testing---more-is-more">Automated Testing - More is more!&lt;/h3>
&lt;p>Automated testing is your best friend when it comes to catching issues early. Think of it as your safety net. By implementing comprehensive automated tests, you can ensure that your code is functioning as expected before it even hits production. This includes&lt;/p>
&lt;ul>
&lt;li>unit tests, which check individual components&lt;/li>
&lt;li>integration tests, which ensure that different parts of your application work together&lt;/li>
&lt;li>and end-to-end tests, which simulate real user scenarios&lt;/li>
&lt;li>performance validation, which measures how speedy operations are being completed&lt;/li>
&lt;li>security tests&lt;/li>
&lt;li>accessibility tests&lt;/li>
&lt;/ul>
&lt;p>The more you test, the more confident you can be in your deployment - regardless of the day of the week!&lt;/p>
&lt;h3 id="continuous-integration-and-continuous-deployment-cicd">Continuous Integration and Continuous Deployment (CI/CD)&lt;/h3>
&lt;p>CI/CD pipelines are the backbone of deployment practices. By adopting CI/CD, you automate the deployment process, ensuring that deployments are consistent and repeatable. This reduces the chance of human error (and yet again: We ALL make mistakes!) and allows you to deploy changes more frequently and reliably. There are lots of options to do this properly in Power Platform, and I&amp;rsquo;ll cover this topic in the next part of this series. Stay tuned!&lt;/p>
&lt;h3 id="monitoring-and-alerts">Monitoring and Alerts&lt;/h3>
&lt;p>Setting up robust monitoring and alerting systems is crucial for maintaining the health of your application post-deployment. These systems make sure that you are the first to know if something went wrong - instead of your users noticing it and calling the emergency hotline (which seems to be your mobile on a weekend). I&amp;rsquo;ll blog about how to monitor your Power Platform solutions in another post of this series.&lt;/p>
&lt;h3 id="rollback-plan">Rollback Plan&lt;/h3>
&lt;p>Always have a rollback plan in place. If something does go wrong (and it will!), you should be able to revert to the previous stable state quickly and efficiently. If you need to figure out how to roll back only when you need to, you will loose time (and get some extra grey hair).&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Yes, deploy (also) on Fridays! Don&amp;rsquo;t fear the weekday, but fear non-tested software and a poor deployment process. If you feel like &amp;ldquo;&lt;em>Yeah, we should probably invest into creating or solidifying our CI/CD process &lt;strong>BUT&lt;/strong> WE JUST DON&amp;rsquo;T HAVE TIME&lt;/em>&amp;rdquo; - then sorry to break it to you, but you are confusing cause and effect. Having even a basic testing routine and pipeline will not only make sure, that you will more often smaller changes (which reduces likelihood of BIG things to break), but also reduce the time you need to spend on every single deployment while ensuring easy revertability. You know, just in case something goes wrong :-)&lt;/p>
&lt;p>&lt;img alt="too busy" src="https://m365princess.com/images/1520048274837.jpg">&lt;/p>
&lt;hr>
&lt;p>This post is the first part of a series about good development and deployment practices. Stay tuned for the next parts where I focus on&lt;/p>
&lt;ul>
&lt;li>How to use Azure DevOps to deploy Power Platform projects&lt;/li>
&lt;li>How to test Power Platform solutions&lt;/li>
&lt;li>How to monitor Power Platform solutions with Azure Application Insights&lt;/li>
&lt;/ul>
&lt;p>Let me know if you have questions :-)&lt;/p></description></item><item><title>How to upload files to SharePoint for Dataverse integration in a Power Apps canvas app</title><link>https://m365princess.com/blogs/sharepoint-dataverse-canvas/</link><pubDate>Tue, 09 Jul 2024 08:29:58 +0000</pubDate><guid>https://m365princess.com/blogs/sharepoint-dataverse-canvas/</guid><description>&lt;h2 id="use-case">Use Case&lt;/h2>
&lt;p>Recently, someone asked me if it was possible to utilize the SharePoint integration in Dataverse not only from a model-driven app, but also from a canvas app. Challenge accepted!&lt;/p>
&lt;p>Tl;dr: yes, it is possible and it&amp;rsquo;s easier than you might think!&lt;/p>
&lt;h2 id="some-prep-work">Some prep work&lt;/h2>
&lt;p>First, we will need to do some prep work.&lt;/p>
&lt;ol>
&lt;li>Setup SharePoint integration in Dataverse - here is a &lt;a href="https://www.matthewdevaney.com/how-to-setup-sharepoint-integration-model-driven-power-apps/">good blog post by Matthew Devaney&lt;/a> that will guide you&lt;/li>
&lt;li>Make sure that the SharePoint folders that get created, do not append the record name of the Dataverse record with some GUID (Microsoft does that so that there are no duplicates, if you for example name the record like a customer and then have 5 customers with name &lt;code>Miller&lt;/code>.) You can do this with an &lt;a href="https://github.com/seanmcne/OrgDbOrgSettings">amazing tool&lt;/a> by Sean McNellis, &lt;a href="https://spandcrm.com/2016/06/24/removing-guids-from-folders-in-sharepoint-created-by-crm-2013/">Shaun Wilkinson&lt;/a> describes how to do this.&lt;/li>
&lt;li>We will need the &lt;strong>SiteId&lt;/strong> and &lt;strong>DriveId&lt;/strong> of our SharePoint library. You can get those using &lt;a href="aka.ms/ge">Graph Explorer&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="the-canvas-app">The canvas app&lt;/h2>
&lt;p>At a minimum, you will need to have 3 things&lt;/p>
&lt;ol>
&lt;li>a connector, that does the heavy lifting calling the right endpoint in the Graph API to upload a file to a specific folder - we will use the &lt;strong>Office 365 Groups&lt;/strong> connector&lt;/li>
&lt;li>an attachment control (you get this from a form, connect this to a random SharePoint list, cut out the &lt;strong>DatacardValue&lt;/strong> control, rename to &lt;strong>Attachments&lt;/strong> and delete the rest of the form)&lt;/li>
&lt;li>a button that will take care of the action.&lt;/li>
&lt;/ol>
&lt;p>I also added a table/gallery so that I can choose the record to which I want to associate a file.&lt;/p>
&lt;p>It looks like this:&lt;/p>
&lt;p>&lt;img alt="canvas app" src="https://m365princess.com/images/canvas-1.png">&lt;/p>
&lt;h3 id="tablegallery">Table/Gallery&lt;/h3>
&lt;ul>
&lt;li>Add the Dataverse table to your app and connect your table/gallery to it&lt;/li>
&lt;li>Add the &lt;strong>Office 365 Groups&lt;/strong> connector&lt;/li>
&lt;li>In the &lt;strong>OnSelect&lt;/strong> of the table, put&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">
Set(varDriveId, &amp;#34;&amp;lt;DriveId goes in here&amp;gt;&amp;#34;);
Set(varSiteId, &amp;#34;&amp;lt;SiteID goes in here&amp;gt;&amp;#34;);
Set(varFolder, Table1.Selected.Name)
&lt;/code>&lt;/pre>&lt;h3 id="button">Button&lt;/h3>
&lt;ul>
&lt;li>Add a button&lt;/li>
&lt;li>Put in the &lt;strong>OnSelect&lt;/strong>&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">
ForAll(
Attachments.Attachments,
Office365Groups.HttpRequest(
&amp;#34;https://graph.microsoft.com/v1.0/sites/&amp;#34; &amp;amp; varSiteId &amp;amp; &amp;#34;/drives/&amp;#34; &amp;amp; varDriveId &amp;amp; &amp;#34;/root:/&amp;#34; &amp;amp; varFolder &amp;amp; &amp;#34;/&amp;#34; &amp;amp; ThisRecord.Name &amp;amp; &amp;#34;:/content&amp;#34;,
&amp;#34;PUT&amp;#34;,
ThisRecord.Value
)
);
Reset(Attachments);
&lt;/code>&lt;/pre>&lt;p>This will upload all your files that you select with the &lt;strong>Attachments&lt;/strong> control to a folder that has the same &lt;strong>Name&lt;/strong> like the associated record that we selected in the table/gallery. If the folder does not exist yet, it will be created, if it already exists, then the file will be only uploaded into the folder.&lt;/p>
&lt;p>As a result, regardless if we use the canvas app or the model-driven app to upload files, they all get associated with the correct record in Dataverse.&lt;/p>
&lt;p>&lt;img alt="folders in the SharePoint library" src="https://m365princess.com/images/SP-library-folders.png">&lt;/p>
&lt;p>If you&amp;rsquo;d like to know how to display previews from files on SharePoint in the canvas app, &lt;a href="https://dianabirkelbach.wordpress.com/2023/08/31/recursive-retrieval-of-sharepoint-documents-in-canvas-apps-using-the-graphapi-connector-and-powerfx/">here is a neat blog post by Diana Birkelbach&lt;/a>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Canvas app can work nicely in addition to a model driven app with SharePoint integration, powered by the Office 365 Groups connector. Let me know what you think!&lt;/p></description></item><item><title>How to build a deskbooking tool with Microsoft Planner and Power Automate</title><link>https://m365princess.com/blogs/build-deskbooking-tool/</link><pubDate>Wed, 03 Jul 2024 06:49:31 +0000</pubDate><guid>https://m365princess.com/blogs/build-deskbooking-tool/</guid><description>&lt;h2 id="use-case">Use Case&lt;/h2>
&lt;p>Recently, a customer asked if it was possible to low code build a desk booking tool without using Power Apps.&lt;/p>
&lt;p>Tl;dr: yes, it is! We will leverage the UI of Microsoft Planner and use Power Automate to do the heavy lifting. Each bucket in Planner represents a day where desks are bookable, each task in Planner represents a room in which desks are present.&lt;/p>
&lt;p>&lt;img alt="overview" src="https://m365princess.com/images/rooms.png">&lt;/p>
&lt;h2 id="features">Features&lt;/h2>
&lt;ul>
&lt;li>Planner &lt;strong>Rooms&lt;/strong> starts with 21 buckets for each day in the next three weeks&lt;/li>
&lt;li>Each bucket is initially filled from a template bucket in a &lt;strong>Rooms Template&lt;/strong> plan. It consists of all tasks which represent the rooms
&lt;ul>
&lt;li>the description shows information about the room&lt;/li>
&lt;li>the checklist shows who already booked a desk on this day in the room&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>A new bucket gets created each day&lt;/li>
&lt;li>The oldest bucket (which now represents the day before the current day) is archived to an &lt;strong>Rooms Archive&lt;/strong> plan.&lt;/li>
&lt;/ul>
&lt;h2 id="how-to-build-this">How to build this&lt;/h2>
&lt;h3 id="planner">Planner&lt;/h3>
&lt;p>We need to create three plans:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Rooms Template&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Rooms&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Rooms Archive&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h4 id="rooms-template">Rooms Template&lt;/h4>
&lt;ol>
&lt;li>Create a bucket&lt;/li>
&lt;li>Create a task for every room with a description&lt;/li>
&lt;li>For each desk, create a checklist item and put a placeholder in like &amp;ldquo;Type in your name here&amp;rdquo;&lt;/li>
&lt;/ol>
&lt;h4 id="rooms">Rooms&lt;/h4>
&lt;p>For now, leave empty, we will deal with this a bit later.&lt;/p>
&lt;h4 id="rooms-archive">Rooms Archive&lt;/h4>
&lt;p>For now, leave empty.&lt;/p>
&lt;h3 id="power-automate-flow-to-initially-fill-the-rooms-plan">Power Automate flow to initially fill the Rooms plan&lt;/h3>
&lt;p>To initially fill the &lt;strong>Rooms&lt;/strong> with 21 buckets (which represent the next 21 days) we will employ a Power Automate flow.&lt;/p>
&lt;ol>
&lt;li>Trigger: Manual trigger (it only needs to run once)&lt;/li>
&lt;li>Planner: List tasks from the &lt;strong>Room Template&lt;/strong> plan&lt;/li>
&lt;li>Initialize a variable &lt;strong>currentDay&lt;/strong> (string)&lt;/li>
&lt;li>in a Do Until &lt;code>(Currentday eq FormatDateTime(addDays(utcNow(), 21), 'D'))&lt;/code>
&lt;ul>
&lt;li>Set the &lt;strong>currentDay&lt;/strong> variable to &lt;code>FormatDateTime(addDays(utcNow(), 21), 'D')&lt;/code>&lt;/li>
&lt;li>Planner: Create a new task with the Name of the currentDay variable&lt;/li>
&lt;li>Add an Apply to each
&lt;ul>
&lt;li>Planner: Get task details from the &lt;strong>Rooms template&lt;/strong>&lt;/li>
&lt;li>Planner: Create a task in the &lt;strong>Rooms&lt;/strong> plan with the title bucket id we already obtained&lt;/li>
&lt;li>Planner: Update task details - for the description&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="do until" src="https://m365princess.com/images/do_until.png">&lt;/p>
&lt;p>Now run the flow and see that your Rooms plan automagically gets populated with 21 buckets containing all the tasks that represent the rooms. You can turn off the flow now, as we don&amp;rsquo;t need to run it anymore.&lt;/p>
&lt;h3 id="power-automate-flow-to-take-care-of-a-new-bucket-to-be-created-every-single-day">Power Automate flow to take care of a new bucket to be created every single day&lt;/h3>
&lt;p>First, some visual representation of our flow:&lt;/p>
&lt;p>&lt;img alt="overview of 2nd flow" src="https://m365princess.com/images/overview-2.png">&lt;/p>
&lt;ol>
&lt;li>
&lt;p>As we want this to run every day, we will start with a &lt;strong>Recurrence&lt;/strong> and let it run for example at 6am.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>We will now initialize two variables:&lt;/p>
&lt;ol>
&lt;li>&lt;code>yesterday&lt;/code> of type string with a Value: &lt;code>formatDateTime(addDays(utcNow(), -1), 'D')&lt;/code>&lt;/li>
&lt;li>&lt;code>day 22&lt;/code> of type string with Value: &lt;code>formatDateTime(addDays(utcNow(), 22), 'D')&lt;/code>&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>In a &lt;strong>Scope&lt;/strong> (makes it look neat and tidy):&lt;/p>
&lt;ol>
&lt;li>Planner - Create a bucket for the new day. &lt;strong>Name&lt;/strong>: our &lt;code>day 22 &lt;/code> variable, &lt;strong>Group Id&lt;/strong> and &lt;strong>Plan Id&lt;/strong> need to be the &lt;strong>Rooms&lt;/strong> plan.&lt;/li>
&lt;li>Planner - List tasks. &lt;strong>Group Id&lt;/strong> and &lt;strong>PlanId&lt;/strong> need to be the &lt;strong>RoomsTemplate&lt;/strong> plan&lt;/li>
&lt;li>Apply to each
&lt;ol>
&lt;li>Planner: Get task details&lt;/li>
&lt;li>Planner: Create a task (Preview)
&lt;img alt="Scope new Day" src="https://m365princess.com/images/scope_newDay1.png">&lt;/li>
&lt;li>Planner: Update task details
&lt;img alt="scope new day 2" src="https://m365princess.com/images/scope_newDay2.png">
This will create a new bucket for the new day and get the task from our template. We will then loop through all of the tasks of the template and re-create them in the new bucket including description and checklist&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>Let&amp;rsquo;s create the next &lt;strong>Scope&lt;/strong> for archiving the oldest bucket (yesterday)&lt;/p>
&lt;ol>
&lt;li>Planner - List buckets in &lt;strong>Rooms&lt;/strong>&lt;/li>
&lt;li>Filter array - to get yesterday&amp;rsquo;s bucket &lt;code>value Name&lt;/code> needs to equal our &lt;code>yesterday&lt;/code> variable&lt;/li>
&lt;li>Use a &lt;strong>Compose&lt;/strong> action to select the &lt;strong>id&lt;/strong>&lt;/li>
&lt;li>Planner: List tasks &lt;strong>Rooms&lt;/strong>&lt;/li>
&lt;li>Planner: Create bucket with &lt;strong>Name&lt;/strong> of the &lt;code>yesterday&lt;/code> variable in &lt;strong>RoomsArchive&lt;/strong> plan&lt;/li>
&lt;li>Apply to each
&lt;ol>
&lt;li>condition if &lt;code>items('Apply_to_Each)?['bucketid']&lt;/code> equals the &lt;strong>id&lt;/strong> we have in our &lt;strong>Compose&lt;/strong> action
&lt;ol>
&lt;li>yes:
&lt;ol>
&lt;li>Planner: Create a task in &lt;strong>Archive&lt;/strong>&lt;/li>
&lt;li>Planner: Get task details from room&lt;/li>
&lt;li>Apply to each
&lt;ol>
&lt;li>Planner: Checklist: Update task details from &lt;strong>Rooms&lt;/strong> to &lt;strong>Archive&lt;/strong>
&lt;img alt="update task details" src="https://m365princess.com/images/updateTaskDetails.png">&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>no: leave empty&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>outside (!) of the 2nd (!) but inside of the first loop in this scope: Planner: Delete a task&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;p>This scope takes care of first recreating all the tasks in the &lt;strong>RoomsArchive&lt;/strong> plan and then deletes the tasks in yesterday&amp;rsquo;s bucket in the &lt;strong>Rooms&lt;/strong> plan.&lt;/p>
&lt;p>We will now create the last scope - which deletes yesterday&amp;rsquo;s bucket. Unfortunately, there is no action in the Planner connector that would allow us to delete a bucket - so we need to handle this ourselves. We will leverage an &lt;strong>HTTP&lt;/strong> request against the Microsoft Graph API. Following the &lt;a href="https://learn.microsoft.com/graph/api/plannertask-delete?view=graph-rest-1.0&amp;tabs=http">docs&lt;/a>, we need to hit &lt;code>DELETE /planner/tasks/{id}&lt;/code>. To authenticate against Graph API, we need to perform an app registration in Entra ID. If you don&amp;rsquo;t know how this works, you can look this up &lt;a href="https://www.m365princess.com/blogs/cli-microsoft-365-power-platform/">here&lt;/a> if you prefer a terminal experience or &lt;a href="https://www.m365princess.com/blogs/2021-02-10-how-to-get-started-with-http-requests-in-power-automate/">here&lt;/a> if you like GUIs more.&lt;/p>
&lt;p>So please first go ahead and register your app in Entra ID, note your tenant id, app id and app secret. For permissions, we will use &lt;code>Tasks.ReadWrite.All&lt;/code>
In this scope&lt;/p>
&lt;ol>
&lt;li>Apply to each&lt;/li>
&lt;li>condition if the bucket name equals our &lt;code>yesterday&lt;/code> variable
&lt;ol>
&lt;li>yes:
&lt;ol>
&lt;li>Apply to each - Body of our Filter array&lt;/li>
&lt;li>HTTP request to &lt;code>https://graph.microsoft.com/v1.0/planner/buckets/@{items('Apply_to_each_-_check_to_only_get_yesterday''s_bucket')?['id']}&lt;/code>&lt;/li>
&lt;li>Headers: &lt;strong>If-Match&lt;/strong>: &lt;code>&amp;quot;@items('Apply_to_each_2')['@odata.etag']&amp;quot;&lt;/code>&lt;/li>
&lt;li>Fill in the values for tenant id, app id and app secret, audience is &lt;code>https://graph.microsoft.com&lt;/code>.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;p>This will delete the empty bucket.&lt;/p>
&lt;p>Here is the full flow:&lt;/p>
&lt;p>&lt;img alt="full flow" src="https://m365princess.com/images/overview-full.png">&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>We now built with 2 quite simple flows a desk booking tool - without the need to build our own UI and in a real low-code manner :-)&lt;/p>
&lt;p>The past booked rooms get archived for traceability reasons (and of course we could set up another flow that would delete those after after a while as well, every day a new set of rooms gets automatically created and everyone can book their rooms up to 3 weeks in advance. Pretty neat, right? Let me know what you think!&lt;/p></description></item><item><title>How to organize your homeoffice with NFC tags and Apple</title><link>https://m365princess.com/blogs/organize-homeoffice-nfc-tags-apple/</link><pubDate>Thu, 29 Feb 2024 07:34:11 +0000</pubDate><guid>https://m365princess.com/blogs/organize-homeoffice-nfc-tags-apple/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>With NFC tags and an iPhone you can quickly build a nice system to organize and find all your cables, adapters and more!&lt;/p>
&lt;h2 id="nfc-tags">NFC tags&lt;/h2>
&lt;h3 id="what-is-an-nfc-tag">What is an NFC tag?&lt;/h3>
&lt;p>An NFC tag (Near field communications) tag is a little sticker that has a microchip in it, which you can program to your needs. It will communicate with other NFC enabled devices, such as your iPhone. You can get those stickers very cheap, I bought &lt;a href="https://www.amazon.de/dp/B06XH2R5ZP?psc=1&amp;ref=ppx_yo2ov_dt_b_product_details">these one here&lt;/a>.&lt;/p>
&lt;h2 id="create-an-organization-system">Create an organization system&lt;/h2>
&lt;h3 id="use-case">Use case&lt;/h3>
&lt;p>I don&amp;rsquo;t know about all of you&amp;hellip;but personally, I have a lot of boxes with too many cables in them. I don&amp;rsquo;t want to throw them away, because I trust that I will need all of them at some point.&lt;/p>
&lt;p>What I want:&lt;/p>
&lt;p>💫 I want to be able to scan an NFC tag on the outside of my box so that a list of the contents pops up on my phone.&lt;/p>
&lt;p>💫 Also I want to be able to add items - but with a little help of &lt;del>my friends&lt;/del> AI.&lt;/p>
&lt;p>💫 Last but not least I want to be able to search an item across all boxes and it shall return the correct box number for me.&lt;/p>
&lt;p>&lt;img alt="nfc tags" src="https://m365princess.com/images/nfc.png">&lt;/p>
&lt;h3 id="prep-work">Prep work&lt;/h3>
&lt;ol>
&lt;li>I put an NFC tag on each of my boxes and write a number on it.&lt;/li>
&lt;li>I take my iPhone, open the &lt;strong>Notes&lt;/strong> app and create a folder &lt;strong>Boxes&lt;/strong> in it.&lt;/li>
&lt;li>For each and every box, I create a new note in that &lt;strong>Boxes&lt;/strong> folder and list all the items that are in that particular box. The title of each box should match the number of the NFC tag.&lt;/li>
&lt;li>Optionally, I take a photo looking inside the box and save it in the note as well.&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Notes app" src="https://m365princess.com/images/notes-app.png">&lt;/p>
&lt;h3 id="create-the-shortcut">Create the Shortcut&lt;/h3>
&lt;p>If I scan a code, my phone shall open a menu from which I can choose&lt;/p>
&lt;ol>
&lt;li>Show me what is in the box I scanned (without me needing to open Pandora&amp;rsquo;s box)&lt;/li>
&lt;li>Let me add an item&lt;/li>
&lt;li>Let me search for an item as I don&amp;rsquo;t know in which box I will find it&lt;/li>
&lt;/ol>
&lt;p>So let&amp;rsquo;s get our hands dirty!&lt;/p>
&lt;h4 id="how-can-we-trigger-an-action">How can we trigger an action?&lt;/h4>
&lt;p>If you own an iPhone, its quite easy. In the pre-installed app on an iPhone, find the &lt;strong>Shortcuts&lt;/strong> app.&lt;/p>
&lt;ol>
&lt;li>Open it&lt;/li>
&lt;li>Select &lt;strong>Automation&lt;/strong>&lt;/li>
&lt;li>Select the &lt;strong>+&lt;/strong>&lt;/li>
&lt;li>Scroll down and find &lt;strong>NFC&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>NFC&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Scan&lt;/strong>&lt;/li>
&lt;li>Scan the NFC tag (just hold the upper part of your phone against ist)&lt;/li>
&lt;li>Type in a name or your NFC tag&lt;/li>
&lt;li>Select &lt;strong>Run immediately&lt;/strong> instead of &lt;strong>Run after confirmation&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Next&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New Blank Automation&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h4 id="how-do-we-define-the-actions">How do we define the actions?&lt;/h4>
&lt;p>As described, I first want to have a menu&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Add a &lt;strong>Choose from menu&lt;/strong> action and add&lt;/p>
&lt;ul>
&lt;li>&lt;strong>👀 View contents&lt;/strong>&lt;/li>
&lt;li>&lt;strong>➕ Add item&lt;/strong>&lt;/li>
&lt;li>&lt;strong>🔍 Find item&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>to the menu&lt;/p>
&lt;/li>
&lt;li>
&lt;p>That will automatically create a switch/case for you so that for each option, you can define the actions, that shall take place.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>When running the shortcut, it will look like this:&lt;/p>
&lt;p>&lt;img alt="choose menu" src="https://m365princess.com/images/choose-menu.png">&lt;/p>
&lt;ol start="3">
&lt;li>For &lt;strong>👀 View Contents&lt;/strong> you want to add an action from the app &lt;strong>Notes&lt;/strong>, more specifically, you want to open a specific note, the one that you asociated with this NFC tag, as every box has its own note. This means, once you scan the box, and select &lt;strong>👀 View Content&lt;/strong> it will open up the note that lists the content of that box and shows a picture whats inside the box.&lt;/li>
&lt;li>For &lt;strong>➕ Add item&lt;/strong> you will not want to manually type in what you add to the box. At least I didn&amp;rsquo;t want that but took the opportunity to include some nice AI feature in this little home organizing solution.&lt;/li>
&lt;/ol>
&lt;p>To do this, download &lt;strong>Toolbox Pro for Shortcuts&lt;/strong> from the App store, which gives you access to some more actions in Shortcuts. For this feature, we will use the &lt;strong>Recognize objects in Photo&lt;/strong> action.&lt;/p>
&lt;p>&lt;img alt="Toolbox Pro for Shortcuts" src="https://m365princess.com/images/toolbox-pro.png">&lt;/p>
&lt;p>Now in our Shortcut, we need to insert a &lt;strong>Take a photo with camera&lt;/strong>, then insert the &lt;strong>Recognize objects in Photo&lt;/strong> action, and will then use the &lt;strong>Notes&lt;/strong> action &lt;strong>Append to Note&lt;/strong> which means that we will add whatever the AI detected in the photo we took, a a text to the note.&lt;/p>
&lt;ol start="5">
&lt;li>For &lt;strong>🔍 Find item&lt;/strong>, we want to first bring up a &lt;strong>Ask for Text&lt;/strong> action which prompts us What are your looking for?, then we add a &lt;strong>Find Notes where Name contains Provided Input&lt;/strong>, before we open the note that it found so that we know which box we need to open.
This is how our entire Shortcut automation looks like:&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="ShortCuts app" src="https://m365princess.com/images/shortcut.png">&lt;/p>
&lt;p>💡Now you repeat this for all the boxes :-)&lt;/p>
&lt;p>As a result, when you scan one of your boxes, you will be prompted with the menu so that you can decide whether you want to (virtually) look inside the box (which will then open the correct note for you which lists all contents), or if you want to add an item via AI by taking a picture with your phone or if you want to type in an item so that it will tell you in which box you can find it.&lt;/p>
&lt;h2 id="more-ideas">More ideas&lt;/h2>
&lt;p>Pretty cool? Let&amp;rsquo;s have more ideas what to do with NFC tags and an iPhone!&lt;/p>
&lt;ul>
&lt;li>Build your own water tracker&lt;/li>
&lt;/ul>
&lt;p>I track my water intake and placed one NFC sticker at the water station in my kitchen. So whenever I fill my glass again, I scan the NFC tag.&lt;/p>
&lt;p>What happens:&lt;/p>
&lt;ol>
&lt;li>I log a sample to the Apple Health app (water, 250ml)&lt;/li>
&lt;li>I display a notification so that I know that this worked&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Get into deep focus work&lt;/li>
&lt;/ul>
&lt;p>I am easily distracted and deep focus work is as necessary as hard to get into for me. Two things though are helpful:&lt;/p>
&lt;ol>
&lt;li>Background noise (I use the built in ones by Apple - yes, that&amp;rsquo;s an accessiblity feature)&lt;/li>
&lt;li>Putting my phone on &lt;strong>Do not disturb&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>So why not make a &lt;strong>Focus&lt;/strong> NFC tag that does exactly that?&lt;/p>
&lt;p>&lt;img alt="focus" src="https://m365princess.com/images/focus.png">&lt;/p>
&lt;p>I hope you got some inspo on what to build - let me know if you have questions!&lt;/p></description></item><item><title>How to use Azure OpenAI with data in your SharePoint libraries</title><link>https://m365princess.com/blogs/2023-10-23-how-to-use-azure-openai-with-data-in-your-sharepoint-libraries-copy/</link><pubDate>Mon, 23 Oct 2023 07:34:11 +0000</pubDate><guid>https://m365princess.com/blogs/2023-10-23-how-to-use-azure-openai-with-data-in-your-sharepoint-libraries-copy/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>We can index documents from a SharePoint library with Azure Cognitive Search and then use an Azure OpenAI model to query the data. Using a custom connector, we can bring this power into Power Apps.&lt;/p>
&lt;p>&lt;img alt="icons of SP, Cognitive search and OpenAI" src="https://m365princess.com/images/icons.png">&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>Have documents (*.docx, *.pdf) in a SharePoint library&lt;/li>
&lt;li>Azure Cognitive Search service (Basic or Standard tier)&lt;/li>
&lt;/ul>
&lt;h2 id="preparations">Preparations&lt;/h2>
&lt;ul>
&lt;li>Note down the URL of the Site your library lives in&lt;/li>
&lt;li>Note down the URL and the Admin Key of your Azure Cognitive Search Service&lt;/li>
&lt;li>Turn on &lt;strong>System-Assigned Managed Identity&lt;/strong> in your Azure Cognitive Search Service&lt;/li>
&lt;li>Create an Entra Id app registration with the following parameters:
&lt;ul>
&lt;li>single tenant&lt;/li>
&lt;li>Microsoft Graph API delegated permissions (don&amp;rsquo;t forget to grant admin consent) for
&lt;ul>
&lt;li>Files.Read.All&lt;/li>
&lt;li>Sites.Read.All&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Mobile and desktop applications
&lt;ul>
&lt;li>enable &lt;code>https://login.microsoftonline.com/common/oauth2/nativeclient&lt;/code>
💡Note down the App Id.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="connect-your-sharepoint-library-with-azure-cognitive-search">Connect your SharePoint library with Azure Cognitive Search&lt;/h2>
&lt;h3 id="create-a-data-source-with-the-azure-cognitive-search-preview-rest-api">Create a Data Source with the Azure Cognitive Search Preview REST API&lt;/h3>
&lt;p>We will hop over to Postman to create a new data source for Azure Cognitive search using the REST API (it is still in preview.)&lt;/p>
&lt;ul>
&lt;li>&lt;strong>POST&lt;/strong> to &lt;code>https://&amp;lt;name-of-your Azure Cognitive Search service&amp;gt;.search.windows.net/datasources?api-version=2023-07-01-Preview&lt;/code>&lt;/li>
&lt;li>Params:
&lt;ul>
&lt;li>&lt;strong>api-version&lt;/strong>: &lt;code>2023-07-01-Preview&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Headers:
&lt;ul>
&lt;li>&lt;strong>Content-Type&lt;/strong>: &lt;code>application/json&lt;/code>&lt;/li>
&lt;li>&lt;strong>api&lt;/strong>-key: &lt;code>&amp;lt;the Admin key of your Azure Cognitive Search Service&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Body (Json):&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint-datasource&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;credentials&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;connectionString&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;SharePointOnlineEndpoint=&amp;lt;your SharePoint Site URL&amp;gt;;ApplicationId=&amp;lt;your App Id&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;container&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;defaultSiteLibrary&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;query&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will return a &lt;code>201&lt;/code> response, indicating that your datasource was created. You can check this in the Azure portal&lt;/p>
&lt;p>&lt;img alt="Azure Cognitive Search service with a freshly created datasource" src="https://m365princess.com/images/acs-datasource.png">&lt;/p>
&lt;h3 id="create-your-index">Create your Index&lt;/h3>
&lt;p>Let&amp;rsquo;s now leverage metadata of your document to enhance your search experience. This as well is done by using the REST API. We will again do this in Postman:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>POST&lt;/strong> to &lt;code>https://&amp;lt;name-of-your Azure Cognitive Search service&amp;gt;.search.windows.net/indexes?api-version=2023-07-01-Preview&lt;/code>&lt;/li>
&lt;li>Params:
&lt;ul>
&lt;li>&lt;strong>api-version&lt;/strong>: &lt;code>2023-07-01-Preview&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Headers:
&lt;ul>
&lt;li>&lt;strong>Content-Type&lt;/strong>: &lt;code>application/json&lt;/code>&lt;/li>
&lt;li>&lt;strong>api&lt;/strong>-key: &lt;code>&amp;lt;the Admin key of your Azure Cognitive Search Service&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Body (Json):&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint-index&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fields&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;metadata_spo_item_name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;filterable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;sortable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;facetable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;metadata_spo_item_path&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;filterable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;sortable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;facetable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;metadata_spo_item_content_type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;filterable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;sortable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;facetable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;metadata_spo_item_last_modified&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.DateTimeOffset&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;filterable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;sortable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;facetable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;metadata_spo_item_size&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.Int64&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;filterable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;sortable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;facetable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Edm.String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;searchable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;filterable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;sortable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;facetable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also this will return a &lt;code>201&lt;/code>.&lt;/p>
&lt;h3 id="create-your-indexer">Create your indexer&lt;/h3>
&lt;p>Additionally to that index, we want to create an indexer. It will later automate the indexing process from your SharePoint library to the Azure Cognitive Search service.&lt;/p>
&lt;p>Once again, we do this in Postman. This is a two-step process as we first need to &lt;strong>POST&lt;/strong> a &lt;strong>Create an indexer&lt;/strong> request - which will run and run and run as it is waiting for us to log in. So we will run a second call, which to &lt;strong>GET&lt;/strong> the indexer status. This will return a devicecode with which we can can sign in - Once we did that we can see that the call returns a &lt;code>200&lt;/code>. After that, the &lt;strong>POST&lt;/strong> will succeed and return a &lt;code>201&lt;/code> as well.&lt;/p>
&lt;p>So let&amp;rsquo;s do this together:&lt;/p>
&lt;h4 id="create-an-indexer-request">Create an indexer request&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>POST&lt;/strong> to &lt;code>https://&amp;lt;name-of-your Azure Cognitive Search service&amp;gt;.search.windows.net/indexers?api-version=2023-07-01-Preview&lt;/code>&lt;/li>
&lt;li>Params:
&lt;ul>
&lt;li>&lt;strong>api-version&lt;/strong>: &lt;code>2023-07-01-Preview&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Headers:
&lt;ul>
&lt;li>&lt;strong>Content-Type&lt;/strong>: &lt;code>application/json&lt;/code>&lt;/li>
&lt;li>&lt;strong>api-key&lt;/strong>: &lt;code>&amp;lt;the Admin key of your Azure Cognitive Search Service&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Body (Json):&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint-indexer&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dataSourceName&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint-datasource&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;targetIndexName&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint-index&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;parameters&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;batchSize&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;maxFailedItems&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;maxFailedItemsPerBatch&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;base64EncodeKeys&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;configuration&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;indexedFileNameExtensions&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;.pdf, .docx&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;excludedFileNameExtensions&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;.png, .jpg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dataToExtract&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;contentAndMetadata&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;schedule&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fieldMappings&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;sourceFieldName&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;metadata_spo_site_library_item_id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;targetFieldName&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;mappingFunction&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;base64Encode&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once you post this, it will - as explained, wait for you to log in.&lt;/p>
&lt;h4 id="get-indexer-status">Get indexer status&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>GET&lt;/strong> to &lt;code>https://&amp;lt;name-of-your Azure Cognitive Search service&amp;gt;.search.windows.net/indexers/sharepoint-indexer/status?api-version=2023-07-01-Preview&lt;/code>&lt;/li>
&lt;li>Params:
&lt;ul>
&lt;li>&lt;strong>api-version&lt;/strong>: &lt;code>2023-07-01-Preview&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Headers:
&lt;ul>
&lt;li>&lt;strong>Content-Type&lt;/strong>: &lt;code>application/json&lt;/code>&lt;/li>
&lt;li>&lt;strong>api&lt;/strong>-key: &lt;code>&amp;lt;the Admin key of your Azure Cognitive Search Service&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>No body needed :-)&lt;/p>
&lt;p>This will return a response that contains an &lt;strong>errormessage&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;errorMessage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code LFXXXXXP to authenticate.\r\nTo sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code LFXXXXXP to authenticate.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Copy the code and open the link, then paste the code into the devicelogin popup. Once you are logged in, you can close that browser tab again.&lt;/p>
&lt;p>Check now in the Azure portal that you do not only have an index, but also an indexer and documents indexed.&lt;/p>
&lt;p>&lt;img alt="Azure Cognitive search indexer" src="https://m365princess.com/images/acs-indexer.png">&lt;/p>
&lt;h2 id="test-your-app-in-the-playground">Test your app in the Playground&lt;/h2>
&lt;p>The Azure OpenAI playground is a fabulous way to test and try out - so let&amp;rsquo;s do this&lt;/p>
&lt;ul>
&lt;li>In the Playground, create a new deployment&lt;/li>
&lt;li>Select &lt;strong>Add your data&lt;/strong> and then &lt;strong>Add a data source&lt;/strong>&lt;/li>
&lt;li>Select the Azure Cognitive Search service, your Subscription its running in, and the index we just created. All of these will automagically 🪄 appear in the respective dropdown fields.&lt;/li>
&lt;li>Now proceed with the &lt;strong>index data field mapping&lt;/strong> - where you select all fields to be &lt;strong>content&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Save and close&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>You can now chat against your documents and ask the bot questions about it. By check/uncheck of the &lt;strong>Limit responses to your data content&lt;/strong> you can determin whether you want the bot only to consider content from your documents or not.&lt;/p>
&lt;p>You can now deploy this as a web app - Or you can walk with me some more steps and have that power in Power Apps or Power Automate&lt;/p>
&lt;h2 id="make-the-magic-happen-in-power-apps">Make the magic happen in Power Apps&lt;/h2>
&lt;p>To connect to this API from a Power Apps, we will need to create a custom connector.&lt;/p>
&lt;p>Following the Custom Connector wizard at &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>General
&lt;ul>
&lt;li>&lt;strong>Host&lt;/strong>: &lt;code>&amp;lt;name-of-your-openai-service&amp;gt;.openai.azure.com&lt;/code>&lt;/li>
&lt;li>&lt;strong>Base URL&lt;/strong>: &lt;code>/openai/deployments/&amp;lt;name-of-your-deployment&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="General tab of the connector" src="https://m365princess.com/images/cc-general.png">&lt;/p>
&lt;ul>
&lt;li>Security:
&lt;ul>
&lt;li>&lt;strong>Authentication type&lt;/strong>: &lt;code>API Key&lt;/code>&lt;/li>
&lt;li>&lt;strong>Parameter-label&lt;/strong>: &lt;code>api-key&lt;/code>&lt;/li>
&lt;li>&lt;strong>Parameter name&lt;/strong>: &lt;code>api-key&lt;/code>&lt;/li>
&lt;li>&lt;strong>Parameter location&lt;/strong>: &lt;code>Header&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Security tab of connector" src="https://m365princess.com/images/cc-security.png">&lt;/p>
&lt;ul>
&lt;li>Definition
&lt;ul>
&lt;li>
&lt;p>General:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Summary&lt;/strong>: &lt;code>ChatCompletion&lt;/code>&lt;/li>
&lt;li>&lt;strong>Description&lt;/strong>: &lt;code>ChatCompletion&lt;/code>&lt;/li>
&lt;li>&lt;strong>Operation ID&lt;/strong>: &lt;code>ChatCompletion&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Request URL: &lt;code>https://&amp;lt;name-of-your-openai-service&amp;gt;.openai.azure.com/openai/deployments/&amp;lt;name-of-your-deployment&amp;gt;/chat/completions&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Body:&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dataSources&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;AzureCognitiveSearch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;parameters&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;endpoint&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://&amp;lt;name-of-your-Azure-Cognitive-Search-service&amp;gt;.search.windows.net&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;your Azure Cognitive Search service Admin key&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;indexName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sharepoint-index&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;semanticConfiguration&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;queryType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;simple&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fieldsMapping&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;inScope&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;roleInformation&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;you are an AI assistant that helps people find information&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;What is the answer to life, universe, and everything?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;deployment&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;name-of-your-deployment&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;temperature&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;top_p&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;max_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">800&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;stop&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;stream&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img alt="Definitions tab of connector" src="https://m365princess.com/images/cc-definition.png">&lt;/p>
&lt;ul>
&lt;li>Test
&lt;ul>
&lt;li>Connection:
&lt;ul>
&lt;li>Create a new Connection, provide the API for your Azure Open Ai service as api-key.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Run your action, it should return a &lt;code>200&lt;/code> response code and show a response to your question. Please note that with the &lt;strong>inScope&lt;/strong> property you can limit the responses to just your documents.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Test tab of connector" src="https://m365princess.com/images/cc-test.png">&lt;/p>
&lt;p>You can now use this connector in either Power Apps or Power Automate.&lt;/p>
&lt;p>What will you build with it?&lt;/p></description></item><item><title>How to add and remove owners from a Power Automate flow with CLI Microsoft 365</title><link>https://m365princess.com/blogs/add-remove-owners-power-automate-flow-cli-microsoft-365/</link><pubDate>Mon, 13 Mar 2023 07:06:11 +0000</pubDate><guid>https://m365princess.com/blogs/add-remove-owners-power-automate-flow-cli-microsoft-365/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Using CLI Microsoft 365 you can easily add and remove owners of a flow.&lt;/p>
&lt;h2 id="use-case">Use Case&lt;/h2>
&lt;p>Let&amp;rsquo;s say, someone in your organization created a super cool Power Automate flow, but over time, this flow needs to be adjusted. Unfortunately, this super cool person is either on a long leave, doesn&amp;rsquo;t feel responsible anymore for that super cool flow after a nice promotion or left the organization. Too bad, that they were the only owner of that flow!&lt;/p>
&lt;h2 id="how-to-solve-this">How to solve this&lt;/h2>
&lt;h3 id="power-automate">Power Automate&lt;/h3>
&lt;p>Of course there are many paths that lead to Rome: You could create a Power Automate flow to remove/add owners&lt;/p>
&lt;p>&lt;img alt="add/remove owners of a flow with Power Automate" src="https://m365princess.com/images/flowmanageowners.png">&lt;/p>
&lt;p>which comes with the disadvantage, that you now need to manage this flow as well.&lt;/p>
&lt;h3 id="cli-for-microsoft-365">CLI for Microsoft 365&lt;/h3>
&lt;p>There is some really cool way though to manage owners of Power Automate flows: CLI Microsoft 365! I blogged about this awesome tool already quite a lot:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/cli-microsoft-365-power-platform/">Get started with CLI Microsoft 365 for Power Platform people&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/azure-ad-directory-extensions/">How to add Azure AD directory extensions&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/2021-03-11-5-commands-to-try-in-cli-for-microsoft-365-to-fall-in-love-with-it/">5 commands to try in CLI for Microsoft 365 to fall in love with it&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.m365princess.com/blogs/2021-02-17-how-to-get-started-with-cli-microsoft-365-and-adaptive-cards/">How to get started with CLI Microsoft 365 and Adaptive Cards&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>In their latest release &lt;a href="https://pnp.github.io/cli-microsoft365/about/release-notes/#v640-beta">v6.4.0.(beta)&lt;/a> they introduced 3 new commands for Power Automate:&lt;/p>
&lt;ul>
&lt;li>&lt;code>flow owner add&lt;/code>&lt;/li>
&lt;li>&lt;code>flow owner list&lt;/code>&lt;/li>
&lt;li>&lt;code>flow owner remove&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>which massively help with those typical &lt;em>Ooops we locked ourselves out of this crucial flow&lt;/em> situations.&lt;/p>
&lt;p>To benefit from these new commands, you need to first install the beta version with &lt;code>npm i -g @pnp/cli-microsoft365@next&lt;/code>. After that is completed, have a look at the docs to see which information you will need to provide to &lt;a href="https://pnp.github.io/cli-microsoft365/cmd/flow/owner/owner-add/">add or remove an owner&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>environmentName&lt;/code>&lt;/li>
&lt;li>&lt;code>flowName&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>To get these, we can again leverage the Power of CLI for Microsoft 365 with the command: &lt;code>m365 flow environment list -o text&lt;/code> - which gives us a nice uncluttered overview on the environments:&lt;/p>
&lt;p>&lt;img alt="environments" src="https://m365princess.com/images/environments.png">&lt;/p>
&lt;p>Now let&amp;rsquo;s list the flows with&lt;/p>
&lt;p>&lt;code>m365 flow list --environmentName Default-b469xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --asAdmin -o text&lt;/code> so that we get the &lt;code>flowName&lt;/code>&lt;/p>
&lt;p>With these 2 properties &lt;code>environmentName&lt;/code> and &lt;code>flowName&lt;/code> we will now feed the &lt;code>m365 flow owner add&lt;/code> or &lt;code>m365 flow owner remove&lt;/code> command:&lt;/p>
&lt;p>&lt;code>m365 flow owner add -e Default-b469xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx -f xxxxxxxx-xxxx-xxxx-xxxx-f7621ad284de --userName &amp;quot;alexw@myDomain.onmicrosoft.com&amp;quot; --roleName CanEdit --asAdmin&lt;/code>&lt;/p>
&lt;p>and/or&lt;/p>
&lt;p>&lt;code>m365 flow owner remove --userName adelev@myDomain.onmicrosoft.com -e Default-b469xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx -f Default-b469xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --asAdmin&lt;/code>&lt;/p>
&lt;p>you can check the result with&lt;/p>
&lt;p>&lt;code>m365 flow owner list --environmentName Default-b469e370-d6a6-45b5-928e-856ae0307a6d -f e6a2fbef-910a-4d53-ae8b-f7621ad284de --asAdmin -o text&lt;/code>&lt;/p>
&lt;p>or - if you assigned yourself as a new owner - even see it in the UI at &lt;a href="https://make.powerautomate.com">make.powerautomate.com&lt;/a>:&lt;/p>
&lt;p>&lt;img alt="2 owners in a Power Automate flow" src="https://m365princess.com/images/2owners.png">&lt;/p>
&lt;h2 id="some-thoughts-on-governance">Some thoughts on governance&lt;/h2>
&lt;p>Well, some admonishing index finger ideas:&lt;/p>
&lt;p>Please ensure proper governance for Power Platform - mission critical processes shouldn&amp;rsquo;t run in the context of a user who can always leave the organization. At least make sure, you have a 2nd owner to those flows - with CLI Microsoft 365, you can even automate this.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>Well, as I love how CLI MIcrosoft 365 also enables us to manage Power Platform as well, I would be super interested in adding/removing owners from Power Apps as well?&lt;/p></description></item><item><title>Get started with CLI Microsoft 365 for Power Platform people</title><link>https://m365princess.com/blogs/cli-microsoft-365-power-platform/</link><pubDate>Mon, 19 Dec 2022 10:32:48 +0000</pubDate><guid>https://m365princess.com/blogs/cli-microsoft-365-power-platform/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>&lt;a href="https://pnp.github.io/cli-microsoft365/">CLI for Microsoft 365&lt;/a> is an amazing tool to manage your Microsoft 365 tenant and SPFx projects. But did you know, that also people working with Power Platform can massively benefit from using it?&lt;/p>
&lt;p>To convince you, I chose a use case, that will probably relate to lots of people, but please be aware, that CLI for Microsoft can do so much more!&lt;/p>
&lt;h2 id="use-case-app-registrations-in-azure-active-directory">Use case: App registrations in Azure Active Directory&lt;/h2>
&lt;p>Very often when working with Power Automate or Power Apps, we want to leverage the power of &lt;a href="https://learn.microsoft.com/en-us/graph/overview">Microsoft Graph&lt;/a> API. To do so, we need to authenticate against Graph using Azure Active Directory (Azure AD) and register an application in the Azure portal at &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a>. Depending on permissions, redirect URI, secret etc. we need to perform several steps and take note of certain outputs. Wouldn&amp;rsquo;t it be nice if the entire app registration process was rather a one line command that automatically outputs the values we need?&lt;/p>
&lt;p>After installing CLI Microsoft 365 and logging in, all we need to do is&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">m365 aad app add &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--name &lt;span class="s1">&amp;#39;myApp001&amp;#39;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--redirectUris &lt;span class="s1">&amp;#39;https://global.consent.azure-apim.net/redirect&amp;#39;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--platform web &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--withSecret &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--apisDelegated &lt;span class="s1">&amp;#39;https://graph.microsoft.com/People.Read.All&amp;#39;&lt;/span> &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">--grantAdminConsent &lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>What this does is registering an application with the following parameters:&lt;/p>
&lt;ol>
&lt;li>Displayname of the app is &lt;code>myApp001&lt;/code>&lt;/li>
&lt;li>Redirect URI is &lt;code>https://global.consent.azure-apim.net/redirect&lt;/code> that is what you need for custom connectors in Power Platform&lt;/li>
&lt;li>It does create a secret (and will output it)&lt;/li>
&lt;li>It has delegated permissions for &lt;code>People.Read.All&lt;/code> on Graph API&lt;/li>
&lt;li>Admin consent is already granted&lt;/li>
&lt;/ol>
&lt;p>If we run this command (and we can do this as one-line as well without the backticks ` at the end of each line)&lt;/p>
&lt;p>&lt;code>m365 aad app add --name 'myApp001' --redirectUris 'https://global.consent.azure-apim.net/redirect' --platform web --withSecret --apisDelegated 'https://graph.microsoft.com/People.Read.All' --grantAdminConsent&lt;/code>&lt;/p>
&lt;p>we get the following output:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;appId&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;164298a8-504c-4234-a43a-XXXXXXXXXXXX&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;objectId&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;02b7577e-4d6b-478a-b34c-XXXXXXXXXXXX&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;tenantId&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;b469e370-d6a6-45b5-928e-XXXXXXXXXXXX&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;secrets&amp;#34;&lt;/span>: &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;displayName&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;Default&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;XXXXX~5FUlgaKtYEAJ-XXXXX~DWsnj6yerYATXXX&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which means, that we can use appId and the value of the secret in our custom connector or in the HTTP action of our Power Automate flow.&lt;/p>
&lt;h2 id="get-started-with-cli-for-microsoft-365">Get started with CLI for Microsoft 365&lt;/h2>
&lt;p>If you now want to try this out as well, you need to follow these steps:&lt;/p>
&lt;ol>
&lt;li>Install node.js
With node.js comes npm and we will install CLI for Microsoft 365 with npm. If you don&amp;rsquo;t have npm or node.js installed:
&lt;ul>
&lt;li>Open &lt;a href="https://nodejs.org/en/">node.js&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>18.12.1 LTS&lt;/strong>&lt;/li>
&lt;li>Install node.js&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Install CLI for Microsoft 365
&lt;ul>
&lt;li>Open a terminal of your choice, I use the built-in terminal of &lt;a href="https://code.visualstudio.com/">Visual Studio Code&lt;/a>&lt;/li>
&lt;li>Type &lt;code>npm i -g @pnp/cli-microsoft365&lt;/code> to install CLI for Microsoft 365 globally&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Login
&lt;ul>
&lt;li>To login, type &lt;code>m365 login&lt;/code>&lt;/li>
&lt;li>You will see a message like this: &lt;code>&amp;quot;To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code BAVUYWLZ3 to authenticate.&amp;quot;&lt;/code> - Do exactly that. Copy that code, open the URL and paste the code.&lt;/li>
&lt;li>Select &lt;strong>Next&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="device code login" src="https://m365princess.com/images/devicelogin.png">&lt;/p>
&lt;p>Now log into the tenant you want to connect with using your username and password (+ optional MFA)&lt;/p>
&lt;p>&lt;img alt="login" src="https://m365princess.com/images/login.png">&lt;/p>
&lt;p>You can close the &lt;code>https://login.microsoftonline.com/common/oauth2/deviceauth&lt;/code> page again&lt;/p>
&lt;p>💡 Until you type &lt;code>m365 logout&lt;/code>, &lt;a href="https://pnp.github.io/cli-microsoft365/concepts/persisting-connection/">you will stay logged in&lt;/a>.&lt;/p>
&lt;ol start="4">
&lt;li>Try out to register an app&lt;/li>
&lt;/ol>
&lt;p>Now lets see if that worked! Register your own application and validate in the Azure portal.&lt;/p>
&lt;ol start="5">
&lt;li>Tell people on twitter about it - &lt;a href="https://twitter.com/intent/tweet?text=I%20used%20%23CLIMicrosoft365%20to%20register%20an%20app%20in%20%23AzureAD%20and%20it%20worked%20like%20a%20charm%21%20-%20%0D%0A%0D%0ARead%20%40LuiseFreese%20s%20blog%20post%20about%20it%20here%3A+https%3A%2F%2Fm365princess.com%2Fblogs%2Fcli-microsoft-365-power-platform">https://twitter.com/intent/tweet?text=I%20used%20%23CLIMicrosoft365%20to%20register%20an%20app%20in%20%23AzureAD%20and%20it%20worked%20like%20a%20charm%21%20-%20%0D%0A%0D%0ARead%20%40LuiseFreese%20s%20blog%20post%20about%20it%20here%3A+https%3A%2F%2Fm365princess.com%2Fblogs%2Fcli-microsoft-365-power-platform&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>If you liked this experience, maybe you want to dip your toes even a bit further into CLI for Microsoft 365 - here is why you should absolutely consider that:&lt;/p>
&lt;ul>
&lt;li>it&amp;rsquo;s open-source and we are all here to learn&lt;/li>
&lt;li>it&amp;rsquo;s an amazing project with the most awesome contributors&lt;/li>
&lt;li>it has a lot of super-helpful commands around Power Apps, Power Automate, and more in Power Platform - more to come! Any ideas?&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://twitter.com/LuiseFreese/status/1604830279998251009">Let me know on twitter&lt;/a> :-)&lt;/p></description></item><item><title>Intro to custom functions in Power Apps</title><link>https://m365princess.com/blogs/intro-custom-functions-power-apps/</link><pubDate>Mon, 12 Dec 2022 09:33:44 +0000</pubDate><guid>https://m365princess.com/blogs/intro-custom-functions-power-apps/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Custom functions are a great way to make code reusable in Power Apps. By also leveraging Power Apps component libraries we can use the same code across apps in the environment.&lt;/p>
&lt;h2 id="once-there-was-a-time">Once there was a time&amp;hellip;&lt;/h2>
&lt;p>when we all started with Power Apps and realized, that all logic in the app is done by calling pre-built functions like &lt;code>CountRows(Inventory)&lt;/code>. With increased complexity of our apps, we sometimes combined and nested these functions and did some pretty cumbersome calculations in order to get the job done - unfortunately over and over again.&lt;/p>
&lt;h2 id="dry">DRY&lt;/h2>
&lt;p>In traditional programming there is a rule called &lt;em>DRY&lt;/em> which stands for &lt;em>Don&amp;rsquo;t repeat yourself&lt;/em>. It means, that you should never have to maintain the same code snippets in several places&amp;hellip; You update 5 and forget the 6th instance. This leads to nightmares when developing, maintaining, debugging.&lt;/p>
&lt;p>To tackle this in traditional languages, we first define a function and then call it where needed, so that we have a single source of truth. This reduces debugging time and increases developer productivity.&lt;/p>
&lt;p>Now what if we could do the very same thing in Power Apps? Instead of copy-pasting code snippets in one app or even across apps (&amp;quot;&lt;em>oh wait, I already solved tis problem in another app&lt;/em>&amp;quot;), we could create a parameterized function in a central repository, that we could call from any app.&lt;/p>
&lt;h2 id="how-to-create-a-custom-function">How to create a custom function&lt;/h2>
&lt;p>Custom functions are custom properties of components, instead of providing a consistent look &amp;amp; feel of UI elements, they allow makers to reuse code snippets. As all components, they should be stored in component libraries. A component library is a special type of canvas app with the purpose to hold your components in one place, again following the principle of &lt;em>DRY&lt;/em>.&lt;/p>
&lt;h3 id="create-a-component-library">Create a component library&lt;/h3>
&lt;ol>
&lt;li>Open &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a>&lt;/li>
&lt;li>Make sure that you are in the environment you want to use the components/custom functions in&lt;/li>
&lt;li>Select &lt;strong>Apps&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Component libraries&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New component library&lt;/strong>&lt;/li>
&lt;li>Type a name like &lt;code>Custom functions&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="enable-enhanced-component-properties">Enable enhanced component properties&lt;/h3>
&lt;p>As this feature is not GA&amp;rsquo;ed yet, we need to enable it in the experimental feature section in settings:&lt;/p>
&lt;p>&lt;img alt="settings" src="https://m365princess.com/images/custom-functions-settings.png">&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>Settings&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Upcoming features&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Experimental&lt;/strong>&lt;/li>
&lt;li>Type the first letters of &lt;code>components&lt;/code> into the search bar&lt;/li>
&lt;li>Switch the toggle of &lt;strong>Enhanced component properties&lt;/strong> to &lt;code>true&lt;/code>&lt;/li>
&lt;li>Close the settings again&lt;/li>
&lt;/ol>
&lt;h3 id="create-a-datefunctions-component">Create a dateFunctions component&lt;/h3>
&lt;ol>
&lt;li>Select &lt;strong>Components&lt;/strong>&lt;/li>
&lt;li>Rename &lt;strong>Component1&lt;/strong> to something like &lt;code>dateFunctions&lt;/code>&lt;/li>
&lt;li>Resize the component to &lt;strong>Width&lt;/strong> and &lt;strong>Height&lt;/strong>: &lt;code>0&lt;/code> - we don&amp;rsquo;t need ANY UI for this&lt;/li>
&lt;/ol>
&lt;p>We will group all functions, that are related to dates in this component.&lt;/p>
&lt;h4 id="create-a-new-custom-property-that-is-our-function">Create a new custom property (that is our function!)&lt;/h4>
&lt;ol start="4">
&lt;li>Select &lt;strong>New custom property&lt;/strong>&lt;/li>
&lt;li>Type &lt;code>getQuarter&lt;/code> as &lt;strong>DisplayName&lt;/strong>&lt;/li>
&lt;li>Set &lt;strong>Property type&lt;/strong> to &lt;code>Output&lt;/code> - as we want to output a value&lt;/li>
&lt;li>Set the &lt;strong>Data Type&lt;/strong> to &lt;strong>Number&lt;/strong> - as we want to return a number (it shall be either 1,2,3, or 4 for the 4 quarters in a year)&lt;/li>
&lt;li>Select &lt;strong>New parameter&lt;/strong>&lt;/li>
&lt;li>Type &lt;code>myDate&lt;/code> as &lt;strong>Parameter Name&lt;/strong>&lt;/li>
&lt;li>Select &lt;code>Date &amp;amp; Time&lt;/code> as &lt;strong>Parameter Type&lt;/strong> (as we will be providing a date)&lt;/li>
&lt;li>Check the &lt;strong>Required&lt;/strong> checkbox.&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>If you can&amp;rsquo;t see the &lt;strong>New parameter&lt;/strong> link, go back to &lt;a href="https://m365princess.com/blogs/intro-custom-functions-power-apps/#enable-enhanced-component-properties">Enable enhanced component properties&lt;/a> and follow instructions :-)&lt;/p>
&lt;/blockquote>
&lt;p>Now let&amp;rsquo;s add some code to our new function. You will notice your newly created property &lt;code>getQuarter&lt;/code> in the dropdown menu:&lt;/p>
&lt;p>&lt;img alt="new property" src="https://m365princess.com/images/customfunctions-new.png">&lt;/p>
&lt;p>Put the following code:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">If(
Month(myDate) &amp;lt;= 3,
1,
Month(myDate) &amp;gt;= 4 &amp;amp;&amp;amp; Month(myDate) &amp;lt;= 6,
2,
Month(myDate) &amp;gt;= 7 &amp;amp;&amp;amp; Month(myDate) &amp;lt; 9,
3,
Month(myDate) &amp;gt;= 10,
4
)
&lt;/code>&lt;/pre>&lt;p>As you can see, this function takes &lt;code>myDate&lt;/code>, which needs to be of type &lt;strong>Date and time&lt;/strong> as an input and outputs the respecting quarter as a number.&lt;/p>
&lt;h2 id="try-out-your-component">Try out your component&lt;/h2>
&lt;p>In this component library, you also have screens, but they are only there to test your components, not to be used as apps.&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>Screens&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>+ Insert&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Custom&lt;/strong> select &lt;strong>dateFunctions&lt;/strong> - by this you add the component the screen&lt;/li>
&lt;li>Insert a datepicker control &lt;code>DatePicker1&lt;/code>&lt;/li>
&lt;li>Insert a textlabel&lt;/li>
&lt;li>Set the &lt;strong>Text&lt;/strong> property of the label to &lt;code>dateFunctions_1.getQuarter(DatePicker1.SelectedDate)&lt;/code>&lt;/li>
&lt;li>If you now change the value of the datepicker, you will see different values in the textlabel - showing the quarter of the date.&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>Please note the &lt;code>_1&lt;/code> in the component instance 💡&lt;/p>
&lt;/blockquote>
&lt;p>Please publish your component library to make it available in the environment.&lt;/p>
&lt;p>The beautiful thing about that is, that you can now consume this function in any app - while the component itself will live and be edited only in the component library. This means that we have our single source of truth and that we enable more people to use code-snippets.&lt;/p>
&lt;h2 id="consume-your-component-in-another-app">Consume your component in another app&lt;/h2>
&lt;p>Let&amp;rsquo;s now consume our brand new function in a canvas app in the same environment. Either open an existing app or create a new one.&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>+ Insert&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Get more components&lt;/strong>
&lt;img alt="get more components" src="https://m365princess.com/images/customfunctions-more.png">&lt;/li>
&lt;li>After your component got imported, you should see this message:
&lt;img alt="component imported successfully" src="https://m365princess.com/images/customfunctions-success.png">&lt;/li>
&lt;li>Now add the component to a screen by selecting &lt;strong>dateFunctions&lt;/strong> under &lt;strong>Library components&lt;/strong>&lt;/li>
&lt;li>Now use the component in your app like you tried before in the component library.&lt;/li>
&lt;/ol>
&lt;h2 id="feedback-and-whats-next">Feedback and whats next?&lt;/h2>
&lt;p>You can now add more custom properties to your dateFunctions component, for other code snippets that you would like to reuse across apps.To do that, create a new custom property and define its parameters. To make it easier for you, you can kickstart your own custom functions component library with my open-source component library:&lt;/p>
&lt;p>&lt;a href="https://github.com/LuiseFreese/customfunctions">custom functions repo&lt;/a>&lt;/p>
&lt;p>Let me know what you think on twitter :-)&lt;/p></description></item><item><title>How to add Azure AD directory extensions</title><link>https://m365princess.com/blogs/azure-ad-directory-extensions/</link><pubDate>Fri, 09 Dec 2022 17:31:01 +0000</pubDate><guid>https://m365princess.com/blogs/azure-ad-directory-extensions/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>If you want a a queryable extension experience for objects in Azure AD, you can achieve that by first creating the extension definition and then associate that new extension to an object. You can now GET and PATCH the new extension via your applications ins the same tenant and even expose the new extension with a custom connector to Power Platform.&lt;/p>
&lt;h2 id="use-case">Use case&lt;/h2>
&lt;p>I wanted to build a Microsoft Graph toolkit lookalike person card, but noticed, that there is an attribute missing, that I&amp;rsquo;d love to include: &lt;em>pronouns&lt;/em>. Turned out, that even in 2022, pronouns are not available by default in the user object in Azure Active Directory and my aim was to not just have pronouns of &lt;em>some&lt;/em> people in &lt;em>one&lt;/em> application, but expose the pronouns to &lt;em>any&lt;/em> application in the tenant.&lt;/p>
&lt;p>&lt;img alt="person card overview" src="https://m365princess.com/images/personcard.png">&lt;/p>
&lt;h2 id="create-a-directory-extension-definition">Create a directory extension definition&lt;/h2>
&lt;p>To create a directory extension definition, we need to send a POST request to &lt;code>https://graph.microsoft.com/v1.0/applications/&amp;lt;app object id&amp;gt;/extensionProperties&lt;/code>, which means, that we need to register an app for that.&lt;/p>
&lt;h3 id="register-an-application-in-azure-ad">Register an application in Azure AD&lt;/h3>
&lt;p>You can register an app either in the Azure Portal or with this one-line-command in &lt;a href="https://pnp.github.io/cli-microsoft365">CLI for Microsoft 365&lt;/a>:&lt;/p>
&lt;p>&lt;code>m365 aad app add --name 'GraphConnector-App' --withSecret --apisApplication 'https://graph.microsoft.com/Directory.ReadWrite.All' --grantAdminConsent&lt;/code>&lt;/p>
&lt;h3 id="use-graph-explorer-to-create-the-extension">Use Graph Explorer to create the extension&lt;/h3>
&lt;p>Now we can log ino &lt;a href="https://aka.ms/ge">Graph Explorer&lt;/a> and do a &lt;code>POST&lt;/code> to&lt;/p>
&lt;p>&lt;code>https://graph.microsoft.com/v1.0/applications/&amp;lt;app object id&amp;gt;/extensionProperties&lt;/code>&lt;/p>
&lt;p>while replacing the &lt;code>&amp;lt;app object id&amp;gt;&lt;/code> (not the app id!) with your app object id.&lt;/p>
&lt;p>The body of the request is:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;pronouns&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dataType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;targetObjects&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This means, that we want to create an extension called &lt;code>pronouns&lt;/code> and associate this with the user object.&lt;/p>
&lt;p>As a response, we will get something like&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JSON" data-lang="JSON">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@odata.context&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/$metadata#applications(&amp;#39;4e3dbc8f-ca32-41b4-825a-346215d7d20f&amp;#39;)/extensionProperties/$entity&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;53645e93-9bad-4cfa-938c-eebaaa84d89f&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;deletedDateTime&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;appDisplayName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;GraphConnector&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dataType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;String&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;isSyncedFromOnPremises&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;extension_8ceab131804c47278376509101e201c3_userPronouns&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;targetObjects&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;User&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can see, that we have a new property &lt;strong>name&lt;/strong> with a value &lt;code>extension_8ceab131804c47278376509101e201c3_userPronouns&lt;/code>&lt;/p>
&lt;p>This new property can now be used from any application in your tenant&lt;/p>
&lt;p>This value always follows the pattern &lt;code>extension_{appId-without-hyphens}_{extensionProperty-name}&lt;/code>&lt;/p>
&lt;p>Make a note of this value somewhere.&lt;/p>
&lt;h3 id="patch-a-user-with-the-new-property">Patch a user with the new property&lt;/h3>
&lt;p>Now we can use a PATCH to &lt;a href="https://graph.microsoft.com/v1.0/users/%7BuserId%7D">https://graph.microsoft.com/v1.0/users/{userId}&lt;/a>&lt;/p>
&lt;p>with a body&lt;/p>
&lt;p>&lt;code>{&amp;quot;extension_8ceab131804c47278376509101e201c3_userPronouns&amp;quot;: &amp;quot;she/they&amp;quot;}&lt;/code>&lt;/p>
&lt;h3 id="check-if-the-new-property-is-in-place">Check if the new property is in place&lt;/h3>
&lt;p>Of course we want to check if it worked, so we will do a GET to &lt;code>https://graph.microsoft.com/v1.0/users/{userId}?$select=displayName, extension_8ceab131804c47278376509101e201c3_userPronouns&lt;/code>&lt;/p>
&lt;p>It is required to select the new property in the GET request, otherwise it won&amp;rsquo;t be returned.&lt;/p>
&lt;p>&lt;img alt="Graph explorer Get pronouns" src="https://m365princess.com/images/ge-pronounssuccess.png">&lt;/p>
&lt;h2 id="create-a-custom-connector-for-power-platform">Create a custom connector for Power Platform&lt;/h2>
&lt;p>Now that we created the extension and know how to patch and get data, we can build a custom connector so we can expose the functionality in Power Platform.&lt;/p>
&lt;h3 id="get-request">GET request&lt;/h3>
&lt;p>You can follow steps &lt;a href="https://m365princess.com/microsoft-graph-people-picker-power-apps#create-the-custom-connector">create the custom connector&lt;/a>, but instead of adding a &lt;strong>GetPeople&lt;/strong> action, add a &lt;strong>GetUsers&lt;/strong> action:&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>Definition&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New action&lt;/strong>&lt;/li>
&lt;li>Type in &lt;code>GetUsers&lt;/code> as &lt;strong>Summary&lt;/strong>, &lt;strong>Description&lt;/strong>, and &lt;strong>Operation ID&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Request&lt;/strong>, select &lt;strong>Import from sample&lt;/strong>&lt;/li>
&lt;li>Select &lt;code>GET&lt;/code> as &lt;strong>Verb&lt;/strong>&lt;/li>
&lt;li>Type &lt;code>https://graph.microsoft.com/v1.0/users/{userid}$select=displayName, extension_8ceab131804c47278376509101e201c3_userPronouns&lt;/code> as &lt;strong>URL&lt;/strong> (remember, it&amp;rsquo;s the same URL we used in Graph explorer, please replace the extension property)&lt;/li>
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Response&lt;/strong>, select &lt;strong>Add default response&lt;/strong>&lt;/li>
&lt;li>Paste in the response you got from Graph Explorer in the &lt;strong>Body&lt;/strong> field&lt;/li>
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Update Connector&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Test&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New Connection&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong> - you will annoyingly be redirected to the connections overview - go back to your custom connector&lt;/li>
&lt;li>Select &lt;strong>Test operation&lt;/strong> - you should receive a HTTP response &lt;code>200&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Close&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="patch-request">PATCH request&lt;/h3>
&lt;p>For the PATCH request, add another action to your custom connector:&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>Definition&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New action&lt;/strong>&lt;/li>
&lt;li>Type in &lt;code>PatchPronouns&lt;/code> as &lt;strong>Summary&lt;/strong>, &lt;strong>Description&lt;/strong>, and &lt;strong>Operation ID&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Request&lt;/strong>, select &lt;strong>Import from sample&lt;/strong>&lt;/li>
&lt;li>Select &lt;code>PATCH&lt;/code> as &lt;strong>Verb&lt;/strong>&lt;/li>
&lt;li>Type &lt;code>https://graph.microsoft.com/v1.0/users/{userid}&lt;/code> as &lt;strong>URL&lt;/strong> (remember, it&amp;rsquo;s the same URL we used in Graph explorer, please replace the extension property )&lt;/li>
&lt;li>Type in the &lt;strong>Body&lt;/strong>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;extension_8ceab131804c47278376509101e201c3_userPronouns&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;she/they&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="8">
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Response&lt;/strong>, select &lt;strong>Add default response&lt;/strong>&lt;/li>
&lt;li>Paste in the response you got from Graph Explorer in the &lt;strong>Body&lt;/strong> field&lt;/li>
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Update Connector&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Test&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New Connection&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong> - you will annoyingly be redirected to the connections overview - go back to your custom connector&lt;/li>
&lt;li>Select &lt;strong>Test operation&lt;/strong> - you should receive a HTTP response &lt;code>204&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Close&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>We can then set the &lt;strong>Text&lt;/strong> of a text label to&lt;/p>
&lt;p>&lt;code>GraphConnector.GetUser(&amp;lt;userId&amp;gt;,{'$select':&amp;quot;displayName, extension_8ceab131804c47278376509101e201c3_userPronouns&amp;quot;}).extension_8ceab131804c47278376509101e201c3_userPronouns&lt;/code> to display the pronouns of a certain user and set the &lt;strong>OnSelect&lt;/strong> of a button to&lt;/p>
&lt;p>&lt;code>GraphConnector.PatchPronouns(&amp;lt;userId&amp;gt;,{extension_8ceab131804c47278376509101e201c3_userPronouns:&amp;quot;she/they&amp;quot;)&lt;/code>&lt;/p>
&lt;h2 id="back-to-my-app-idea">Back to my app idea&amp;hellip;&lt;/h2>
&lt;p>Following &lt;a href="https://m365princess.com/microsoft-graph-people-picker-power-apps">my idea to re-create Microsoft Graph toolkit components as Power Apps canvas components&lt;/a> I did a more inclusive person card component - with pronouns.&lt;/p>
&lt;p>&lt;img alt="submit new pronouns" src="https://m365princess.com/images/pronouns.gif">&lt;/p>
&lt;h2 id="feedback-and-whatss-next">Feedback and whats&amp;rsquo;s next&lt;/h2>
&lt;p>What do you think? Which custom fields would you like to expose? Let me know on twitter. I will blog about how to create the component (and open-source it) in the next post.&lt;/p></description></item><item><title>Microsoft Graph toolkit people-picker lookalike for Power Apps</title><link>https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/</link><pubDate>Mon, 05 Dec 2022 14:54:59 +0000</pubDate><guid>https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/</guid><description>&lt;h2 id="tldr">tl:dr&lt;/h2>
&lt;p>&lt;a href="https://learn.microsoft.com/graph/toolkit/overview">Microsoft Graph toolkit&lt;/a> is a collection of reusable components for accessing and working with Microsoft Graph API - However, we can&amp;rsquo;t use them in Power Apps, which is why I started to build a Microsoft Graph toolkit (MGT) lookalike component library for Power Apps. The people-picker component looks like this in action:&lt;/p>
&lt;p>&lt;img alt="people picker in Teams app" src="https://m365princess.com/images/pp.gif">&lt;/p>
&lt;h2 id="people-picker">People-Picker&lt;/h2>
&lt;p>The MGT people picker is one of the most powerful components - it connects directly to your Azure Active Directory and shows your co-workers, which you can then select from. To make this work in a Power Apps canvas app, we need a few things:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/#power-platform-custom-connector">Power Platform custom connector&lt;/a> to &lt;a href="https://learn.microsoft.com/graph/api/overview?view=graph-rest-1.0">Microsoft Graph API&lt;/a>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>requires an app registration in Azure Active Directory&lt;/li>
&lt;li>makes this approach reusable&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>&lt;a href="https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/#ui-in-power-apps">UI in Power Apps&lt;/a>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>works best as a component as you will want to re-use this&lt;/li>
&lt;li>shall adapt to different themes in Teams&lt;/li>
&lt;/ul>
&lt;h3 id="power-platform-custom-connector">Power Platform Custom Connector&lt;/h3>
&lt;h4 id="understand-the-mepeople-endpoint">Understand the /me/people endpoint&lt;/h4>
&lt;p>Let&amp;rsquo;s first understand what our custom connector will do. We will want to leverage the &lt;code>https://graph.microsoft.com/v1.0/me/people&lt;/code> endpoint in our people picker to display people we work with - if you are unfamiliar with this, you can try out this query in &lt;a href="https://aka.ms/ge">Microsoft Graph explorer&lt;/a>. To execute this, Graph Explorer needs permission for &lt;code>People.Read&lt;/code> or &lt;code>People.Read.All&lt;/code>.&lt;/p>
&lt;p>&lt;img alt="Microsoft Graph Explorer" src="https://m365princess.com/images/ge.png">&lt;/p>
&lt;p>It will return an object also containing an array with users, following this schema&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@odata.context&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/$metadata#users(&amp;#39;2607b77e-22c4-4081-b278-a9516da4accd&amp;#39;)/people&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@odata.nextLink&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/v1.0/me/people?$skip=0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;7af3431f-ee68-4559-a1ca-17dc7bfcc269&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;displayName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Alex Wilber&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;givenName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Alex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;surname&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Wilber&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;birthday&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;personNotes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;isFavorite&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;jobTitle&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Marketing Assistant&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;companyName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;yomiCompany&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;department&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Marketing&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;officeLocation&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;131/1104&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;profession&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;userPrincipalName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;AlexW@hscluise.onmicrosoft.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;imAddress&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;sip:alexw@hscluise.onmicrosoft.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scoredEmailAddresses&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;address&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;AlexW@hscluise.onmicrosoft.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;relevanceScore&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1111&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;selectionLikelihood&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;notSpecified&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;phones&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;business&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;number&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;+1 858 555 0110&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;personType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;class&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Person&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;subclass&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;OrganizationUser&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="err">...&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="app-registration">App registration&lt;/h4>
&lt;p>As our connector will need to authenticate, we will register an application in Azure Active Directory (AAD) as our identity provider. This means, that we will let AAD generate a username/password combination (client id and client secret) and equip that with permissions needed to call &lt;code>/me/people&lt;/code> endpoint: &lt;code>People.Read&lt;/code> or &lt;code>People.Read.All&lt;/code>.&lt;/p>
&lt;p>To do that, you can either&lt;/p>
&lt;ol>
&lt;li>Open &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Azure Active Directory&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Add&lt;/strong>, then &lt;strong>App registration&lt;/strong>&lt;/li>
&lt;li>Enter a name for your app - choose something like &lt;code>GraphConnector-App&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Accounts in this organizational directory only&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Register&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Overview&lt;/strong> and copy &lt;strong>Directory (tenant) ID&lt;/strong> and &lt;strong>Application (client) ID&lt;/strong> - we will need these later&lt;/li>
&lt;li>Select &lt;strong>Certificates &amp;amp; secrets&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New client secret&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Save the value of this secret somewhere - we will need this later&lt;/li>
&lt;li>Select &lt;strong>Permissions&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Microsoft Graph&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Delegated permissions&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>People.Read&lt;/strong> or &lt;strong>People.Read.All&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Add permissions&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Grant admin consent for &lt;your tenant name>&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Yes&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>or if you use &lt;a href="https://pnp.github.io/cli-microsoft365/">CLI for Microsoft 365&lt;/a> you can register your app including permissions, granting admin consent and generating a secret with this command:&lt;/p>
&lt;p>&lt;code>m365 aad app add --name 'GraphConnector-App' --redirectUris 'https://global.consent.azure-apim.net/redirect' --platform web --withSecret --apisDelegated 'https://graph.microsoft.com/People.Read.All' --grantAdminConsent&lt;/code>&lt;/p>
&lt;p>This will output something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;appId&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;objectId&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;tenantId&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;secrets&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;displayName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Default&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;XXXXX~-XXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXX&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save the value of &lt;code>appId&lt;/code> and &lt;code>secret&lt;/code>somewhere.&lt;/p>
&lt;h3 id="create-the-custom-connector">Create the custom connector&lt;/h3>
&lt;ol>
&lt;li>Open &lt;a href="https://make.powerapps">make.powerapps.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Dataverse&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Custom Connectors&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New custom connector&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Create from blank&lt;/strong>&lt;/li>
&lt;li>Type in a name like &lt;code>GraphConnector&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Continue&lt;/strong>&lt;/li>
&lt;li>Type in &lt;code>graph.microsoft.com&lt;/code> as &lt;strong>Host&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Custom connector wizard step 1" src="https://m365princess.com/images/cc-1.png">&lt;/p>
&lt;ol start="9">
&lt;li>Select &lt;strong>Security&lt;/strong>&lt;/li>
&lt;li>Choose &lt;code>OAuth 2.0&lt;/code> as &lt;strong>Authentication type&lt;/strong>&lt;/li>
&lt;li>Choose &lt;code>Azure Active Directory&lt;/code> as &lt;strong>Identity Provider&lt;/strong>&lt;/li>
&lt;li>Paste your &lt;code>appId&lt;/code> from Azure AD app registration as &lt;strong>Client Id&lt;/strong>&lt;/li>
&lt;li>Paste your &lt;code>client secret&lt;/code> from Azure AD app registration as &lt;strong>Client secret&lt;/strong>&lt;/li>
&lt;li>Notice that &lt;strong>Login URL&lt;/strong> is already set to &lt;code>https://login.windows.net&lt;/code> - don&amp;rsquo;t change&lt;/li>
&lt;li>Notice that &lt;strong>Tenant Id&lt;/strong> is set to &lt;code>Common&lt;/code> - don&amp;rsquo;t touch this&lt;/li>
&lt;li>Type in &lt;code>https://graph.microsoft.com&lt;/code> as &lt;strong>Resource URL&lt;/strong>&lt;/li>
&lt;li>Notice that &lt;strong>Enable on behalf of login&lt;/strong> is set to &lt;code>false&lt;/code> - don&amp;rsquo;t change&lt;/li>
&lt;li>Type in &lt;code>People.Read.All&lt;/code> as &lt;strong>Scope&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Create connector&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h4 id="add-a-getpeople-action">Add a GetPeople action&lt;/h4>
&lt;p>Now that we have a custom connector, we want to add at least one action to it.&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>Definition&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New action&lt;/strong>&lt;/li>
&lt;li>Type in &lt;code>GetPeople&lt;/code> as &lt;strong>Summary&lt;/strong>, &lt;strong>Description&lt;/strong>, and &lt;strong>Operation ID&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Request&lt;/strong>, select &lt;strong>Import from sample&lt;/strong>&lt;/li>
&lt;li>Select &lt;code>GET&lt;/code> as &lt;strong>Verb&lt;/strong>&lt;/li>
&lt;li>Type &lt;code>https://graph.microsoft.com/v1.0/me/people&lt;/code> as &lt;strong>URL&lt;/strong> (remember, it&amp;rsquo;s the same URL we used in Graph explorer)&lt;/li>
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Under &lt;strong>Response&lt;/strong>, select &lt;strong>Add default response&lt;/strong>&lt;/li>
&lt;li>Paste in the response you got from Graph Explorer in the &lt;strong>Body&lt;/strong> field&lt;/li>
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Update Connector&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Test&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New Connection&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong> - you will annoyingly be redirected to the connections overview - go back to your custom connector&lt;/li>
&lt;li>Select &lt;strong>Test operation&lt;/strong> - you should receive a HTTP response &lt;code>200&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Close&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Custom connector last step" src="https://m365princess.com/images/cc-2.png">&lt;/p>
&lt;h3 id="ui-in-power-apps">UI in Power Apps&lt;/h3>
&lt;p>Now that we have the connector working, let&amp;rsquo;s&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/#create-an-app">create a Power Apps&lt;/a> canvas app &lt;a href="https://www.m365princess.com/blogs/2022-07-06-how-to-enhance-maker-experience-with-a-custom-theme-for-teams-apps-in-power-apps-studio/">using a theme&lt;/a> that reflects default, dark, high-contrast mode in Microsoft Teams&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/#create-a-canvas-component">create a reusable canvas component&lt;/a> as the people-picker&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>For all lovers of dark-mode: here is a little motivation to keep reading this post. This is what the people-picker will look like in dark mode in Microsoft Teams:&lt;/p>
&lt;p>&lt;img alt="people picker in dark mode" src="https://m365princess.com/images/pp-dark.png">&lt;/p>
&lt;h4 id="create-an-app">Create an app&lt;/h4>
&lt;ol>
&lt;li>Create a new app&lt;/li>
&lt;li>Save it locally&lt;/li>
&lt;li>Unpackage the app via &lt;a href="https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/canvas#pac-canvas-unpack">Power Platform CLI&lt;/a>&lt;/li>
&lt;li>Select the &lt;code>themes.json&lt;/code> file&lt;/li>
&lt;li>Replace its content by &lt;a href="https://github.com/pnp/powerapps-samples/blob/main/samples/fluentui-for-teams-theme/sourcecode/Themes.json">FluentUI for Teams theme&lt;/a>&lt;/li>
&lt;li>Package the app again via &lt;a href="https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/canvas#pac-canvas-pack">Power Platform CLI&lt;/a>&lt;/li>
&lt;li>Open the app from within &lt;a href="https://make.powerapps.com">Power Apps Studio&lt;/a>&lt;/li>
&lt;li>Add a Dropdown control &lt;code>drp_theme&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Items&lt;/strong> property to &lt;code>[&amp;quot;default&amp;quot;, &amp;quot;dark&amp;quot;, &amp;quot;high-contrast&amp;quot;]&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>OnChange&lt;/strong> of the dropdown and the &lt;strong>OnStart&lt;/strong> property of the App to the content of &lt;a href="https://gist.github.com/LuiseFreese/41354f8563b9ee239f1454ca799dab97">this file&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>Now you have an app that reflects the 3 themes of Microsoft Teams. It will automatically adjust to the theme that is used in the Teams client. For development and test purposes, you can use the dropdown to switch to another theme.&lt;/p>
&lt;h5 id="add-the-custom-connector">Add the custom connector&lt;/h5>
&lt;p>Now add the custom connector on the &lt;strong>Data&lt;/strong> tab to your app. Once you did that, select the &lt;strong>OnVisible&lt;/strong> of a screen and create a collection that contains people you work with:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">ClearCollect(
colPeople,
Filter(&amp;#39;GraphConnector&amp;#39;.GetPeople().value, personType.subclass = &amp;#34;OrganizationUser&amp;#34;)
);
&lt;/code>&lt;/pre>&lt;h5 id="add-the-office-365-users-connector">Add the Office 365 Users connector&lt;/h5>
&lt;p>Add this built-in connector to easily retrieve Profile pictures of people&lt;/p>
&lt;h4 id="create-a-canvas-component">Create a canvas component&lt;/h4>
&lt;ol>
&lt;li>Create a new component &lt;code>cmp_MGT_PeoplePicker&lt;/code>&lt;/li>
&lt;li>Enable &lt;strong>Access app scope&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>Let&amp;rsquo;s now understand what the people-picker is built:&lt;/p>
&lt;ol>
&lt;li>We will deal with two collections:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>colPeople - which contains the people our custom connector returns&lt;/li>
&lt;li>colAttendees - which contains only people that a user selected from the colPeople&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>Both collections will be reflected in two galleries - gal_People and gal_Attendees&lt;/li>
&lt;li>Both collections work exclusively, this means, that a user, who is in colPeople, can&amp;rsquo;t be in colAttendees and the other way around, as it wouldn&amp;rsquo;t make any sense to select a person twice&lt;/li>
&lt;li>A textinput control allows a user to type in a name they want to add&lt;/li>
&lt;li>A Chevron down icon allows a user to expand the entire colPeople&lt;/li>
&lt;li>A X icon allows a user to delete all items from the colAttendees collection&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="people picker component explained" src="https://m365princess.com/images/pp-explained.png">&lt;/p>
&lt;ol>
&lt;li>1 - textinput which filters the galPeople&lt;/li>
&lt;li>2 - x icon which resets the textinput&lt;/li>
&lt;li>3 - gal_People&lt;/li>
&lt;li>4 - gal_Attendees&lt;/li>
&lt;li>5 - Chevron down to expand/collapse gal_People&lt;/li>
&lt;/ol>
&lt;h5 id="textinput">Textinput&lt;/h5>
&lt;ol>
&lt;li>Add a TextInput &lt;code>txt_input&lt;/code>&lt;/li>
&lt;li>Set it&amp;rsquo;s &lt;strong>HintText&lt;/strong> to &lt;code>&amp;quot;Start typing a name&amp;quot;&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Default&lt;/strong> to &lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>OnSelect&lt;/strong> to &lt;code>If(IsEmpty(Self.Text), Set(isGallery, false), Set(isGallery, true))&lt;/code>&lt;/li>
&lt;li>Take care of colors:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>BorderColor&lt;/strong>: &lt;code>gblAppStyles.TextInput.BorderColor&lt;/code>,&lt;/li>
&lt;li>&lt;strong>Color&lt;/strong>: &lt;code>gblAppStyles.Label.Color&lt;/code>&lt;/li>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>gblAppStyles.TextInput.Fill&lt;/code>,&lt;/li>
&lt;li>&lt;strong>FocusedBorderColor&lt;/strong>: &lt;code>Self.BorderColor&lt;/code>,&lt;/li>
&lt;li>&lt;strong>HoverBorderColor&lt;/strong>: &lt;code>gblAppStyles.TextInput.BorderColor&lt;/code>,&lt;/li>
&lt;li>&lt;strong>HoverColor&lt;/strong>: &lt;code>gblAppStyles.Label.Color&lt;/code>,&lt;/li>
&lt;li>&lt;strong>HoverFill&lt;/strong>: &lt;code>Self.Fill&lt;/code>,&lt;/li>
&lt;li>&lt;strong>PressedColor&lt;/strong>: &lt;code>Self.Color&lt;/code>&lt;/li>
&lt;li>&lt;strong>PressedFill&lt;/strong>: &lt;code>Self.Fill&lt;/code>&lt;/li>
&lt;/ul>
&lt;h5 id="rectangle">Rectangle&lt;/h5>
&lt;p>we will now add a rectangle below the textinput to create that Teams look &amp;amp; feel&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>gblAppStyles.ButtonPrimary.BorderColor&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong>: &lt;code>2&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>txt_input.Width&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>txt_input.X&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>txt_input.Y+ txt_input.Height&lt;/code>&lt;/li>
&lt;/ol>
&lt;h5 id="gal_people">gal_people&lt;/h5>
&lt;ol>
&lt;li>Create a blank vertical gallery&lt;/li>
&lt;li>Set its &lt;strong>Items&lt;/strong> to &lt;code>Sort(Filter(colPeople,StartsWith(displayName, txt_input.Text )),displayName, Ascending)&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Height&lt;/strong> to &lt;code>CountRows(colPeople)*(Self.TemplateHeight+ Self.TemplatePadding)&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Width&lt;/strong> to &lt;code>txt_input.Width-txt_input.X*2&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Visible&lt;/strong> to &lt;code>isGallery&lt;/code>&lt;/li>
&lt;li>Add an image to the gallery&lt;/li>
&lt;li>Set&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>X&lt;/strong> to &lt;code>10&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>(Parent.TemplateHeight / 2) - (Self.Height / 2)&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong> and &lt;strong>Height&lt;/strong>: &lt;code>32&lt;/code>&lt;/li>
&lt;li>&lt;strong>Image&lt;/strong> to &lt;code>If(!IsBlank(ThisItem.userPrincipalName),Office365Users.UserPhoto(ThisItem.userPrincipalName),SampleImage)&lt;/code>&lt;/li>
&lt;/ul>
&lt;ol start="8">
&lt;li>Add a textlabel to the gallery&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Set &lt;strong>Color&lt;/strong> to &lt;code>gblAppStyles.Label.Color&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>HoverFill&lt;/strong> to &lt;code>gblAppStyles.TextInput.HoverFill&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Width&lt;/strong> to &lt;code>Parent.TemplateWidth&lt;/code> and &lt;strong>Height&lt;/strong> to &lt;code>Parent.TemplateHeight&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Text&lt;/strong> to &lt;code>ThisItem.displayName&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>OnSelect&lt;/strong> to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Collect(
colAttendees,
{
type: &amp;#34;required&amp;#34;,
emailAddress: {
name: gal_people.Selected.displayName,
address: gal_people.Selected.userPrincipalName
}
}
);
Remove(
colPeople,
ThisItem
);
Reset(txt_input)
&lt;/code>&lt;/pre>&lt;p>With that, we add people from our colPeople to our colAttendees and remove them from the colPeople. We also reset the textinput.&lt;/p>
&lt;h5 id="chevron-down">Chevron down&lt;/h5>
&lt;ol>
&lt;li>Add an image and set its &lt;strong>Image&lt;/strong> to&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">&amp;#34;data:image/svg+xml;utf8, &amp;#34; &amp;amp; EncodeUrl(
&amp;#34;
&amp;lt;svg width=&amp;#39;48&amp;#39; height=&amp;#39;48&amp;#39; viewBox=&amp;#39;0 0 48 48&amp;#39; fill=&amp;#39;&amp;#34; &amp;amp; If(
gblThemeHiCo,
&amp;#34;#ffffff&amp;#34;,
gblThemeDark,
&amp;#34;#ffffff&amp;#34;,
&amp;#34;#252423&amp;#34;
) &amp;amp; &amp;#34;&amp;#39; xmlns=&amp;#39;http://www.w3.org/2000/svg&amp;#39;&amp;gt;
&amp;lt;path d=&amp;#39;M8.36612 16.1161C7.87796 16.6043 7.87796 17.3957 8.36612 17.8839L23.1161 32.6339C23.6043 33.122 24.3957 33.122 24.8839 32.6339L39.6339 17.8839C40.122 17.3957 40.122 16.6043 39.6339 16.1161C39.1457 15.628 38.3543 15.628 37.8661 16.1161L24 29.9822L10.1339 16.1161C9.64573 15.628 8.85427 15.628 8.36612 16.1161Z&amp;#39; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;#34;
)
&lt;/code>&lt;/pre>&lt;p>This is the Fluent UI chevron down and it will now reflect the different themes.&lt;/p>
&lt;ol start="2">
&lt;li>Set the &lt;strong>OnSelect&lt;/strong> to&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Set(isGallery, !isGallery);
If(
CountRows(colPeople) = 0,
ClearCollect(
colPeople,
Filter(&amp;#39;GraphConnector&amp;#39;.GetPeople().value, personType.subclass = &amp;#34;OrganizationUser&amp;#34;)
)
);
&lt;/code>&lt;/pre>&lt;p>This controls how much we see from the gallery and also refreshes the colPeople when a user expands the gallery&lt;/p>
&lt;h5 id="gal_attendees">gal_Attendees&lt;/h5>
&lt;p>We need another gallery to show the selected people&lt;/p>
&lt;ol start="3">
&lt;li>Add a blank vertical gallery&lt;/li>
&lt;li>Set&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>5&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>txt_input.Y+ txt_input.Height&lt;/code>&lt;/li>
&lt;li>&lt;strong>Items&lt;/strong>: &lt;code>If(isGallery,FirstN(colAttendees,6),FirstN(colAttendees,3))&lt;/code>&lt;/li>
&lt;li>&lt;strong>WrapCount&lt;/strong>: &lt;code>If(isGallery,If(CountRows(colAttendees)&amp;lt;4,1,2),1)&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong>: &lt;code>If(isGallery,If(CountRows(colAttendees)&amp;lt;4,txt_input.Height*1,If(CountRows(colAttendees)&amp;lt;6,txt_input.Height*2),txt_input.Height*2),40)&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>This makes sure, that we wrap items dynamically and adjust the &lt;strong>Height&lt;/strong> of the gallery depending on how many items are selected. This still leaves a little problem - as there is not a lot of space where we can display all people in the gal_Attendees and we already limited this to either showing 3 (when collapsed) or 6 (when expanded), we should indicate, that there are more people added.&lt;/p>
&lt;h5 id="the-more-pill">The &amp;lsquo;more&amp;rsquo; pill&lt;/h5>
&lt;ol>
&lt;li>Outside of the gallery, add a new button &lt;code>btn_plus&lt;/code>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>3*(gal_attendees.TemplateWidth)+10&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>gal_attendees.Y + (gal_attendees.TemplateHeight-Self.Height)/2&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>40&lt;/code> and &lt;strong>Height&lt;/strong>: &lt;code>32&lt;/code>&lt;/li>
&lt;li>&lt;strong>Text&lt;/strong>: &lt;code>&amp;quot;+&amp;quot; &amp;amp;If(isGallery, CountRows(colAttendees)-6, CountRows(colAttendees)-3)&lt;/code>&lt;/li>
&lt;li>&lt;strong>DisplayMode&lt;/strong>: &lt;code>View&lt;/code>&lt;/li>
&lt;li>&lt;strong>Radius&lt;/strong>: &lt;code>1000&lt;/code>&lt;/li>
&lt;li>&lt;strong>Visible&lt;/strong> &lt;code>If(isGallery,CountRows(colAttendees)&amp;gt;6,CountRows(colAttendees)&amp;gt;3)&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>By that, we display this button only if we have more than 3 (collapsed) or more than 6 (expanded) and show the number of items in the collection (minus the items that are already shown)&lt;/p>
&lt;p>Now let&amp;rsquo;s fill the gallery with people we selected from the colPeople:&lt;/p>
&lt;ol>
&lt;li>Add a button &lt;code>btn_pill&lt;/code>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Set its &lt;strong>Displaymode&lt;/strong> to &lt;code>View&lt;/code>&lt;/li>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>gblAppStyles.TextInput.HoverFill&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>150&lt;/code> and &lt;strong>Height&lt;/strong>: &lt;code>32&lt;/code>&lt;/li>
&lt;li>&lt;strong>Radius&lt;/strong>: &lt;code>1000&lt;/code>&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>Add an image &lt;code>img_profilePic&lt;/code>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Set &lt;strong>Image&lt;/strong> to &lt;code>If(!IsBlank(ThisItem.emailAddress.address),Office365Users.UserPhotoV2(ThisItem.emailAddress.address),SampleImage)&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>btn_pill.X + 5&lt;/code>, and &lt;strong>Y&lt;/strong>: &lt;code>btn_pill.Y + (btn_pill.Height - Self.Height)/2&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>20&lt;/code>, and &lt;strong>Height&lt;/strong>: &lt;code>20&lt;/code>&lt;/li>
&lt;li>&lt;strong>Visible&lt;/strong>: &lt;code>CountRows(colAttendees) &amp;gt;0&lt;/code>&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>Add a textlabel &lt;code>lbl_displayName&lt;/code>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Set &lt;strong>Text&lt;/strong>: &lt;code>ThisItem.emailAddress.name&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>img_profilePicture.X + img_profilePic.Width&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>btn_pill.Width - img_profilePicture.Width - icn_close.Width-20&lt;/code> and &lt;strong>Height&lt;/strong>: &lt;code>20&lt;/code>&lt;/li>
&lt;li>Set: &lt;strong>Visible&lt;/strong>: &lt;code>CountRows(colAttendees) &amp;gt;0&lt;/code>&lt;/li>
&lt;/ul>
&lt;ol start="4">
&lt;li>Add an icon &lt;code>icn_close&lt;/code>&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>btn_pill.X + btn_pill.Width-Self.Width-10&lt;/code> and &lt;strong>Y&lt;/strong>: &lt;code>btn_pill.Y + (btn_pill.Height - Self.Height)/2&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong> and &lt;strong>Height&lt;/strong>: &lt;code>12&lt;/code>&lt;/li>
&lt;li>&lt;strong>Onselect&lt;/strong>&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Remove(colAttendees,ThisItem);Collect(colPeople,{userPrincipalName:ThisItem.emailAddress.address,displayName:ThisItem.emailAddress.name})
&lt;/code>&lt;/pre>&lt;p>With this we allow users to remove a person from the colAttendees and add them again to the colPeople.&lt;/p>
&lt;h5 id="the-x-icon">The X icon&lt;/h5>
&lt;p>Now let&amp;rsquo;s add an X icon &lt;code>icon_removeAttendees&lt;/code> so that a user can remove all people at once from the colAttendees and start all over again&lt;/p>
&lt;ol>
&lt;li>&lt;strong>OnSelect&lt;/strong>: &lt;code>Clear(colAttendees);ClearCollect(colPeople,'ProductivityHub-Connector'.GetMyPeople().value);Reset(txt_input)&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>gal_attendees.X +gal_attendees.Width-20&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>txt_input.Y+ (txt_input.Height - Self.Height)/2&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong> and &lt;strong>Width&lt;/strong>: &lt;code>16&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="some-more-context">Some more context&lt;/h2>
&lt;p>This component is part of an app - here is the real world use case: Finding meeting times for multiple colleagues.&lt;/p>
&lt;p>&lt;img alt="Find meeting times Power App" src="https://m365princess.com/images/findmeetingtimes.png">&lt;/p>
&lt;p>This app is inspired by &lt;a href="https://twitter.com/waldekm">Waldek Mastykarz&lt;/a> who created a similar app with Microsoft Graph toolkit. You can &lt;a href="https://blog.mastykarz.nl/find-meeting-times-schedule-meeting-microsoft-graph/">find his blogpost here&lt;/a>.&lt;/p>
&lt;h2 id="get-the-people-picker">Get the people-picker&lt;/h2>
&lt;p>You followed through but would just love to get this component? Don&amp;rsquo;t fret, I got you covered. Its the first component of an entire library (so this will be updated) and you get it &lt;a href="https://github.com/LuiseFreese/powerapps-samples/tree/main/samples/Microsoft-Graph-toolkit-people-picker">here on GitHub&lt;/a>&lt;/p>
&lt;p>Please follow the steps under &lt;a href="https://m365princess.com/blogs/microsoft-graph-people-picker-power-apps/#create-an-app">Create an app&lt;/a> so that you get the themes working in your app.&lt;/p>
&lt;h3 id="import-the-component">Import the component&lt;/h3>
&lt;ol>
&lt;li>Import the component library into your environment&lt;/li>
&lt;li>In your app, select the &lt;code>+&lt;/code> icon on the left bar&lt;/li>
&lt;li>Select &lt;code>get more components&lt;/code>&lt;/li>
&lt;li>Choose the &lt;strong>MGT components&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>cmp_MGT_pp&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Import&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="import component" src="https://m365princess.com/images/import-components.png">&lt;/p>
&lt;h3 id="add-the-component-to-your-app">Add the component to your app&lt;/h3>
&lt;ol>
&lt;li>Select the &lt;code>+&lt;/code> icon on the left bar&lt;/li>
&lt;li>Expand the section &lt;strong>Library components&lt;/strong>&lt;/li>
&lt;li>Select the &lt;strong>cmp_MGT_pp&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="insert component" src="https://m365princess.com/images/insert-component.png">&lt;/p>
&lt;p>This adds the component to the screen - but it looks ugly and full of bugs - let&amp;rsquo;s fix the colors&lt;/p>
&lt;ol>
&lt;li>Right-click on the component&lt;/li>
&lt;li>Select &lt;strong>Edit component&lt;/strong> - you will be prompted with a dialogue that you need to create a local copy -&lt;/li>
&lt;li>Select &lt;strong>Create a copy&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="edit component" src="https://m365princess.com/images/edit-componnet.png">&lt;/p>
&lt;ol>
&lt;li>Select &lt;strong>Components&lt;/strong> on the left menu&lt;/li>
&lt;li>Select your new component&lt;/li>
&lt;li>Switch the &lt;strong>Access app scope&lt;/strong> toggle to off and then to on again (yes, I know 🙄)&lt;/li>
&lt;li>Select &lt;strong>Screens&lt;/strong> on the left menu&lt;/li>
&lt;li>Select &lt;code>+&lt;/code> icon onm the left bar&lt;/li>
&lt;li>⚠️ Resist the urge to expand the &lt;strong>Library components&lt;/strong> section - remember? We created a copy, which sits right in our app 💡&lt;/li>
&lt;li>Expand &lt;strong>Custom&lt;/strong> - you will find your copy there&lt;/li>
&lt;li>Select it - it will be added to your screen&lt;/li>
&lt;li>Either expand/collapse the &lt;strong>galPeople&lt;/strong> by clicking on the Chevron or navigate from the screen and to it again so that the galPeople gets populated with the colPeople`&lt;/li>
&lt;/ol>
&lt;h2 id="feedback--whats-next">Feedback &amp;amp; What&amp;rsquo;s next&lt;/h2>
&lt;p>What do you think? Is this people-picker helpful? Which other Microsoft Graph toolkit component shall I rebuild next? &lt;a href="https://twitter.com/LuiseFreese/status/1600172653100138507">Let me on on twitter&lt;/a>&lt;/p></description></item><item><title>How to make a auto-height textinput component for Power Apps</title><link>https://m365princess.com/blogs/auto-height-textinput-component/</link><pubDate>Wed, 05 Oct 2022 16:53:24 +0000</pubDate><guid>https://m365princess.com/blogs/auto-height-textinput-component/</guid><description>&lt;p>Some controls in Power Apps do not have an auto height property, which means that we can&amp;rsquo;t get the &lt;strong>Height&lt;/strong> of a control to automagically ✨ adjust to its content. Especially for the textinput control, this is a real bummer, as we a user&amp;rsquo;s input a pretty much unpredictable. On the one hand, we do not want to waste precious screen estate by making the box as big as possible, on the other hand, a too small box will result in an endless scrollbar which is a bad user experience.&lt;/p>
&lt;p>&lt;img alt="gif of autoheight textinput" src="https://m365princess.com/images/textinputauto.gif">&lt;/p>
&lt;h2 id="build-a-component-with-me">Build a component with me&lt;/h2>
&lt;p>If you follow my blog posts then you might know that I love to componentize everything that I&amp;rsquo;d like to use again, and an auto-height textinput seems to tick that box as well.&lt;/p>
&lt;ol>
&lt;li>Open &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a>&lt;/li>
&lt;li>Create a new component &lt;code>cmp_textinput&lt;/code>&lt;/li>
&lt;li>Create a custom input property &lt;strong>outsideMargin&lt;/strong> (Number), &lt;code>20&lt;/code> - determines the margin around the component&lt;/li>
&lt;li>Create a custom output property &lt;strong>userText&lt;/strong> (Text), &lt;code>txt_userInput.Text&lt;/code> so that we can pass this value back to our app&lt;/li>
&lt;/ol>
&lt;h3 id="make-magic-work">Make magic work&lt;/h3>
&lt;ol>
&lt;li>Insert a text label &lt;code>lbl_autoHeightHelper&lt;/code> and a textinput &lt;code>txt_userInput&lt;/code>&lt;/li>
&lt;li>Make sure that the textinput sits on top&lt;/li>
&lt;li>Set the &lt;strong>Mode&lt;/strong> of the textinput to &lt;code>Multiline&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>X&lt;/strong> and &lt;strong>Y&lt;/strong> of the textinput to &lt;code>cmp_textinput.outsideMargin&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Width&lt;/strong> of the textinput to &lt;code>cmp_textinput.Width-2*cmp_textinput.outsideMargin&lt;/code> - this makes sure, that when you horizontally resize the component, this also applies to the textinput as well&lt;/li>
&lt;li>Set &lt;strong>Height&lt;/strong> of the textinput to &lt;code>Max(42, lbl_autoHeightHelper.Height)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Text&lt;/strong> of the text label to &lt;code>txt_userInput1.Text&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>X&lt;/strong> of the text label to &lt;code>txt_userInput1.X&lt;/code> and &lt;strong>Y&lt;/strong> to &lt;code>txt_userInput1.Y&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Width&lt;/strong> of the text label to &lt;code>txt_userInput1.Width&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Height&lt;/strong> of the text label to &lt;code>cmp_textinput.Height-2*cmp_textinput.outsideMargin&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>auto height&lt;/strong> of the text label to &lt;code>true&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Color&lt;/strong> of the text label to &lt;code>Transparent&lt;/code>&lt;/li>
&lt;li>For the text label, set &lt;strong>Font&lt;/strong> to &lt;code>txt_userinput.Font&lt;/code>, &lt;strong>FontWeight&lt;/strong> to &lt;code>txt_userinput.FontWeight&lt;/code>, and &lt;strong>Size&lt;/strong> to &lt;code>txt_userinput.Size&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Width&lt;/strong> of the textlabel to &lt;code>txt_userInput.Width&lt;/code> and the &lt;strong>Height&lt;/strong> to &lt;code>Max(42, lbl_autoHeightHelper.Height)&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="understand-the-magic">Understand the magic&lt;/h3>
&lt;ol>
&lt;li>We utilize the auto height property of the text label and hook it to the &lt;strong>Height&lt;/strong> property of the text input. The text label will get exactly the text in the Font, Size, FontWeight of the textinput, but as we set the &lt;strong>Color&lt;/strong> to &lt;code>Transparent&lt;/code>, it won&amp;rsquo;t show up.&lt;/li>
&lt;li>For the Width of the text input, we reference the width of the component itself so that we can make the textinput as wide as necessary by adjusting the size of the component instance in an app&lt;/li>
&lt;li>The &lt;code>Max(42, lbl_autoHeightHelper.Height)&lt;/code> for the &lt;strong>Height&lt;/strong> of the textinput ensures that we always have 42 as a minimum Height and the &lt;strong>Height&lt;/strong> of the text label (which has auto height enabled) as maximum Height&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="auto height textinput component" src="https://m365princess.com/images/textinputautoheight.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next&lt;/h2>
&lt;p>That&amp;rsquo;s it!&lt;/p>
&lt;p>a little hack to bring auto-height to controls that don&amp;rsquo;t come with it out of the box. &lt;a href="https://twitter.com/LuiseFreese/status/1578056178323570694">Let me know what you think on twitter&lt;/a>.&lt;/p>
&lt;p>Maybe you could spot in the screenshot above, that I added some more cool features to my component, such as a trailing icon (delete, error), a label, an asterisk. a helper and an error text, and an indicator of how many characters are still left. This is a sweet sneak preview on a Material design component library that I am building together with my dear friend and partner in crime &lt;a href="https://twitter.com/power_r2">Robin Rosengrün&lt;/a>.&lt;/p></description></item><item><title>Series: Build Power Apps that don't look like Power Apps - Material Design part 2</title><link>https://m365princess.com/blogs/powerapps-material-design-2/</link><pubDate>Sun, 04 Sep 2022 10:17:47 +0000</pubDate><guid>https://m365princess.com/blogs/powerapps-material-design-2/</guid><description>&lt;p>For real, your low code apps don&amp;rsquo;t need to look like as if design wasn&amp;rsquo;t important. I hear lots of people say, that they &lt;em>suck at design&lt;/em> or don&amp;rsquo;t have an eye for good UX. But a visually appealing UI is not the cherry on the cake but one of the core tasks of everyone who builds apps. Let me guide you step by step how you can improve your design skills - we will be again leveraging Google&amp;rsquo;s Material Design system. This is part 2 of How to build Power Apps - that don&amp;rsquo;t like Power Apps. If you didn&amp;rsquo;t read &lt;a href="https://www.m365princess.com/blogs/powerapps-material-design-1/">part 1&lt;/a> yet, this is your chance to catch up :-)&lt;/p>
&lt;p>Why do we use Material Design? It&amp;rsquo;s&lt;/p>
&lt;ul>
&lt;li>goodlooking&lt;/li>
&lt;li>well documented&lt;/li>
&lt;li>widely used&lt;/li>
&lt;/ul>
&lt;p>Also: If we can build Google/Android look-a-like apps with Microsoft Power Apps, we can build everything :-)&lt;/p>
&lt;p>This blog post focuses on how to create those beautiful Material Design &lt;a href="https://material.io/components/image-lists#usage">quilted image lists&lt;/a> in Power Apps galleries.&lt;/p>
&lt;p>&lt;img alt="Material Design gallery in Power Apps" src="https://m365princess.com/images/fab.png">&lt;/p>
&lt;p>This is the schema that we are aiming for: it consists of 4 types of rows which hold different amount of images in different widths and x positions.&lt;/p>
&lt;p>&lt;img alt="gallery schema" src="https://m365princess.com/images/schema_gallery.png">&lt;/p>
&lt;h2 id="create-a-component">Create a component&lt;/h2>
&lt;p>Let&amp;rsquo;s do this properly and create a canvas component &lt;code>cmp_MD_Gallery&lt;/code> with the following custom properties:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>singleImageWidth&lt;/strong>: &lt;code>200&lt;/code>&lt;/li>
&lt;li>&lt;strong>imagePadding&lt;/strong>: &lt;code>10&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Now let&amp;rsquo;s take care of how to feed our gallery. Create an additional custom property &lt;code>galleryContent&lt;/code> in the component of type &lt;strong>Table&lt;/strong>:&lt;/p>
&lt;pre tabindex="0">&lt;code>Table(
//row 1, 3 images
{
id: 1,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
{
id: 2,
image:&amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
{
id: 3,
image:&amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
// row 2, 2 images (1st 2/3, 2nd 1/3)
{
id: 4,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: 2 * (cmp_MD_Gallery.singleImageWidth) + cmp_MD_Gallery.imagePadding
},
{id: 5},
{
id: 5,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
//3rd row - 1 image
{
id: 6,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: 3 * (cmp_MD_Gallery.singleImageWidth) + 2 * ( cmp_MD_Gallery.imagePadding)
},
{id: 7},
{id: 8},
//4th row - 2 images (1st 1/3, 2nd 2/3)
{
id: 7,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
{
id: 8,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: 2 * (cmp_MD_Gallery.singleImageWidth) + cmp_MD_Gallery.imagePadding
},
{id: 9},
//5th row - 3 images
{
id: 9,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
{
id: 10,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
{
id: 11,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
//6th row -rest
{
id: 12,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: cmp_MD_Gallery.singleImageWidth
},
{
id: 13,
image: &amp;#34;&amp;lt;link to your image goes here&amp;gt;&amp;#34;,
width: 2 * (cmp_MD_Gallery.singleImageWidth) + cmp_MD_Gallery.imagePadding
}
)
&lt;/code>&lt;/pre>&lt;p>As you notice, we leave some items blank to make room for items that span the width of two items.&lt;/p>
&lt;h2 id="create-a-gallery">Create a gallery&lt;/h2>
&lt;ol>
&lt;li>Create a vertical gallery, set its &lt;strong>Width&lt;/strong> to &lt;code>Parent.Width&lt;/code>, its &lt;strong>Height&lt;/strong> to &lt;code>1000&lt;/code>, its &lt;strong>TemplateSize&lt;/strong> to &lt;code>300&lt;/code>&lt;/li>
&lt;li>Set the the &lt;strong>Items&lt;/strong> to &lt;code>cmp_MD_Gallery.galleryContent&lt;/code>&lt;/li>
&lt;li>Insert an image into the gallery, set its &lt;strong>Height&lt;/strong> to &lt;code>Parent.TemplateHeight&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Image&lt;/strong> property of the image to &lt;code>ThisItem.image&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Width&lt;/strong> property of the image to &lt;code>ThisItem.width&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="create-the-invisible-scrollbar">Create the invisible scrollbar&lt;/h2>
&lt;p>If you want to create an invisible scrollbar&lt;/p>
&lt;ol>
&lt;li>add a vertical slider on top of the gallery, set its &lt;strong>Max&lt;/strong> to &lt;code>0&lt;/code> and its &lt;strong>Min&lt;/strong> to &lt;code>-1000&lt;/code>, and its &lt;strong>Height&lt;/strong> to &lt;code>Gallery1.Height&lt;/code>&lt;/li>
&lt;li>Set &lt;em>all&lt;/em> its colors to &lt;code>Transparent&lt;/code> to make it disappear, but don&amp;rsquo;t set &lt;strong>Visible&lt;/strong> to &lt;code>false&lt;/code> (Users can&amp;rsquo;t interact with a control that has that setting)&lt;/li>
&lt;li>Set the &lt;strong>Y&lt;/strong> of the Image to &lt;code>Slider1.Value&lt;/code> - boom, done 🚀&lt;/li>
&lt;/ol>
&lt;p>You can set the &lt;strong>Width&lt;/strong> of the slider and the Size of the Handle to the Width of the gallery if users shall use the entire gallery width to scroll or you can limit this and give them a visual cue (a button, circle, etc) so that they know that they can scroll here.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next&lt;/h2>
&lt;p>As you can see, its relatively easy to create an engaging UX in Power Apps. When discussing this with the Math &amp;amp; SVG magician &lt;a href="https://twitter.com/power_r2">Robin Rosengrün&lt;/a>, he found an even more elegant way to calculate widths and x positions of each image, hope he records a video on that soon - #TeamWorkMakesTheDreamWork&lt;/p>
&lt;p>Would you try this gallery in your Power Apps? &lt;a href="https://twitter.com/LuiseFreese/status/1566693132128903170">Let me know what you think on twitter&lt;/a>.&lt;/p>
&lt;p>Next blog post in this series is how to create basic UI elements that you will love.&lt;/p></description></item><item><title>Series: Build Power Apps that don't look like Power Apps - Material Design part 1</title><link>https://m365princess.com/blogs/powerapps-material-design-1/</link><pubDate>Tue, 23 Aug 2022 12:17:47 +0000</pubDate><guid>https://m365princess.com/blogs/powerapps-material-design-1/</guid><description>&lt;p>One of my most important goals when developing Power Apps is good design. But for me,&lt;/p>
&lt;blockquote>
&lt;p>Design is not just pretty looks or some stunning effects, but it is how things &lt;em>work&lt;/em>.&lt;/p>
&lt;/blockquote>
&lt;p>This means that a well designed app makes people feel related, understood, comfortable and easily experienced. People will want to use the app again and again.&lt;/p>
&lt;p>Looking at the typical look of Power Apps, I don&amp;rsquo;t feel that these are well-designed in term of increasing usability, being visually appealing, or blending into the context they probably live in. This highly functional look shall convey the message, that people will get something working without putting too much effort into it. And that then leads to that perception, that low-code equals low standards in terms of design.&lt;/p>
&lt;p>This is the first part of a little series that illustrates, how we can develop Power Apps, that don&amp;rsquo;t look like Power Apps. I will use &lt;a href="https://material.io/design/introduction">Google&amp;rsquo;s Material design&lt;/a> system to showcase this. If you are looking into some Microsoft Fluent UI guidance - I blogged about &lt;a href="https://www.m365princess.com/blogs/2022-07-06-how-to-enhance-maker-experience-with-a-custom-theme-for-teams-apps-in-power-apps-studio/">good looking apps for Teams here&lt;/a>.&lt;/p>
&lt;h2 id="floating-action-button-fab">Floating Action Button (FAB)&lt;/h2>
&lt;p>The &lt;a href="https://material.io/components/buttons-floating-action-button#:~:text=A%20floating%20action%20button%20(FAB)%20performs%20the%20primary%2C%20or,regular%2C%20mini%2C%20and%20extended.">Floating Action Button (FAB)&lt;/a> is a design concept, that we can find in all kinds of mobile apps, for example Twitter mobile app, Outlook mobile app, and mny, many more. We say its floating, as it doesn&amp;rsquo;t sit in a dedicated navigation area, but &lt;em>floats&lt;/em> right on top of most probably scrolling content. Once that FAB is selected, it show some more related buttons that allows users to perform related actions.&lt;/p>
&lt;p>&lt;img alt="fab in action" src="https://m365princess.com/images/fabblog.gif">&lt;/p>
&lt;h2 id="how-to-build-a-fab-in-power-apps">How to build a FAB in Power Apps&lt;/h2>
&lt;p>To make this FAB as flexible and reusable as possible, we will create a canvas component &lt;code>cmp_MD_Fab&lt;/code>. Set its &lt;strong>Width&lt;/strong> to &lt;code>100&lt;/code> and its &lt;strong>Height&lt;/strong> to &lt;code>380&lt;/code>. Our Fab will consist of&lt;/p>
&lt;h3 id="overview">Overview&lt;/h3>
&lt;ol>
&lt;li>1 button &lt;code>btn_primary&lt;/code> with two icons &lt;code>icon_start&lt;/code> and &lt;code>icon_close&lt;/code>&lt;/li>
&lt;li>3 &lt;code>btn_secondary&lt;/code>, &lt;code>btn_tertiary&lt;/code>, &lt;code>btn_quarternary&lt;/code> with 3 corresponding icons &lt;code>icon_secondary&lt;/code>, &lt;code>icon_tertiary&lt;/code>, &lt;code>icon_quarternary&lt;/code>&lt;/li>
&lt;li>4 timers &lt;code>timer1&lt;/code>, &lt;code>timer2&lt;/code>, &lt;code>timer3&lt;/code>, &lt;code>timer4&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Add these to your component - we will style them in a few.&lt;/p>
&lt;h3 id="custom-properties">Custom properties&lt;/h3>
&lt;p>We will create the following custom properties&lt;/p>
&lt;ol>
&lt;li>&lt;strong>timerDuration&lt;/strong> (Number), defaults to &lt;code>300&lt;/code>, determines the &lt;strong>Duration&lt;/strong> of all timers&lt;/li>
&lt;li>&lt;strong>button1Fill&lt;/strong> (Color), defaults to &lt;code>Black&lt;/code>, determines &lt;strong>Fill&lt;/strong> of &lt;code>btn_primary&lt;/code>&lt;/li>
&lt;li>&lt;strong>button2Fill&lt;/strong> (Color), defaults to &lt;code>ColorValue(&amp;quot;#4F1FDC&amp;quot;)&lt;/code>, determines &lt;strong>Fill&lt;/strong> of &lt;code>btn_secondary&lt;/code>&lt;/li>
&lt;li>&lt;strong>button3Fill&lt;/strong> (Color), defaults to &lt;code>ColorValue(&amp;quot;#9964f4&amp;quot;)&lt;/code>, determines &lt;strong>Fill&lt;/strong> of &lt;code>btn_tertiary&lt;/code>&lt;/li>
&lt;li>&lt;strong>button4Fill&lt;/strong> (Color), defaults to &lt;code>ColorValue(&amp;quot;#BB8cF4&amp;quot;)&lt;/code>, determines &lt;strong>Fill&lt;/strong> of &lt;code>btn_quarternary&lt;/code>&lt;/li>
&lt;li>&lt;strong>iconStart&lt;/strong> (Image), defaults to &lt;code>Icon.Add&lt;/code>, determines the &lt;strong>Icon&lt;/strong> of &lt;code>icon_Start&lt;/code>&lt;/li>
&lt;li>&lt;strong>iconClose&lt;/strong> (Image), defaults to &lt;code>Icon.Cancel&lt;/code>, determines the &lt;strong>Icon&lt;/strong> of &lt;code>icon_Close&lt;/code>&lt;/li>
&lt;li>&lt;strong>icon2&lt;/strong> (Image), defaults to &lt;code>Icon.People&lt;/code>, determines the &lt;strong>Icon&lt;/strong> of &lt;code>icon_secondary&lt;/code>&lt;/li>
&lt;li>&lt;strong>icon3&lt;/strong> (Image), defaults to &lt;code>Icon.Bookmark&lt;/code>, determines the &lt;strong>Icon&lt;/strong> of &lt;code>icon_tertiary&lt;/code>&lt;/li>
&lt;li>&lt;strong>icon4&lt;/strong> (Image), defaults to &lt;code>Icon.Crop&lt;/code>, determines the &lt;strong>Icon&lt;/strong> of &lt;code>icon_quarternary&lt;/code>&lt;/li>
&lt;li>&lt;strong>iconColor&lt;/strong> (Color), defaults to &lt;code>White&lt;/code>, determines the &lt;strong>Color&lt;/strong> of all icons&lt;/li>
&lt;li>&lt;strong>button2OnSelect&lt;/strong> (Behavior, boolean), defaults to &lt;code>true&lt;/code>, determines the &lt;strong>OnSelect&lt;/strong> of &lt;code>btn_secondary&lt;/code>&lt;/li>
&lt;li>&lt;strong>button3OnSelect&lt;/strong> (Behavior, boolean), defaults to &lt;code>true&lt;/code>, determines the &lt;strong>OnSelect&lt;/strong> of &lt;code>btn_tertiary&lt;/code>&lt;/li>
&lt;li>&lt;strong>button4OnSelect&lt;/strong> (Behavior, boolean), defaults to &lt;code>true&lt;/code>, determines the &lt;strong>OnSelect&lt;/strong> of &lt;code>btn_quarternary&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="properties of the fab component" src="https://m365princess.com/images/md-fab-properties.png">&lt;/p>
&lt;p>and assign them as stated above. Example: Select all timers, select the &lt;strong>Duration&lt;/strong> property for them, set it to &lt;code>cmp_MD_FAB.timerDuration&lt;/code>. Proceed with all other custom properties like that.&lt;/p>
&lt;h3 id="the-buttons">The buttons&lt;/h3>
&lt;p>We will now take care of the buttons. Select all of them and set their&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Width&lt;/strong> to &lt;code>56&lt;/code>, &lt;strong>Height&lt;/strong> to &lt;code>Self.Width&lt;/code> and &lt;code>Radius&lt;/code> to &lt;code>Self.Width&lt;/code>. - We now have circle buttons!&lt;/li>
&lt;li>&lt;strong>BorderColor&lt;/strong>, &lt;strong>HoverBorderColor&lt;/strong>, &lt;strong>PressedBorderColor&lt;/strong>, &lt;strong>HoverFill&lt;/strong> to &lt;code>Self.Fill&lt;/code>&lt;/li>
&lt;li>&lt;strong>Text&lt;/strong> to &lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Now only select the &lt;code>btn_primary&lt;/code> and set its &lt;strong>X&lt;/strong> to &lt;code>(Parent.Width-Self.Width)/2&lt;/code> and its &lt;strong>Y&lt;/strong> to &lt;code>Parent.Height-Self.Height-10&lt;/code>. After that is done, select all &lt;em>other&lt;/em> buttons and set their &lt;strong>X&lt;/strong> to &lt;code>btn_primary.X&lt;/code>.&lt;/p>
&lt;h3 id="the-icons">The Icons&lt;/h3>
&lt;p>Set &lt;strong>Width&lt;/strong> of all icons to &lt;code>32&lt;/code> and &lt;strong>Height&lt;/strong> to &lt;code>Self.Width&lt;/code>
Set &lt;strong>X&lt;/strong> of all icons to &lt;code>btn_primary.X+ (btn_primary.Width-Self.Width)/2&lt;/code>
Set &lt;strong>Y&lt;/strong> of &lt;code>btn_secondary&lt;/code> to &lt;code>btn_secondary.Y+ (btn_secondary.Height-Self.Height)/2&lt;/code>
Set &lt;strong>Y&lt;/strong> of &lt;code>btn_tertiary&lt;/code> to &lt;code>btn_tertiary.Y+ (btn_tertiary.Height-Self.Height)/2&lt;/code>
Set &lt;strong>Y&lt;/strong> of &lt;code>btn_quarternary&lt;/code> to &lt;code>btn_quarternary.Y+ (btn_quarternary.Height-Self.Height)/2&lt;/code>&lt;/p>
&lt;p>Make sure that all controls sit in the correct order as they overlap:&lt;/p>
&lt;p>&lt;img alt="controls in the FAB component" src="https://m365princess.com/images/md-fab-controls.png">&lt;/p>
&lt;h3 id="the-timers">The Timers&lt;/h3>
&lt;p>Now we will take care of the logic.&lt;/p>
&lt;ol>
&lt;li>In the &lt;strong>OnSelect&lt;/strong> of the &lt;code>icon_Start&lt;/code> we want to extract all buttons and handle which icon appears : &lt;code>Set(start1, true); Set(start4, false); Set(isCloseVisible, true); Set(isStartVisible, false);&lt;/code>&lt;/li>
&lt;li>In the &lt;strong>OnSelect&lt;/strong> of &lt;code>icon_Close&lt;/code> we want to collapse all buttons and handle which icon appears: &lt;code>Set(isCloseVisible, false); Set(isStartVisible, true); Set(start1, false); Set(start2, false); Set(start3, false); Set(start4, true);&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Visible&lt;/strong> of &lt;code>icon_Close&lt;/code> to &lt;code>isCloseVisible&lt;/code>, the &lt;strong>Visible&lt;/strong> of &lt;code>icon_Start&lt;/code> to &lt;code>isStartVisible&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Start&lt;/strong> of &lt;code>timer1&lt;/code> to &lt;code>start1&lt;/code>, and the &lt;code>OnTimerEnd&lt;/code> of &lt;code>timer1&lt;/code> to &lt;code>Set(start2, true)&lt;/code> - which means that at the end of the first timer, we kick off the second timer.&lt;/li>
&lt;li>Set the &lt;strong>Start&lt;/strong> of &lt;code>timer2&lt;/code> to &lt;code>start2&lt;/code>, and the &lt;code>OnTimerEnd&lt;/code> of &lt;code>timer2&lt;/code> to &lt;code>Set(start3, true)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Start&lt;/strong> of &lt;code>timer3&lt;/code> to &lt;code>start3&lt;/code> - please note that at this point we don&amp;rsquo;t want to kick off another timer - all buttons are expanded and we only want to collapse them when our user selects the &lt;code>icon_Close&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>Let&amp;rsquo;s now hook the &lt;strong>Y&lt;/strong> property of &lt;code>btn_secondary&lt;/code>, &lt;code>btn_tertiary&lt;/code>, &lt;code>btn_quarternary&lt;/code> to the timers so that the buttons nicely float to their final position:&lt;/p>
&lt;ol>
&lt;li>Set the &lt;strong>Y&lt;/strong> of &lt;code>btn_secondary&lt;/code> to &lt;code>If(!start4, btn_primary.Y-100*(timer1.Value/timer1.Duration),btn_primary.Y)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Y&lt;/strong> of &lt;code>btn_tertiary&lt;/code> to &lt;code>If(!start4, btn_primary.Y-200*(timer2.Value/timer2.Duration), btn_primary.Y)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Y&lt;/strong> of &lt;code>btn_quarternary&lt;/code> to &lt;code>If(!start4,btn_primary.Y-300*(timer3.Value/timer3.Duration),btn_primary.Y)&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Add your component to a screen and don&amp;rsquo;t forget to now assign actions to the buttons :-)&lt;/p>
&lt;p>&lt;img alt="FAB in Power Apps" src="https://m365princess.com/images/fab.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>That&amp;rsquo;s it!&lt;/p>
&lt;p>a few buttons, icons and 4 timers are enough to create some advanced UI that you&amp;rsquo;d usually not find in a Power Apps. Next blog post is about how to create such beautiful photo galleries, that don&amp;rsquo;t look like default Power Apps experience. Let me know what you think on twitter.&lt;/p>
&lt;p>Next blog post in this series is about how you can create this cool looking gallery with with various image sizes.&lt;/p></description></item><item><title>Get started with planning your Power Apps components properly</title><link>https://m365princess.com/blogs/plan-components/</link><pubDate>Wed, 17 Aug 2022 09:27:55 +0000</pubDate><guid>https://m365princess.com/blogs/plan-components/</guid><description>&lt;p>Power Apps components are awesome, still I do not see too many organization using them, which is why I want to give some guidance on how to better plan and build components. Proper planning can save hours or even days of development time and lead to a better result, so it could be worthwhile to dig into how we plan components:&lt;/p>
&lt;p>When building components for we usually build something in an app, then realize that this could/should be a component and then rework this as a component. This comes with a lot of going back and forth and is neither the most efficient way nor the most satisfying one.&lt;/p>
&lt;p>Also undocumented (and no, some inline comment don&amp;rsquo;t make proper docs!) components won&amp;rsquo;t serve the components goal of being reusable so that they reduce time and effort that goes into building UI, but will furthermore increase technical debt.&lt;/p>
&lt;p>To improve both developer experience and user satisfaction, here is some guideline to follow along.&lt;/p>
&lt;p>💡 Note: I will talk about the person who is making components and component libraries for other makers to use as a &lt;strong>maker enabler&lt;/strong> and about the person using ready to be used components in their apps as &lt;strong>makers&lt;/strong>, while people who will use apps will be called &lt;strong>users&lt;/strong>.&lt;/p>
&lt;h2 id="start-with-the-docs-what-is-this">Start with the docs: What is this?&lt;/h2>
&lt;ol>
&lt;li>Give your component a meaningful name&lt;/li>
&lt;/ol>
&lt;p>If you plan on doing an entire component library or if you see potential to share more than one component with co-workers, its a good idea to stick to a namig convention. This makes it easier for makers to find the right component when building apps.&lt;/p>
&lt;ol start="2">
&lt;li>Describe what your component should do&lt;/li>
&lt;/ol>
&lt;p>Give some thoughts to the purpose of the component. Shall it display information, gather data from a user, facilitate how users interact with UI?&lt;/p>
&lt;ol start="3">
&lt;li>Describe the kind of app it is intended to be used for&lt;/li>
&lt;/ol>
&lt;p>For which kind of apps is this component intended to be used for, what is its context? Does it run standalone, in SharePoint, in Teams? On which devices will it be used?&lt;/p>
&lt;ol start="4">
&lt;li>Describe use cases this component could serve&lt;/li>
&lt;/ol>
&lt;p>A generic popup component could serve to display information, a warning or an error message. It can be used to let users confirm something, give them a guided tour through an app, a process or more, could gather their input and so much more. The more time you spend on exploring what your component could do, the more likely it is, that you build a component that is flexible enough to adjust to maker&amp;rsquo;s needs.&lt;/p>
&lt;p>Take your time to write this down, if you plan on creating more than 1 component but rather a component library, make a habit to start with the docs and make these accessible and discoverable to every maker who could use these components. Trust me, if you commit to work in the open and document your work, you will likely also take more care, as you would need to write down messy workarounds as well.&lt;/p>
&lt;h2 id="design-considerations">Design considerations&lt;/h2>
&lt;p>We will use a popup component as an example, but the process will apply to all kinds of components.&lt;/p>
&lt;p>&lt;img alt="popup component" src="https://m365princess.com/images/popup.png">&lt;/p>
&lt;h3 id="visual-design---how-things-look">Visual design - how things look&lt;/h3>
&lt;p>To make a component as reusable and adjustable as possible, its a good idea as a maker enabler to let makers adjust colors and shapes but give them already something that works without needing to change anything.&lt;/p>
&lt;h4 id="colors">Colors&lt;/h4>
&lt;p>A good practice is to layout a color palette for your component or your component library and then stick to this.&lt;/p>
&lt;p>If you need a little help with which colors go well together: I use &lt;a href="https://coolors.co">coolors.co&lt;/a> palette generator to find those matching colors. To harmonize the look you can use the &lt;a href="https://uxplanet.org/5-simple-tips-on-using-color-in-your-design-40916d0dfa63">60-30-10 rule&lt;/a>, which states that about 60% should be a primary color (dominant), 30% should be a secondary color (supporting), and 10% should be an accent color.&lt;/p>
&lt;p>&lt;img alt="colorpalette" src="https://m365princess.com/images/colorpalette.png">&lt;/p>
&lt;p>Define a palette with these 3 colors, but keep accessibility and color contrast in mind. Also think about hover or pressed events.&lt;/p>
&lt;h4 id="shapes--sizes">Shapes &amp;amp; Sizes&lt;/h4>
&lt;p>Now think about the shapes and sizes in your components, for example rounded corners, border thickness, width and height of your component. Write these considerations down. Also think about media such as images, icons and more to be consistent.&lt;/p>
&lt;h4 id="look-and-feel-of-your-component---input-properties">Look and feel of your component - input properties&lt;/h4>
&lt;p>Now that you already defined a color palette and got an idea how your component and its parts could look and feel, let&amp;rsquo;s make this happen.&lt;/p>
&lt;ol>
&lt;li>Define input properties for the colors you want to use: &lt;code>primaryColor&lt;/code>, &lt;code>secondaryColor&lt;/code>, &lt;code>accentColor&lt;/code>&lt;/li>
&lt;li>Assign your color palette values to them as default values.&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="custom properties" src="https://m365princess.com/images/cmp_props.png">&lt;/p>
&lt;ol start="3">
&lt;li>Create also input properties for radius of buttons, standard width or height of controls you use, and more. The goal is to avoid hard coded values as you don&amp;rsquo;t want to go over the entire component over and over just because someone requests &amp;lsquo;just a tiny change&amp;rsquo; 🙃&lt;/li>
&lt;li>Write everything down. Seriously.&lt;/li>
&lt;/ol>
&lt;h3 id="functional-design---how-things-work">Functional design - how things work&lt;/h3>
&lt;p>After working on UI, let&amp;rsquo;s work on UX and consider how the app works. First think about the logic within the component, then think about how the component interacts with the apps it shall run in.&lt;/p>
&lt;h4 id="behavior-properties---make-your-component-interact-with-the-app">Behavior properties - make your component interact with the app&lt;/h4>
&lt;p>For this, we can fire custom events with behavior properties - for example we can close a popup-component by&lt;/p>
&lt;ol>
&lt;li>Add a behavior property &lt;code>onClose&lt;/code> (boolean) to our component&lt;/li>
&lt;li>Add an &lt;code>X&lt;/code> icon to the component&lt;/li>
&lt;li>Assign the property to the &lt;strong>OnSelect&lt;/strong> of the &lt;code>X&lt;/code> icon&lt;/li>
&lt;li>In your component instance, set the &lt;strong>OnSelect&lt;/strong> property of your component to &lt;code>Set(isCmpVisible, false)&lt;/code>&lt;/li>
&lt;li>In your component instance, set the &lt;strong>Visible&lt;/strong> property of your component to &lt;code>iscmpVisible&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>As a result, the component will automagically 🦄 disappear, once a user selects that &lt;code>X&lt;/code> icon.&lt;/p>
&lt;p>💡 Note: If you like behavior properties, you need to first turn on the respecting setting:&lt;/p>
&lt;p>&lt;img alt="settings" src="https://m365princess.com/images/settings.png">&lt;/p>
&lt;h4 id="content---display-useful-information-with-more-input-properties">Content - display useful information with more input properties&lt;/h4>
&lt;p>As we will also want to give makers good options to customize the content being displayed in the component:&lt;/p>
&lt;p>Define input properties for every text, number, table, or image being displayed, such as&lt;/p>
&lt;ol>
&lt;li>mainButtonContent&lt;/li>
&lt;li>labelHeaderContent&lt;/li>
&lt;li>labelBodyContent&lt;/li>
&lt;/ol>
&lt;p>so that makers can easily adjust these values.&lt;/p>
&lt;h4 id="return-values-from-component-to-app---output-properties">Return values from component to app - Output properties&lt;/h4>
&lt;p>If users interact with our components and we want to know &lt;em>what&lt;/em> they did, we will need to return values from our component to the app. We do this with output properties.&lt;/p>
&lt;p>For example, if our component contains a TextInput control and we want to know the &lt;strong>Text&lt;/strong> property of that in our app, we will need to create an output property, hook that to &lt;code>TextInput.Text&lt;/code> and then use the Output property in the app to return that value.&lt;/p>
&lt;h2 id="build-the-component">Build the component&lt;/h2>
&lt;p>Now that you defined all the properties to take care of&lt;/p>
&lt;ol>
&lt;li>look and feel of your component&lt;/li>
&lt;li>content to be displayed&lt;/li>
&lt;li>inner and outer logic in app context&lt;/li>
&lt;/ol>
&lt;p>its time to build the component.&lt;/p>
&lt;ol>
&lt;li>Define the components size, preferably relative to the App&amp;rsquo;s &lt;strong>Width&lt;/strong> and &lt;strong>Height&lt;/strong>&lt;/li>
&lt;li>Add the controls you need to your component, define &lt;strong>X&lt;/strong>, &lt;strong>Y&lt;/strong>, &lt;strong>Width&lt;/strong> and &lt;strong>Height&lt;/strong> relative to the component, don&amp;rsquo;t use hard coded values&lt;/li>
&lt;li>Assign the custom input properties for look and feel: colors and shapes&lt;/li>
&lt;li>Assign the custom input properties for displaying content: texts, tables, images&lt;/li>
&lt;li>Assign the behavior properties to the controls that shall call that property like a function&lt;/li>
&lt;li>Assign the output properties to the controls that shall return a value to the app&lt;/li>
&lt;/ol>
&lt;h2 id="use-the-component">Use the component&lt;/h2>
&lt;p>Once you finished the component, add it to your app and try it out.&lt;/p>
&lt;ol>
&lt;li>Make sure that you assign a custom event to each behavior property&lt;/li>
&lt;li>Understand the output properties you already defined&lt;/li>
&lt;li>Document this :-)&lt;/li>
&lt;/ol>
&lt;p>As a last hint: This is how proper docs for a component could look like:&lt;/p>
&lt;p>&lt;img alt="proper docs for a component" src="https://m365princess.com/images/docs.png">&lt;/p>
&lt;h2 id="result">Result&lt;/h2>
&lt;p>As a result, you can establish this as your standard process to build components, which will also reduce your development time and increase component quality. You will also take advantage of proper documentation, which will make sure that you are not the only one who needs to take care if the component needs an adjustment or if users need a similar component. What do you think? &lt;a href="https://twitter.com/LuiseFreese/status/1559948044078030853">Let me know on twitter&lt;/a> :-)&lt;/p></description></item><item><title>Build a progress button with me (yes it is a component)</title><link>https://m365princess.com/blogs/build-progress-button/</link><pubDate>Mon, 08 Aug 2022 11:39:48 +0000</pubDate><guid>https://m365princess.com/blogs/build-progress-button/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>You know that feeling when right after hitting that send button you still want to edit something or changed your mind? This progress button allows people to rethink again and change their minds.&lt;/p>
&lt;h2 id="why-would-we-need-a-progress-button">Why would we need a progress button?&lt;/h2>
&lt;p>My proofreading skills improve by at least 200% &lt;em>after&lt;/em> I hit a &lt;strong>send&lt;/strong> button. If you feel your users could use a little &lt;em>think again&lt;/em> option, this progress button is for you.&lt;/p>
&lt;p>Desired goal: Once a user selects the &lt;strong>Send request&lt;/strong> button, the button shall fill in a different color, linear progressing from left to right and display a text like &lt;code>sending... click to cancel&lt;/code>. If the user selects that button now again to cancel the action, we want to reset that Fill and Text to the original state.&lt;/p>
&lt;p>&lt;img alt="progress button" src="https://m365princess.com/images/progressbutton.gif">&lt;/p>
&lt;h2 id="build-the-component">Build the component&lt;/h2>
&lt;p>As we want to make this as reusable as possible, we will invest some time to create it as a component.&lt;/p>
&lt;h3 id="set-the-custom-properties">Set the custom properties&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>backgroundFill&lt;/strong> (Color) : &lt;code>ColorValue(&amp;quot;#1e6091&amp;quot;)&lt;/code>&lt;/li>
&lt;li>&lt;strong>progressFill&lt;/strong> (Color): &lt;code>ColorValue(&amp;quot;#168aad&amp;quot;)&lt;/code>&lt;/li>
&lt;li>&lt;strong>textColor&lt;/strong> (Color): &lt;code>White&lt;/code>&lt;/li>
&lt;li>&lt;strong>progressbuttonHeight&lt;/strong> (Number): &lt;code>40&lt;/code>&lt;/li>
&lt;li>&lt;strong>progressbuttonWidth&lt;/strong> (Number): &lt;code>200&lt;/code>&lt;/li>
&lt;li>&lt;strong>labelContent&lt;/strong> (Table):&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Table(
{
id: 1,
text: &amp;#34;send request&amp;#34;
},
{
id: 2,
text: &amp;#34;sending... click to cancel&amp;#34;
},
{
id: 3,
text: &amp;#34;sent&amp;#34;
}
)
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Now set ths &lt;strong>OnReset&lt;/strong> property of the component to &lt;code>Set(cmp_labelTrack,1);Set(cmp_timerRun,false)&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="controls">Controls&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>Add&lt;/p>
&lt;ul>
&lt;li>3 buttons &lt;code>btn_background&lt;/code>, &lt;code>btn_progress&lt;/code> and &lt;code>btn_overlay&lt;/code>&lt;/li>
&lt;li>1 labels &lt;code>lbl_Content&lt;/code>&lt;/li>
&lt;li>1 timer &lt;code>Timer1&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>to the component&lt;/p>
&lt;ul>
&lt;li>Set all of these to the same &lt;strong>Width&lt;/strong>: &lt;code>cmp_progressButton.progressButtonWidth&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>cmp_progressButton.progressButtonHeight&lt;/code>, &lt;strong>X&lt;/strong>: &lt;code>5&lt;/code> and &lt;strong>Y&lt;/strong>: &lt;code>5&lt;/code>, so that they overlap. Order them so that the overlay button is on top, the &lt;code>lbl_Content&lt;/code> is on top of &lt;code>btn_progress&lt;/code> and the &lt;code>btn_progress&lt;/code> is on top of the &lt;code>btn_background&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="button progress controls" src="https://m365princess.com/images/progressbutton-controls.png">&lt;/p>
&lt;ul>
&lt;li>We will want to set the initial state of our label in the &lt;strong>OnVisible&lt;/strong> of the screen &lt;code>Reset(cmp_progressButton_3)&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="overlay-button">Overlay button&lt;/h3>
&lt;p>This button is the one that contains all the magic:&lt;/p>
&lt;ul>
&lt;li>Set its &lt;strong>OnSelect&lt;/strong> to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Set(cmp_timerRun,!cmp_timerRun);Set(cmp_labelTrack,2);
&lt;/code>&lt;/pre>&lt;p>as we want to start/stop a timer and control which row of our content we want to display in the text label.&lt;/p>
&lt;ul>
&lt;li>Set all Colors of this button to &lt;code>Transparent&lt;/code> - we want it to visually disappear. Don&amp;rsquo;t set the &lt;strong>Visible&lt;/strong> property to &lt;code>false&lt;/code> - users can&amp;rsquo;t interact then with it.&lt;/li>
&lt;li>Set the &lt;strong>Text&lt;/strong> property to &lt;code>&amp;quot;&amp;quot;&lt;/code> - we don&amp;rsquo;t need any text in here.&lt;/li>
&lt;/ul>
&lt;h3 id="text-label">Text label&lt;/h3>
&lt;ul>
&lt;li>Set the &lt;strong>Text&lt;/strong> property of the label to &lt;code>LookUp(cmp_progressButton.labelContent,id=cmp_labelTrack, text)&lt;/code> - This makes sure &lt;strong>OnVisible&lt;/strong> of the screen (&lt;code>cmp_labelTrack&lt;/code> = 1), we display &lt;code>send request&lt;/code>, while once our user selected the button (&lt;code>cmp_labelTrack&lt;/code> = 2) we display &lt;code>sending... click to cancel&lt;/code>. We will later adjust the timer so that the last text &lt;code>sent!&lt;/code> is being displayed &lt;strong>OnTimerEnd&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h3 id="background-button">Background button&lt;/h3>
&lt;p>This is the button that looks as if it was the one people click on - but as we have an overlap with the overlay button, it shall just look pretty - I liked it plain and simple and went for &lt;strong>Radius&lt;/strong>: &lt;code>0&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>Set its &lt;strong>DisplayMode&lt;/strong> to &lt;code>View&lt;/code> - users won&amp;rsquo;t interact with it.&lt;/li>
&lt;li>Set its &lt;strong>Text&lt;/strong> to &lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Fill&lt;/strong> to a &lt;code>cmp_progressButton.backgroundFill&lt;/code>, make the &lt;strong>BorderColor&lt;/strong> &lt;code>Transparent&lt;/code> and let &lt;strong>HoverBorderColor&lt;/strong>, &lt;strong>PressedColorBorder&lt;/strong> = &lt;code>Self.Fill&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="progress-button">progress button&lt;/h3>
&lt;p>This is the button that fill up from left to right once user selects the overlay button&lt;/p>
&lt;ul>
&lt;li>Set its &lt;strong>Fill&lt;/strong> property to &lt;code>cmp_progressButton.progressFill&lt;/code>&lt;/li>
&lt;li>Make the &lt;strong>BorderColor&lt;/strong> &lt;code>Transparent&lt;/code> and let &lt;strong>HoverBorderColor&lt;/strong>, &lt;strong>PressedColorBorder&lt;/strong> = &lt;code>Self.Fill&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Text&lt;/strong> to &lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Width&lt;/strong> to &lt;code>cmp_progressButton.progressButtonWidth*(Timer.Value/Timer.Duration)&lt;/code> - this means that at any given point the Width of the button will be determined by the progression of the running timer&lt;/li>
&lt;li>Set &lt;strong>HoverFill&lt;/strong> to &lt;code>btn_background.Fill&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="timer">Timer&lt;/h3>
&lt;p>It&amp;rsquo;s time to take care of the Timer control&lt;/p>
&lt;ul>
&lt;li>&lt;strong>AutoStart&lt;/strong>: &lt;code>cmp_timerRun&lt;/code>&lt;/li>
&lt;li>&lt;strong>OnTimerEnd&lt;/strong>: &lt;code>Set(cmp_labelTrack, 3);Set(cmp_timerRun, false)&lt;/code> - as mentioned above - we now display the third text in the label&lt;/li>
&lt;li>&lt;strong>Reset&lt;/strong> &lt;code>!cmp_timerRun&lt;/code>&lt;/li>
&lt;li>&lt;strong>Duration&lt;/strong>: &lt;code>2000&lt;/code> (milliseconds)&lt;/li>
&lt;/ul>
&lt;h2 id="one-more-thing">one more thing&amp;hellip;&lt;/h2>
&lt;p>To make this component fully adjustable, now add two more custom properties to it:&lt;/p>
&lt;ul>
&lt;li>sizeWidth** (Number): &lt;code>btn_background.Width+10&lt;/code>&lt;/li>
&lt;li>sizeHeight** (Number): &lt;code>btn_background.Height+10&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Adjust &lt;strong>X&lt;/strong> and &lt;strong>Y&lt;/strong> of the &lt;code>btn_background&lt;/code> to &lt;code>5&lt;/code>.&lt;/p>
&lt;p>That&amp;rsquo;s it!&lt;/p>
&lt;p>&lt;img alt="progress button" src="https://m365princess.com/images/progressbutton.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>Would you like to include such a button? Please &lt;a href="https://twitter.com/LuiseFreese/status/1557277300458151937">let me know on twitter&lt;/a>.&lt;/p></description></item><item><title>Can you make it pop?</title><link>https://m365princess.com/blogs/pop/</link><pubDate>Sat, 06 Aug 2022 15:10:52 +0000</pubDate><guid>https://m365princess.com/blogs/pop/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Want to improve your app design and your galleries? Learn how to make your galleries pop out and get this very dynamic look and feel!&lt;/p>
&lt;p>There are a few things you can do to make your galleries stand out and to create this rich and visually appealing look.&lt;/p>
&lt;p>&lt;img alt="pop out gallery gif" src="https://m365princess.com/images/popgallery.gif">&lt;/p>
&lt;h2 id="some-basics">some basics&lt;/h2>
&lt;h3 id="transition">Transition&lt;/h3>
&lt;p>I only recently stumbled upon a property in Power Apps galleries: &lt;strong>Transition&lt;/strong>. It has a default value of &lt;code>None&lt;/code>, but can be set to &lt;code>Pop&lt;/code> or to &lt;code>Push&lt;/code>, which give the illusion that on hover of an item the selected item either pops out or is pushed in. This already looks good on regular text labels, but will be taken to next level if we add some sugar&amp;amp;spice aka buttons and shadows.&lt;/p>
&lt;h3 id="use-buttons-instead-of-text-labels">Use buttons instead of text labels&lt;/h3>
&lt;p>Choosing buttons over text labels has some serious advantages:&lt;/p>
&lt;ol>
&lt;li>they can have rounded corners, which means they are very flexible to style&lt;/li>
&lt;li>they have properties for &lt;strong>HoverFill&lt;/strong>, &lt;strong>PressedFill&lt;/strong> etc.&lt;/li>
&lt;/ol>
&lt;p>💡 Please always set the &lt;strong>DisplayMode&lt;/strong> property of buttons you don&amp;rsquo;t want users to interact with to &lt;code>View&lt;/code>, this is important for accessibility reasons.&lt;/p>
&lt;p>If you still prefer text labels, you can add them on top of a button to benefit from the flexible design options.&lt;/p>
&lt;p>&lt;img alt="button shapes" src="https://m365princess.com/images/button-shapes.png">&lt;/p>
&lt;p>Using buttons, you can easily create this card look.&lt;/p>
&lt;h2 id="elevate-your-design">Elevate your design&lt;/h2>
&lt;p>Add a little bit of drop shadow to your cards with an HTMLtext control. I use something like this to create a subtle shadow:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">&amp;#34;&amp;lt;div style=&amp;#39;
margin: 20px;
width: 219px;
height: 380px;
box-shadow: -6px 6px 14px rgba(25, 25, 25, .2);
border-radius: 15px
&amp;#39;&amp;gt;
&amp;lt;/div&amp;gt;&amp;#34;
&lt;/code>&lt;/pre>&lt;h2 id="shapes-and-sizes">Shapes and sizes&lt;/h2>
&lt;p>Play around a bit with shapes and sizes of images- you can also add blobs to your gallery to make it look more dynamic. In my example, I generated a few blobs with &lt;a href="https://www.blobmaker.app/">blobmaker.app&lt;/a>, uploaded them to the app and referred to them in the table that feeds my gallery:&lt;/p>
&lt;pre tabindex="0">&lt;code>Table(
{
id: 1,
blob: blob2,
fill: ColorValue(&amp;#34;#ffffff&amp;#34;),
hoverfill:ColorValue(&amp;#34;#f5f5f5&amp;#34;),
title: &amp;#34;ocean&amp;#34;,
image: &amp;#39;image1&amp;#39;,
textcolor:Black,
body: &amp;#34;The ocean (also the sea or the world ocean) is the body of salt water that covers approximately 70.8%...&amp;#34;
},
{
id: 2,
blob: blob3,
fill:ColorValue(&amp;#34;#FAF0CA&amp;#34;),
hoverfill:ColorValue(&amp;#34;#efd157&amp;#34;),
title: &amp;#34;desert&amp;#34;,
image: &amp;#39;image2&amp;#39;,
body: &amp;#34;A desert is a barren area of landscape where little precipitation occurs and, consequently... &amp;#34;,
textcolor:Black
},
...
...
{
id: 7,
blob: blob3,
fill:ColorValue(&amp;#34;#718F94&amp;#34;),
hoverfill:ColorValue(&amp;#34;#2C383A&amp;#34;),
title: &amp;#34;city&amp;#34;,
image: &amp;#39;image7&amp;#39;,
body: &amp;#34;A city is a large human settlement. It can be defined as a permanent and densely settled...&amp;#34;,
textcolor: White
}
)
&lt;/code>&lt;/pre>&lt;h2 id="make-your-gallery-scrollable---without-a-scrollbar">Make your gallery scrollable - without a scrollbar&lt;/h2>
&lt;p>As already covered in &lt;a href="https://www.m365princess.com/blogs/build-curved-gallery-power-apps/">How to build a curved gallery in Power Apps&lt;/a> and in &lt;a href="https://www.m365princess.com/blogs/create-scrollbar-galleries-power-apps/">How to create your own scrollbar for galleries in Power Apps&lt;/a> we don&amp;rsquo;t need to use the built-in gallery scrollbar, but can either scroll without any control by swiping or we can create the illusion of having a scrollbar in the design of your choice.&lt;/p>
&lt;p>This time I again used a slider control and related the &lt;strong>X&lt;/strong> property of the button in my gallery to its value. All other &lt;strong>X&lt;/strong> properties of the other controls in the gallery depend on that button.&lt;/p>
&lt;p>I use a plane svg ✈ and relate its position as well to the slider value. As we can use CSS in svg code, we can rotate the svg conditionally to the value of the slider as well:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">&amp;#34;data:image/svg+xml;utf8, &amp;#34; &amp;amp; EncodeUrl(
&amp;#34;&amp;lt;svg xmlns=&amp;#39;http://www.w3.org/2000/svg&amp;#39; width=&amp;#39;16&amp;#39; height=&amp;#39;16&amp;#39; fill=&amp;#39;rgb(231,221,251,0.9)&amp;#39; class=&amp;#39;bi bi-airplane-engines-fill&amp;#39; viewBox=&amp;#39;0 0 16 16&amp;#39; &amp;#34;&amp;amp; If(Slider1.Value &amp;gt; (Slider1.Max/2), &amp;#34;transform=&amp;#39;rotate(90)&amp;#39;&amp;#34;, &amp;#34;transform=&amp;#39;rotate(-90)&amp;#39;&amp;#34;)&amp;amp;&amp;#34;&amp;gt;
&amp;lt;path d=&amp;#39;...&amp;#39;/&amp;gt;
&amp;lt;/svg&amp;gt; &amp;#34;
)
&lt;/code>&lt;/pre>&lt;h2 id="add-an-additional-visual-layer-on-your-screen">Add an additional visual layer on your screen&lt;/h2>
&lt;h3 id="background">Background&lt;/h3>
&lt;p>Don&amp;rsquo;t use a plain White or plain Black background fillcolor. Both colors are very harsh on the eyes and don&amp;rsquo;t leave a lot of room for color harmony.&lt;/p>
&lt;h3 id="use-some-waves">Use some waves&lt;/h3>
&lt;p>Love waves? Samesies. I use the wave generator at &lt;a href="https://getwaves.io/">getwaves.io&lt;/a>, save as svg and upload to my app. 💡 - &lt;strong>ImagePosition&lt;/strong>: &lt;code>Fit&lt;/code> is your friend.&lt;/p>
&lt;p>If you now have the waves in the background and slightly overlap your gallery with them, you create an additional layer, so that the screen doesn&amp;rsquo;t look flat, but more 3D and dynamic.&lt;/p>
&lt;h3 id="create-pop-ups">Create Pop-Ups&lt;/h3>
&lt;p>&lt;img alt="Popup in canvas apps" src="https://m365princess.com/images/popgallery-modal.png">&lt;/p>
&lt;p>Sometimes, a modal window is enough and you don&amp;rsquo;t need to build an entire new screen. If you want to create different pop-ups depending on which item in your gallery was selected (now again the button comes in handy), you can achieve that by:&lt;/p>
&lt;ol>
&lt;li>Have a button/icon in the gallery&lt;/li>
&lt;li>Set its &lt;strong>OnSelect&lt;/strong> property to something like &lt;code>If(ThisItem.IsSelected, Set(isShowPopup, true);Set(gbl_PopupContent, ThisItem.id), &amp;quot;&amp;quot;)&lt;/code> to first determine if a popup shall be displayed and then which content shall be shown.&lt;/li>
&lt;li>Create a text label of the size of your screen, fill it in a grey-ish color and set transparency to a value that you like.&lt;/li>
&lt;li>On top of that blur text label, create a button/text label in a bright color (you can reuse the color of the button) and display the content you like on top of that. For this, set the &lt;strong>Fill&lt;/strong> property of that background button to &lt;code>gal_pop.Selected.fill&lt;/code> and the &lt;strong>Image&lt;/strong> property of the image/content that you want to show to &lt;code>gal_pop.Selected.image&lt;/code>&lt;/li>
&lt;li>For some more depth, create a shadow with &lt;strong>HTMLText&lt;/strong> again&lt;/li>
&lt;li>Don&amp;rsquo;t forget to have a close/cancel icon as well. Set it&amp;rsquo;s &lt;strong>OnSelect&lt;/strong> to &lt;code>Set(isShowPopup, false)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Visible&lt;/strong> property of the blur text label, your background button, your content, the htmlText and the cancel icon to &lt;code>isShowPopup&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>There you go! You created a modern design, that doesn&amp;rsquo;t look like the typical canvas app.&lt;/p>
&lt;p>&lt;img alt="pop out gallery" src="https://m365princess.com/images/popgallery.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>What are your design hacks? How do you make your apps stand out? I am curious! &lt;a href="https://twitter.com/LuiseFreese/status/1556919208956526593">Please let me know on twitter*&lt;/a>.&lt;/p></description></item><item><title>How to create your own scrollbar for galleries in Power Apps</title><link>https://m365princess.com/blogs/create-scrollbar-galleries-power-apps/</link><pubDate>Fri, 05 Aug 2022 20:12:36 +0000</pubDate><guid>https://m365princess.com/blogs/create-scrollbar-galleries-power-apps/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Did you know that two buttons and a slider make a sweet scrollbar? Let me show you how to do it!&lt;/p>
&lt;p>&lt;img alt="curved gallery" src="https://m365princess.com/images/scrollbarandgallery.gif">&lt;/p>
&lt;p>In my last blog post about &lt;a href="https://www.m365princess.com/blogs/build-curved-gallery-power-apps/">How to build a curved gallery in Power Apps&lt;/a> I already showed how you can use a slider to scroll through a horizontal gallery, and we hid that slider. This post shall show you how you can create your own scrollbar to navigate a vertical gallery.&lt;/p>
&lt;p>To make this as flexible and reuasable as possible, we will componentize this.&lt;/p>
&lt;h2 id="create-a-component">create a component&lt;/h2>
&lt;ol>
&lt;li>Create a new component &lt;code>cmp_scrollGallery&lt;/code> and add the following custom properties to it:&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>galleryStyles&lt;/strong> (Record): &lt;code>{X:0,Y:0,Width:cmp_scrollGallery.Width,Height:cmp_scrollGallery.Height}&lt;/code>&lt;/p>
&lt;p>&lt;strong>galleryContent&lt;/strong> (Table):&lt;/p>
&lt;p>&lt;code>Table({id:1,image:'image1',title:&amp;quot;Lake Tahoe&amp;quot;,description:&amp;quot;d1&amp;quot;},{id:2,image:'image2',title:&amp;quot;Trees&amp;quot;,description:&amp;quot;d2&amp;quot;},{id:3,image:'image3',title:&amp;quot;Waterfall&amp;quot;,description:&amp;quot;d3&amp;quot;},{id:4,image:'image4',title:&amp;quot;Lake Louise&amp;quot;,description:&amp;quot;d4&amp;quot;},{id:5,image:'image5',title:&amp;quot;Santa Barbara&amp;quot;,description:&amp;quot;d5&amp;quot;},{id:6,image:'image6',title:&amp;quot;Ocean&amp;quot;,description:&amp;quot;d6&amp;quot;},{id:7,image:'image7',title:&amp;quot;Beaver Dam&amp;quot;,description:&amp;quot;d7&amp;quot;},{id:8,image:'image8',title:&amp;quot;Green&amp;quot;,description:&amp;quot;d8.&amp;quot;})&lt;/code>&lt;/p>
&lt;p>&lt;strong>sizeWidth&lt;/strong> (Number): &lt;code>App.DesignWidth&lt;/code>&lt;/p>
&lt;p>&lt;strong>sizeHeight&lt;/strong> (Number): &lt;code>App.DesignHeight&lt;/code>&lt;/p>
&lt;p>&lt;strong>sliderStyles&lt;/strong> (Record): &lt;code>{X:cmp_scrollGallery.Width,Y:0,Width:50,Height:cmp_scrollGallery.Height}&lt;/code>&lt;/p>
&lt;p>&lt;strong>backgroundBarColor&lt;/strong> (Color): &lt;code>ColorValue(&amp;quot;#168aad&amp;quot;)&lt;/code>&lt;/p>
&lt;p>&lt;strong>scrollBarColor&lt;/strong> (Color): &lt;code>ColorValue(&amp;quot;#1e6091&amp;quot;)&lt;/code>&lt;/p>
&lt;p>&lt;strong>scrollbarStyles&lt;/strong> (Record): &lt;code>{X:cmp_scrollGallery.galleryStyles.Width-20,Y:0,Width:20,Height:cmp_scrollGallery.Height}&lt;/code>&lt;/p>
&lt;p>&lt;strong>titleStyles&lt;/strong> (Record): &lt;code>{Font: Font.'Open Sans', FontSize: 16, FontWeight: Bold, Color: White, Width: cmp_scrollGallery.galleryStyles.Width*0.5, Height:40, X: cmp_scrollGallery.imageStyles.X+cmp_scrollGallery.imageStyles.Width +40 }&lt;/code> |&lt;/p>
&lt;p>&lt;strong>imageStyles&lt;/strong> (Record): &lt;code>{Width:128, Height: 128, BorderTopLeft: 0, BorderTopRight: Self.Width, BorderBottomLeft: 0, BorderBottomRight:0,X: 16}&lt;/code>&lt;/p>
&lt;p>&lt;strong>bodyStyles&lt;/strong> (Record):&lt;code>{Font: Font.'Open Sans', FontSize: 12, FontWeight: Lighter, Color: White, Width: cmp_scrollGallery.galleryStyles.Width*0.7, Height:40, X: cmp_scrollGallery.imageStyles.X+cmp_scrollGallery.imageStyles.Width +40 }&lt;/code>&lt;/p>
&lt;ol start="2">
&lt;li>Set the &lt;strong>Width&lt;/strong> of the component to &lt;code>cmp_scrollGallery.sizeWidth&lt;/code> and the &lt;strong>Height&lt;/strong> to &lt;code>cmp_scrollGallery.sizeHeight&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="gallery">gallery&lt;/h2>
&lt;p>We will now add a gallery and refer to our custom properties.&lt;/p>
&lt;ol>
&lt;li>Upload a few images&lt;/li>
&lt;li>Add a horizontal gallery &lt;code>gal_1&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>ShowScrollbar&lt;/strong> property to &lt;code>false&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>items&lt;/strong> property to &lt;code>cmp_scrollGallery.galleryContent&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>cmp_scrollGallery.galleryStyles.X&lt;/code>, &lt;strong>Y&lt;/strong>: &lt;code>cmp_scrollGallery.galleryStyles.Y&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>cmp_scrollGallery.galleryStyles.Height&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>cmp_scrollGallery.galleryStyles.Width&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="image-in-the-gallery">image in the gallery&lt;/h3>
&lt;ol>
&lt;li>Add an image &lt;code>img&lt;/code> to the gallery, set its &lt;strong>Image&lt;/strong> property to &lt;code>ThisItem.image&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>16&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>cmp_scrollGallery.imageStyles.Width&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>cmp_scrollGallery.imageStyles.Height&lt;/code>&lt;/li>
&lt;li>Add a button to the gallery, (I liked it to be semi transparent) and set its &lt;strong>X&lt;/strong> to &lt;code>img.X&lt;/code> and its &lt;strong>Width&lt;/strong>*&lt;code>to&lt;/code>img.Width`&lt;/li>
&lt;li>&lt;strong>RadiusTopRight&lt;/strong>: &lt;code>cmp_scrollGallery.imageStyles.BorderTopRight&lt;/code>, all other &lt;strong>Radius&amp;hellip;&lt;/strong> properties shall be &lt;code>0&lt;/code> - this creates this quarter circle effect&lt;/li>
&lt;li>&lt;strong>TemplatePadding&lt;/strong>: &lt;code>20&lt;/code>, &lt;strong>TemplateSize&lt;/strong>: &lt;code>140&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="title-text-label-in-the-gallery">title text label in the gallery&lt;/h3>
&lt;ol>
&lt;li>Add a text label &lt;code>lbl_title&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: cmp_scrollGallery.TitleStyles.X, &lt;strong>Y&lt;/strong>: &lt;code>img.Y&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>cmp_scrollGallery.TitleStyles.Width&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>40&lt;/code>&lt;/li>
&lt;li>&lt;strong>Color&lt;/strong>: &lt;code>cmp_scrollGallery.TitleStyles.Color&lt;/code>, &lt;strong>Font&lt;/strong>: &lt;code>cmp_scrollGallery.TitleStyles.Font&lt;/code>, &lt;strong>FontWeight&lt;/strong>: &lt;code>cmp_scrollGallery.TitleStyles.FontWeight&lt;/code>, &lt;strong>Size&lt;/strong>: &lt;code>cmp_scrollGallery.TitleStyles.FontSize&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Text&lt;/strong> property to &lt;code>ThisItem.title&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="description-text-label-in-the-gallery">description text label in the gallery&lt;/h3>
&lt;ol>
&lt;li>Add a text label &lt;code>lbl_description&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: cmp_scrollGallery.bodyStyles.X, &lt;strong>Y&lt;/strong>: &lt;code>img.Y&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>cmp_scrollGallery.bodyStyles.Width&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>40&lt;/code>&lt;/li>
&lt;li>&lt;strong>Color&lt;/strong>: &lt;code>cmp_scrollGallery.bodyStyles.Color&lt;/code>, &lt;strong>Font&lt;/strong>: &lt;code>cmp_scrollGallery.bodyStyles.Font&lt;/code>, &lt;strong>FontWeight&lt;/strong>: &lt;code>cmp_scrollGallery.bodyStyles.FontWeight&lt;/code>, &lt;strong>Size&lt;/strong>: &lt;code>cmp_scrollGallery.bodyStyles.FontSize&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Text&lt;/strong> property to &lt;code>ThisItem.description&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Now that we have the gallery, lets make it scrollable. We will add a slider control and hook this the &lt;strong>Y&lt;/strong> property of the image. 💡 The &lt;strong>Y&lt;/strong> property of all other controls in the gallery depends on &lt;strong>img.Y&lt;/strong>&lt;/p>
&lt;h2 id="slider">slider&lt;/h2>
&lt;ol>
&lt;li>Add a vertical slider &lt;code>sli_gal&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>cmp_scrollGallery.sliderStyles.X-Self.Width&lt;/code>, &lt;strong>Y&lt;/strong>: &lt;code>cmp_scrollGallery.sliderStyles.Y&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>cmp_scrollGallery.sliderStyles.Height&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>cmp_scrollGallery.sliderStyles.Width&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Min&lt;/strong> to &lt;code>gal_1.TemplatePadding+btn_ScrollBar.Height/2&lt;/code>, its &lt;strong>Max&lt;/strong> to &lt;code>gal_1.Height+gal_1.TemplatePadding-btn_ScrollBar.Height/2&lt;/code> and its &lt;strong>Default&lt;/strong> to &lt;code>50&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>HandleSize&lt;/strong> to &lt;code>50&lt;/code>&lt;/li>
&lt;li>Now set &lt;em>all&lt;/em> color values to &lt;code>Transparent&lt;/code> - we want to make the slider disappear. Don&amp;rsquo;t set the &lt;strong>visible&lt;/strong> property to &lt;code>false&lt;/code> - users can&amp;rsquo;t interact then with the control anymore&lt;/li>
&lt;/ol>
&lt;p>Now the most important step: Go back to &lt;strong>img.Y&lt;/strong> and set it to &lt;code>sli_gal.Value-gal_1.Height+gal_1.TemplatePadding&lt;/code>. You can now move the &lt;em>hidden&lt;/em> handle and see that the items in the gallery in fact scroll :-)&lt;/p>
&lt;p>Now we want to add the scroll bar:&lt;/p>
&lt;h2 id="2-buttons">2 buttons&lt;/h2>
&lt;p>Yes, for real. Our scrollbar only consists of two buttons, one for the background bar and one for the actual scroll bar&lt;/p>
&lt;h3 id="background-bar">background bar&lt;/h3>
&lt;ol>
&lt;li>Add a button &lt;code>btn_backgroundBar&lt;/code>, set its &lt;strong>DisplayMode&lt;/strong> to &lt;code>View&lt;/code>, we don&amp;rsquo;t want users to interact with it.&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>cmp_scrollGallery.scrollbarStyles.X&lt;/code>, &lt;strong>Y&lt;/strong>: 0, &lt;strong>Height&lt;/strong>: &lt;code>cmp_scrollGallery.Height&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>20&lt;/code>&lt;/li>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>cmp_scrollGallery.backgroundBarColor&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="scroll-bar">scroll bar&lt;/h2>
&lt;ol>
&lt;li>Add a button &lt;code>btn_scrollBar&lt;/code>, set its &lt;strong>DisplayMode&lt;/strong> to &lt;code>View&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>cmp_scrollGallery.scrollbarStyles.X&lt;/code>, &lt;strong>Y&lt;/strong>: &lt;code>sli_gal.Height-sli_gal.Value+Self.Height/2-gal_1.TemplatePadding&lt;/code>, &lt;strong>Height&lt;/strong>: &lt;code>40&lt;/code>, &lt;strong>Width&lt;/strong>: &lt;code>20&lt;/code>&lt;/li>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>cmp_scrollGallery.scrollBarColor&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>As a last step, rearrange the controls so that the slider is on top&lt;/p>
&lt;p>&lt;img alt="scrollbar and gallery" src="https://m365princess.com/images/scrollbarandgallery.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>Who would have thought that its that easy to create your own custom scrollbar? Adjust sizes and colors as you please, and &lt;a href="https://twitter.com/LuiseFreese/status/1556544157224681472">let me know on twitter how you like it&lt;/a>!&lt;/p></description></item><item><title>How to build a curved gallery in Power Apps</title><link>https://m365princess.com/blogs/build-curved-gallery-power-apps/</link><pubDate>Fri, 05 Aug 2022 12:32:53 +0000</pubDate><guid>https://m365princess.com/blogs/build-curved-gallery-power-apps/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Galleries in Power Apps do not have to look boring. With a little creativity we can create a curve effect.&lt;/p>
&lt;p>&lt;img alt="curved gallery" src="https://m365princess.com/images/curvedgallery.gif">&lt;/p>
&lt;h2 id="gallery">gallery&lt;/h2>
&lt;ol>
&lt;li>Upload a few images&lt;/li>
&lt;li>Add a horizontal gallery &lt;code>gal&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>ShowScrollbar&lt;/strong> property to &lt;code>false&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>items&lt;/strong> property to&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Table(
{
id: 1,
image: &amp;#39;image1&amp;#39;,
title: &amp;#34;image1&amp;#34;
},
{
id: 2,
image: &amp;#39;image2&amp;#39;,
title: &amp;#34;image2&amp;#34;
},
{
id: 3,
image: &amp;#39;image3&amp;#39;,
title: &amp;#34;image3&amp;#34;
},
{
id: 4,
image: &amp;#39;image4&amp;#39;,
title: &amp;#34;image4&amp;#34;
},
{
id: 5,
image: &amp;#39;image5&amp;#39;,
title: &amp;#34;image5&amp;#34;
},
{
id: 6,
image: &amp;#39;image6&amp;#39;,
title: &amp;#34;image6&amp;#34;
},
{
id: 7,
image: &amp;#39;image7&amp;#39;,
title: &amp;#34;image7&amp;#34;
},
{
id: 8,
image: &amp;#39;image8&amp;#39;,
title: &amp;#34;title8&amp;#34;
}
)
&lt;/code>&lt;/pre>&lt;ol start="3">
&lt;li>Add an image &lt;code>img&lt;/code> to the gallery, set its &lt;strong>Image&lt;/strong> property to &lt;code>ThisItem.image&lt;/code>&lt;/li>
&lt;li>Add a button to the gallery, (I liked it to be semi transparent) and set its &lt;strong>X&lt;/strong> to &lt;code>img.X&lt;/code> and its &lt;strong>Width&lt;/strong>* to &lt;code>img.Width&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Text&lt;/strong> property to &lt;code>ThisItem.title&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="ovals">ovals&lt;/h2>
&lt;ol>
&lt;li>Add two ovals to your screen, set their &lt;strong>Width&lt;/strong> to &lt;code>gal.Width&lt;/code>, set their &lt;strong>Y&lt;/strong> property that the ovals slightly overlap with the gallery (depending on how intense you want the curve effect to look like)&lt;/li>
&lt;li>Set the &lt;strong>Fill&lt;/strong> property to &lt;code>Screen1.Fill&lt;/code> and their &lt;strong>BorderColor&lt;/strong> to &lt;code>Transparent&lt;/code> - voila, they seem to be invisible&lt;/li>
&lt;/ol>
&lt;h2 id="slider">slider&lt;/h2>
&lt;p>As we don&amp;rsquo;t show a scrollbar (&lt;a href="https://www.m365princess.com/blogs/create-scrollbar-galleries-power-apps/">I find the built-in scrollbar ugly&lt;/a>), we will add a slider with which we can scroll through our gallery&lt;/p>
&lt;ol>
&lt;li>Add a horizontal slider&lt;/li>
&lt;li>Place it on top of the gallery, matching its size&lt;/li>
&lt;li>Set the &lt;strong>HandleSize&lt;/strong> to &lt;code>gal.TemplateHeight&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Min&lt;/strong> to &lt;code>(gal.TemplateWidth*CountRows(gal.AllItems)-gal.Width-gal.TemplateWidth)*-1&lt;/code>, its &lt;strong>Max&lt;/strong> to &lt;code>gal.Width-gal.TemplateWidth&lt;/code>, and its &lt;strong>Default&lt;/strong> to &lt;code>Self.Max&lt;/code>&lt;/li>
&lt;li>Now set &lt;em>all&lt;/em> color values to &lt;code>Transparent&lt;/code> - we want to make the slider disappear. Don&amp;rsquo;t set the &lt;strong>visible&lt;/strong> property to &lt;code>false&lt;/code> - users can&amp;rsquo;t interact then with the control anymore&lt;/li>
&lt;/ol>
&lt;p>One last thing: Set the &lt;strong>X&lt;/strong> property of the image in the gallery to &lt;code>slider.Value&lt;/code>&lt;/p>
&lt;p>That&amp;rsquo;s it!&lt;/p>
&lt;p>&lt;img alt="curved gallery" src="https://m365princess.com/images/curvedgallery.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>I&amp;rsquo;d like to know what you would like to display in such a curved gallery? Preview of documents? Images of assets? Also: did you know that you could use a slider to scroll through a gallery? &lt;a href="https://twitter.com/LuiseFreese/status/1555548745709883393">Let me know on twitter&lt;/a>!&lt;/p></description></item><item><title>How to build a swipe-right component in Power Apps</title><link>https://m365princess.com/blogs/build-swipe-component-power-apps/</link><pubDate>Thu, 04 Aug 2022 10:15:31 +0000</pubDate><guid>https://m365princess.com/blogs/build-swipe-component-power-apps/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Your canvas apps do not have to look ugly. This blog post guides you how to build a swipe-right component that you can reuse across apps.&lt;/p>
&lt;h2 id="what-we-are-going-to-build">What we are going to build&lt;/h2>
&lt;p>&lt;img alt="swipe right components" src="https://m365princess.com/images/swiperight.gif">&lt;/p>
&lt;h2 id="create-the-component-with-custom-properties">Create the Component with custom properties&lt;/h2>
&lt;ol>
&lt;li>Create a new component &lt;code>cmp_SwipeRight&lt;/code> and add the following custom properties&lt;/li>
&lt;/ol>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">property&lt;/th>
&lt;th style="text-align: left">type&lt;/th>
&lt;th style="text-align: left">default&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>swipeHeight&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Number&lt;/td>
&lt;td style="text-align: left">&lt;code>55&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>swipeWidth&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Number&lt;/td>
&lt;td style="text-align: left">&lt;code>250&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>primaryColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>ColorValue(&amp;quot;#c0c0c0&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>secondaryColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>RGBA(116,116,116,1)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>accentColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>RosyBrown&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>textColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>White&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>backgroundText1&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Text&lt;/td>
&lt;td style="text-align: left">&lt;code>&amp;quot;swipe to approve&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>backgroundText2&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Text&lt;/td>
&lt;td style="text-align: left">&lt;code>&amp;quot;thank you&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>chevronColor1&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>White&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>chevronColor2&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>LightGray&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>chevronColor3&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>DarkGray&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>icon&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Image&lt;/td>
&lt;td style="text-align: left">&lt;code>Icon.Heart&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Onchange&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Behavior(Text)&lt;/td>
&lt;td style="text-align: left">(needs number parameter called &lt;code>valueslider&lt;/code>)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ol start="2">
&lt;li>Set the &lt;strong>Width&lt;/strong> property of the component to &lt;code>cmp_SwipeRight.swipeWidth+10&lt;/code> and the &lt;strong>Height&lt;/strong> property to &lt;code>cmp_SwipeRight.swipeHeight+20&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="background-button">background button&lt;/h3>
&lt;ol>
&lt;li>Add a button &lt;code>btn_background_1&lt;/code>, set its &lt;strong>DisplayMode&lt;/strong> property to &lt;code>View&lt;/code> - We don&amp;rsquo;t want people to interact with this button. The only reason this is not a text label is that we can&amp;rsquo;t do rounded corners with a text label 🙄&lt;/li>
&lt;li>Set the &lt;strong>Fill&lt;/strong> property to &lt;code>cmp_SwipeRight.primaryColor&lt;/code>, the &lt;strong>BorderColor&lt;/strong> property to &lt;code>Self.Fill&lt;/code> and the &lt;strong>Color&lt;/strong> property to &lt;code>cmp_SwipeRight.textColor&lt;/code>&lt;/li>
&lt;li>Position the button to &lt;strong>X&lt;/strong> = &lt;code>(Parent.Width-Self.Width)/2&lt;/code> and &lt;strong>Y&lt;/strong> = &lt;code>(Parent.Height-Self.Height)/2&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Width&lt;/strong> property to &lt;code>Parent.Width-10&lt;/code> and the &lt;strong>Height&lt;/strong> property to &lt;code>Parent.Height-10&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Border radius&lt;/strong> to &lt;code>10&lt;/code> (it&amp;rsquo;s the default value)&lt;/li>
&lt;li>Set the &lt;strong>Align&lt;/strong> property to &lt;code>Align.Center&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Text&lt;/strong> property to &lt;code>If(sli_swipeRight_1.Value&amp;lt;100,cmp_SwipeRight.backgroundText1,cmp_SwipeRight.backGroundText2)&lt;/code> - don&amp;rsquo;t worry, this will give an error as we are referring to a slider control that doesn&amp;rsquo;t exist yet - we will fix this in the next step.&lt;/li>
&lt;/ol>
&lt;h3 id="slider">slider&lt;/h3>
&lt;p>You guessed it - we need a slider control.&lt;/p>
&lt;ol>
&lt;li>Add a horizontal slider control, set its &lt;strong>Min&lt;/strong> to &lt;code>0&lt;/code> and its &lt;strong>Max&lt;/strong> to &lt;code>100&lt;/code>, &lt;strong>Default&lt;/strong> is &lt;code>0&lt;/code> as well&lt;/li>
&lt;li>Set its &lt;strong>HandleSize&lt;/strong> to &lt;strong>200&lt;/strong>&lt;/li>
&lt;li>Set &lt;strong>X&lt;/strong> to &lt;code>btn_background_1.X+5&lt;/code> and &lt;strong>Y&lt;/strong> to &lt;code>0&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Width&lt;/strong> to &lt;code>cmp_SwipeRight.swipeWidth-btn_swipe_1.Width-12&lt;/code> and &lt;strong>Height&lt;/strong> to &lt;code>Parent.Height&lt;/code>&lt;/li>
&lt;li>Set &lt;em>all&lt;/em> color values to &lt;code>Transparent&lt;/code> - this control should be invisible to users - still resist that urge to set the &lt;strong>Visible&lt;/strong> property to &lt;code>false&lt;/code> - users can&amp;rsquo;t interact with the control anymore if you do that&lt;/li>
&lt;li>Set the &lt;strong>OnChange&lt;/strong> property to &lt;code>If(Self.Value&amp;lt;100, Set(isActionSuccess, false), Set(isActionSuccess, true)); cmp_SwipeRight.Onchange(Self.Value)&lt;/code> - We determine if a user has (completely) swiped right and output this into a variable.&lt;/li>
&lt;/ol>
&lt;h3 id="swipe-button">swipe button&lt;/h3>
&lt;p>As we don&amp;rsquo;t want users to see the slider handle (you can&amp;rsquo;t change its shape), we need something else so that they know that they swiped :-)&lt;/p>
&lt;ol>
&lt;li>Add a button &lt;code>btn_swipe_1&lt;/code> and again set its &lt;strong>DisplayMode&lt;/strong> property to &lt;code>View&lt;/code> - this is just to be pretty, not to have users interact with it directly&lt;/li>
&lt;li>Set &lt;strong>X&lt;/strong> to &lt;code>sli_swipeRight_1.X+ sli_swipeRight_1.Width/100*sli_swipeRight_1.Value+1&lt;/code> and &lt;strong>Y&lt;/strong> to &lt;code>(Parent.Height-Self.Height)/2&lt;/code> - which means that our swipe button moves alongside with the (hidden) handle of the slider&lt;/li>
&lt;li>Set &lt;strong>Height&lt;/strong> to &lt;code>btn_background_1.Height-20&lt;/code> and &lt;strong>Width&lt;/strong> to &lt;code>Self.Height&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Fill&lt;/strong> to &lt;code>cmp_SwipeRight.secondaryColor&lt;/code> and &lt;strong>BorderColor&lt;/strong> to &lt;code>Self.Fill&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Text property&lt;/strong> to &lt;code>&amp;quot;&amp;quot;&lt;/code> - we don&amp;rsquo;t need any text in here - the entire magic is done in a gallery&lt;/li>
&lt;/ol>
&lt;h3 id="gallery">gallery&lt;/h3>
&lt;p>Speaking of a gallery - we need this to show those nice chevrons!&lt;/p>
&lt;ol>
&lt;li>Create a horizontal gallery &lt;code>gal_chevrons&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>items&lt;/strong> property to&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Table(
{
id: 1,
icon: Icon.ChevronRight,
color: cmp_SwipeRight.chevronColor1
},
{
id: 2,
icon: Icon.ChevronRight,
color: cmp_SwipeRight.chevronColor2
},
{
id: 3,
icon: Icon.ChevronRight,
color: cmp_SwipeRight.chevronColor3
}
)
&lt;/code>&lt;/pre>&lt;ol start="3">
&lt;li>Set &lt;strong>X&lt;/strong> to &lt;code>sli_swipeRight_1.X+ sli_swipeRight_1.Width/100*sli_swipeRight_1.Value+1&lt;/code> and &lt;strong>Y&lt;/strong> to &lt;code>btn_swipe_1.Y&lt;/code> - which means that our gallery will &lt;em>stick&lt;/em> to our swipe button when it moves&lt;/li>
&lt;li>Set its &lt;strong>Width&lt;/strong> to &lt;code>btn_swipe_1.Width&lt;/code> and its &lt;strong>Height&lt;/strong> to &lt;code>btn_swipe_1.Height&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>TemplatePadding&lt;/strong> to &lt;code>1&lt;/code> and &lt;strong>TemplateSize&lt;/strong> to &lt;code>12&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Visible&lt;/strong> to &lt;code>!isActionSuccess&lt;/code> - remember, this is set when we move the slider :-) As a result, the gallery wil disappear once we swiped&lt;/li>
&lt;li>Add an icon &lt;code>icon_chevron&lt;/code> to that gallery&lt;/li>
&lt;li>Set the &lt;strong>Icon&lt;/strong> property of the icon to &lt;code>ThisItem.icon&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>Height&lt;/strong> to &lt;code>Parent.Height&lt;/code> and its &lt;strong>Width&lt;/strong> to &lt;code>15&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Color&lt;/strong> property to &lt;code>ThisItem.color&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>This empty swipe button should now indicate, that the swipe was a success:&lt;/p>
&lt;h3 id="icon">icon&lt;/h3>
&lt;ol>
&lt;li>Add an icon to your component&lt;/li>
&lt;li>Set &lt;strong>X&lt;/strong> to &lt;code>btn_swipe_1.X+ (btn_swipe_1.Width-Self.Width)/2&lt;/code> and &lt;strong>Y&lt;/strong> to &lt;code>btn_swipe_1.Y+(btn_swipe_1.Height-Self.Height)/2&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Width&lt;/strong> and &lt;strong>Height&lt;/strong> to &lt;code>16&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Color&lt;/strong> to &lt;code>cmp_SwipeRight.accentColor&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>icon&lt;/strong> property of the icon to &lt;code>cmp_SwipeRight.icon&lt;/code>&lt;/li>
&lt;li>Set &lt;strong>Visible&lt;/strong> to &lt;code>isActionSuccess&lt;/code> - as a result once the gallery disappeared, the single icon shows up&lt;/li>
&lt;/ol>
&lt;p>WE now want to add some more 3D effect to our component and have a nice shadow on the swipe button.&lt;/p>
&lt;h3 id="htmltext">HTMLtext&lt;/h3>
&lt;ol>
&lt;li>Add an HTMLText control &lt;code>html_shadowSwipe_1&lt;/code> to the component&lt;/li>
&lt;li>Set &lt;strong>X&lt;/strong> to &lt;code>sli_swipeRight_1.X+ sli_swipeRight_1.Width/100*sli_swipeRight_1.Value-10&lt;/code> and &lt;strong>Y&lt;/strong> to &lt;code>4&lt;/code> - this way it moves along with the swipe button and the gallery&lt;/li>
&lt;li>Set &lt;strong>Width&lt;/strong> to &lt;code>btn_swipe_1.Width+25&lt;/code> and &lt;strong>Height&lt;/strong> to &lt;code>btn_swipe_1.Height+25&lt;/code>&lt;/li>
&lt;li>Set its &lt;strong>HTMLText&lt;/strong> property to &lt;code>&amp;quot;&amp;lt;div style='margin:5px;width:&amp;quot;&amp;amp;btn_swipe_1.Width&amp;amp;&amp;quot;px;height:&amp;quot;&amp;amp;btn_swipe_1.Height&amp;amp;&amp;quot;px;background-color:#;box-shadow:3px 6px 6px 1px rgba(0,0,0,0.14); border-radius:&amp;quot;&amp;amp;btn_swipe_1.RadiusBottomLeft&amp;amp;&amp;quot;px'&amp;gt;&amp;lt;/div&amp;gt;&amp;quot;&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="bring-all-controls-into-the-right-order">Bring all controls into the right order&lt;/h2>
&lt;p>Make sure that all controls sit like this:&lt;/p>
&lt;p>&lt;img alt="swipeRight component controls" src="https://m365princess.com/images/swipe-controls.png">&lt;/p>
&lt;h2 id="add-the-component-to-your-app">Add the component to your app&lt;/h2>
&lt;p>Now let&amp;rsquo;s add the component to our app and play around with the appearance - as we set a ton of custom properties for colors, sizes, and icon, we can easily adjust the look of this component. You can try out for example to change the colors of the chevrons depending on the Fill color of your swipe button or different icons to determine success and of course different texts.&lt;/p>
&lt;p>Still one thing is missing - we need to know when our user swiped, right? To do so, set the (custom) &lt;strong>Onchange&lt;/strong> property of the component to &lt;code>UpdateContext({loc_isSuccessValue: valueslider})&lt;/code> - This way we set a local variable that returns a &lt;code>100&lt;/code> if the slider was swiped completely.&lt;/p>
&lt;h2 id="feedback--whats-next">Feedback &amp;amp; what&amp;rsquo;s next?&lt;/h2>
&lt;p>What do you think? Would you like to use such a swipe component in your mobile apps? Let me know on twitter!&lt;/p></description></item><item><title>How to build a split button component for Power Apps</title><link>https://m365princess.com/blogs/build-split-button-component-power-apps/</link><pubDate>Wed, 03 Aug 2022 17:18:53 +0000</pubDate><guid>https://m365princess.com/blogs/build-split-button-component-power-apps/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Less controls mean less user confusion and better performance - This blog post guides you through the creation of a simple yet effective split button component.&lt;/p>
&lt;p>&lt;img alt="splitbutton walkthrough" src="https://m365princess.com/images/splitbutton.gif">&lt;/p>
&lt;h2 id="lets-create-a-component">Let&amp;rsquo;s create a component&lt;/h2>
&lt;ol>
&lt;li>Create a new canvas component &lt;code>cmp_SplitButton&lt;/code> and add the following custom properties to it
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">property&lt;/th>
&lt;th style="text-align: left">type&lt;/th>
&lt;th style="text-align: left">default&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>splitButtonHeight&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Number&lt;/td>
&lt;td style="text-align: left">&lt;code>40&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>splitButtonWidth&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Number&lt;/td>
&lt;td style="text-align: left">&lt;code>196&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>primaryColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>ColorValue(&amp;quot;#1e6091&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>secondaryColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>ColorValue(&amp;quot;#168aad&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>textColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Color&lt;/td>
&lt;td style="text-align: left">&lt;code>White&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>buttonText&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Text&lt;/td>
&lt;td style="text-align: left">&lt;code>&amp;quot;open&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Onchange&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Behavior(Text)&lt;/td>
&lt;td style="text-align: left">(needs boolean parameter called &lt;code>option&lt;/code>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Onselect&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Behavior(Boolean)&lt;/td>
&lt;td style="text-align: left">&lt;code>true&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="custom properties" src="https://m365princess.com/images/splitbutton-cp.png">&lt;/p>
&lt;ol start="2">
&lt;li>Add a button &lt;code>btn_main&lt;/code> to the component&lt;/li>
&lt;li>Set its &lt;strong>OnSelect&lt;/strong> property to &lt;code>cmp_SplitButton.Onselect()&lt;/code> - this will make sure that when we later call that function we will return a &lt;code>true&lt;/code> so that we can determine in our app if that button was selected.&lt;/li>
&lt;li>Now let&amp;rsquo;s refer to our custom properties:
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">property&lt;/th>
&lt;th style="text-align: left">value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>BorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>Self.Fill&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Color&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.textColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Fill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.primaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Height&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.splitButtonHeight&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverBorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>Self.Color&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>PressedBorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>PressedColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>Self.Color&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>PressedFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Radius&lt;/strong>&lt;/td>
&lt;td style="text-align: left">0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Width&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.splitButtonWidth-36&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/li>
&lt;li>Add a dropdown control &lt;code>drp_options&lt;/code> to the component and refer as follows to our custom properties:&lt;/li>
&lt;/ol>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">property&lt;/th>
&lt;th style="text-align: left">value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>BorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.primaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>ChevronBackground&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.primaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>ChevronFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.textColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>ChevronHoverBackground&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>ChevronHoverFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.textColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverBorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Color&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Fill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>White&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Height&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.splitButtonHeight+2&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverBorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.textColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HoverFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>PressedBorderColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>PressedColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.textColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>PressedFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>SelectionColor&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.textColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>SelectionFill&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.secondaryColor&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Width&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;code>cmp_SplitButton.splitButtonWidth+2&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I know, that is a tedious task, but trust me, the result looks good.&lt;/p>
&lt;ol start="6">
&lt;li>Set the &lt;strong>Items&lt;/strong> property to any array that you like - I used &lt;code>[&amp;quot;open in SharePoint&amp;quot;, &amp;quot;open in Teams&amp;quot;, &amp;quot;send as an email&amp;quot;]&lt;/code>&lt;/li>
&lt;li>Now let&amp;rsquo;s take of functionality of the dropdown - set the &lt;strong>OnChange&lt;/strong> property to &lt;code>cmp_SplitButton.Onchange(drp_options.SelectedText.Value)&lt;/code>.&lt;/li>
&lt;/ol>
&lt;h2 id="add-functionality-to-your-component">Add Functionality to your component&lt;/h2>
&lt;p>Depending on your use case, you will want to at least&lt;/p>
&lt;ol>
&lt;li>determine, if the button has been selected (to then perform other actions)&lt;/li>
&lt;li>determine, which value has been selected in the dropdown.&lt;/li>
&lt;/ol>
&lt;p>To achieve this,&lt;/p>
&lt;ol>
&lt;li>Add your component to the app&lt;/li>
&lt;li>Set the &lt;strong>Onselect&lt;/strong> (custom) property to &lt;code>UpdateContext({loc_isButtonClicked:true})&lt;/code> - which saves a &lt;code>True&lt;/code> value in local variable to determine if that button was clicked.&lt;/li>
&lt;li>Set the &lt;strong>Onchange&lt;/strong> (custom) property to &lt;code>UpdateContext({loc_selectedOption: option})&lt;/code> - this way you set a local variable to the Selected Text of your dropdown&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="variables" src="https://m365princess.com/images/variables.png">&lt;/p>
&lt;h2 id="why-is-this-better-than-a-dropdown-menu-and-a-separate-button">Why is this better than a dropdown menu and a separate button?&lt;/h2>
&lt;p>We aim to deliver clean, consistent, and intuitive user experiences - and in cases where we want users to perform a main action or where its likely that one action is the most important action on a screen, we want to make that obvious to them. However sometimes, there are similar actions that can be performed as well - and then such a split button comes in handy. This design pattern is a great way to reduce visual clutter and provide a good user experience.&lt;/p>
&lt;p>&lt;img alt="splitbutton" src="https://m365princess.com/images/splitbutton.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>I am curious - do you use split buttons as well? What are your use cases&amp;gt;? &lt;a href="https://twitter.com/LuiseFreese/status/1555104115910377473">Let me know on twitter&lt;/a> :-)&lt;/p></description></item><item><title>How to show the app version in Power Apps Canvas Apps</title><link>https://m365princess.com/blogs/show-app-version-power-apps-canvas-apps/</link><pubDate>Wed, 03 Aug 2022 09:03:25 +0000</pubDate><guid>https://m365princess.com/blogs/show-app-version-power-apps-canvas-apps/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>You can display the current app version in a canvas app using the Power Apps for Makers connector. This is especially helpful while during developing/debugging Power Apps for Microsoft Teams.&lt;/p>
&lt;p>⚡ When building a Canvas app for Microsoft Teams, you will add your app to Teams to get contextual information such as locale or theme of Teams and validate if your app works as intended. In Power Apps Studio, there is a nifty button for that, which abstracts away the creation of a &lt;code>manifest.json&lt;/code> file, but has some flaws. (Stay tuned for a post that covers that)&lt;/p>
&lt;p>&lt;img alt="add an app to Teams" src="https://m365princess.com/images/addtoTeams.png">&lt;/p>
&lt;p>One of the problems is, that once you added the app to Teams, it doesn&amp;rsquo;t always pick up the latest version - which can make debugging a nightmare, as the UI in Teams doesn&amp;rsquo;t show you what you see in Power Apps Studio or in your extracted files in Visual Studio Code. Gave me some &lt;em>want to bang my head against the keyboard moments&lt;/em> until I figured that the changes I made were just not present in the version that Teams picked up 🤯&lt;/p>
&lt;h2 id="power-apps-for-maker-connector">Power Apps for Maker connector&lt;/h2>
&lt;p>&lt;img alt="Power Apps for makers" src="https://m365princess.com/images/powerappsformakers.png">&lt;/p>
&lt;p>Microsoft built a connector called &lt;a href="https://docs.microsoft.com/connectors/powerappsforappmakers/">Power Apps for Makers&lt;/a>, which can help with that issue. The connector can show all kinds of information about the apps that you build and we can use it to display the version number inside of the canvas app. I usually have a variable in my apps called &lt;code>isDebugMode&lt;/code> that controls if I want to display information for myself during development/debugging.&lt;/p>
&lt;p>We will use three functions of that connector:&lt;/p>
&lt;ol>
&lt;li>&lt;code>PowerAppsforMakers.GetApps&lt;/code>, which gives us the names of our apps (watch out, these are not a friendly name/displayName, but a GUID)&lt;/li>
&lt;li>&lt;code>PowerAppsforMakers.GetAppVersions&lt;/code>, in which we can pass in the app name to retrieve the version number.&lt;/li>
&lt;li>&lt;code>PowerAppsforMakers.GetApp&lt;/code> which gives us the creation timestamp&lt;/li>
&lt;/ol>
&lt;h3 id="get-the-app-name">Get the app name&lt;/h3>
&lt;p>To get the app name, add a text label, set its &lt;strong>Text&lt;/strong> property to&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">LookUp(
PowerAppsforMakers.GetApps().value,
properties.displayName = &amp;#34;PrettyUp&amp;#34;,
name
)
&lt;/code>&lt;/pre>&lt;p>We use LookUp to get the &lt;code>name&lt;/code> property of an app that has a displayName &lt;code>PrettyUp&lt;/code>. This will return the GUID that we need to pass into the &lt;code>PowerAppsforMakers.GetAppVersions&lt;/code> function in the next step.&lt;/p>
&lt;h3 id="get-timestamp">Get timestamp&lt;/h3>
&lt;p>We can get the timestamp when we last published the app with&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">PowerAppsforMakers.GetApp(
LookUp(
PowerAppsforMakers.GetApps().value,
properties.displayName = &amp;#34;PrettyUp&amp;#34;,
name
)
).properties.appVersion
&lt;/code>&lt;/pre>&lt;p>Once again we pass in the GUID dynamically with the LookUp function.&lt;/p>
&lt;p>💡 Watch out, &lt;code>properties.appVersion&lt;/code> is not the version number, but a date-time field/timestamp.&lt;/p>
&lt;h3 id="get-the-app-version">Get the app version&lt;/h3>
&lt;p>As we want to get the version every time we run &lt;strong>OnStart&lt;/strong>, its a good idea to put these functions in the &lt;strong>OnStart&lt;/strong> property of your app:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-powerappsfl" data-lang="powerappsfl">Set(
appVersion,
CountRows(
PowerAppsforMakers.GetAppVersions(
LookUp(
PowerAppsforMakers.GetApps().value,
properties.displayName = &amp;#34;PrettyUp&amp;#34;,
name
)
).value
)
);
Set(
appTimeStamp,
PowerAppsforMakers.GetApp(
LookUp(
PowerAppsforMakers.GetApps().value,
properties.displayName = &amp;#34;PrettyUp&amp;#34;,
name
)
).properties.appVersion
)
&lt;/code>&lt;/pre>&lt;p>In the first function, as we don&amp;rsquo;t want all versions, but only the latest one, we will use &lt;code>CountRows()&lt;/code> and return the value of the &lt;code>PowerAppsForMakers.GetAppVersions&lt;/code> table, in which we use the LookUp function of step 1. In the second function we will use &lt;code>PowerAppsForMakers.GetApp&lt;/code> to retrieve the appVersion.&lt;/p>
&lt;h3 id="show--app-version-on-screen">Show app version on screen&lt;/h3>
&lt;p>Change the &lt;strong>Text&lt;/strong> property of the text label of step 1 to &lt;code>&amp;quot;This is v.&amp;quot; &amp;amp;appVersion &amp;amp; &amp;quot; published last time &amp;quot; &amp;amp; appTimeStamp&lt;/code>.&lt;/p>
&lt;p>&lt;img alt="app version" src="https://m365princess.com/images/appversion.png">&lt;/p>
&lt;p>If you now publish your app and add it to Teams, you can see which version you are on and when you lastly published. This way it&amp;rsquo;s easy to determine if Teams picked up the latest version of your canvas app.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next&lt;/h2>
&lt;p>I&amp;rsquo;m curios as always - how do you use the Power Apps for maker connector and which struggles do you have with debugging Power Apps for Teams? &lt;a href="https://twitter.com/LuiseFreese/status/1554793143207448576">Let me know on twitter&lt;/a>!&lt;/p></description></item><item><title>How to navigate Power Apps studio formula bar keyboard-only</title><link>https://m365princess.com/blogs/navigate-power-apps-studio-formula-bar-keyboard/</link><pubDate>Thu, 28 Jul 2022 11:13:19 +0000</pubDate><guid>https://m365princess.com/blogs/navigate-power-apps-studio-formula-bar-keyboard/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>you can use Power Apps Studio way more efficient - and way more accessible in regards of different input devices than you might be aware of.&lt;/p>
&lt;h2 id="the-annoyance">the annoyance&lt;/h2>
&lt;p>Recently, I had a chat with &lt;a href="https://twitter.com/yanickreekmans">Yannick Reekmans&lt;/a> who told me that he really wonders how anyone could be productive on Power Apps Studio. His example was:&lt;/p>
&lt;p>&lt;em>If we select a property on the property-dropdown, then double-tab to the formula bar, make our changes and then want to select the next property, there is no obvious way on how to select the property-dropdown again - unless we use the mouse, which means losing time and also disturbing our flow of work.&lt;/em>&lt;/p>
&lt;p>And it was this kind of thing, that I always accepted as &lt;em>this is just how it is&lt;/em>, and never really questioned this poor experience.&lt;/p>
&lt;h2 id="the-solution">the solution&lt;/h2>
&lt;p>After some investigation and looking for accessibility content, I found a way of doing it.&lt;/p>
&lt;p>Let&amp;rsquo;s say we want to insert a button and change some properties, going back and forth between property pane and formula bar:&lt;/p>
&lt;ul>
&lt;li>Use the &lt;code>Tab&lt;/code> key until you are on the &lt;strong>Insert&lt;/strong> menu&lt;/li>
&lt;li>&lt;code>Enter&lt;/code>&lt;/li>
&lt;li>User the &lt;code>Tab&lt;/code> key until you are on the &lt;strong>Button&lt;/strong>&lt;/li>
&lt;li>&lt;code>Enter&lt;/code>&lt;/li>
&lt;li>2 times &lt;code>CTRL + F6&lt;/code> to select the property dropdown (you can use &lt;code>Ctrl + F6&lt;/code> or &lt;code>CTRL + Shift + F6&lt;/code> to navigate to the next landmark )&lt;/li>
&lt;li>&lt;code>Arrow down&lt;/code> or &lt;code>Arrow up&lt;/code> until the desired property is selected&lt;/li>
&lt;li>&lt;code>Enter&lt;/code>&lt;/li>
&lt;li>2 times &lt;code>Tab&lt;/code>&lt;/li>
&lt;li>change the value&lt;/li>
&lt;li>&lt;code>ESC&lt;/code>&lt;/li>
&lt;li>2 times &lt;code>Tab&lt;/code> to have the property dropdown selected again&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Power Apps Studio keyboard navigation" src="https://m365princess.com/images/PowerAppsStudioKeyboard.png">&lt;/p>
&lt;p>We could not be the only ones not knowing this :-)&lt;/p>
&lt;h2 id="some-more-resources">some more resources&lt;/h2>
&lt;ul>
&lt;li>If you want to know more on how to use Power Apps Studio with Screen Reader/keyboard, I can recommend to have a look at &lt;a href="https://www.linkedin.com/in/kelly-ford-7267577">Kelly Ford&lt;/a>&amp;rsquo;s session on &lt;a href="https://www.youtube.com/watch?v=KtpWS5XTHfM">Building a Microsoft Canvas Power App with a Screen Reader&lt;/a> - thanks &lt;a href="https://aprildunnam">April Dunnam&lt;/a> for the pointer.&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/power-apps/maker/canvas-apps/keyboard-shortcuts">Keyboard shortcuts for canvas apps&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="feedback-and-whats-next">Feedback and What&amp;rsquo;s next?&lt;/h2>
&lt;p>Now why is all of this important? Because it gives people choice on how they want to interact with Power Apps studio, and choice is always improving accessibility. For me, personally, this has already been a game changer and I am only using it for 2 hours. I very much enjoy to not rely on my mouse when changing properties of controls, and I hope this tip will help you, too. I am curios, which hidden keyboard/shortcut/accessibility gems did you find in Power Apps studio and what makes you more efficient? &lt;a href="https://twitter.com/LuiseFreese/status/1552628492692512768">Share your tips on twitter in this thread&lt;/a> :-)&lt;/p></description></item><item><title>How to build a Power Apps progress bar component</title><link>https://m365princess.com/blogs/build-powerapps-progressbar-component/</link><pubDate>Wed, 27 Jul 2022 14:20:36 +0000</pubDate><guid>https://m365princess.com/blogs/build-powerapps-progressbar-component/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Components are reusable building blocks in Power Apps which increase maker productivity and design consistency. If you are new to them, you should definitely start to learn how to build them - This post will make it easier for you! This progress bar component showcases also custom properties to give you an idea on how you can make a component customizable to different needs.&lt;/p>
&lt;h2 id="progress-bar-component-the-result">Progress Bar component: the result&lt;/h2>
&lt;p>Let&amp;rsquo;s dive in head first and have a look how the completed components will look like. As I designed them to be adjustable to different designs, here is some inspiration:&lt;/p>
&lt;p>&lt;img alt="Power Apps progress bar components" src="https://m365princess.com/images/progressbar2.gif">&lt;/p>
&lt;p>As you can see, the progress bars can have different widths and heights and can be horizontal or vertical. We will first tackle the horizontal one.&lt;/p>
&lt;h2 id="build-the-component---horizontal">Build the component - horizontal&lt;/h2>
&lt;p>For this component, that I will name &lt;code>cmp_ProgressBar_hor&lt;/code>, we only need two text labels, which you need to insert&lt;/p>
&lt;ul>
&lt;li>&lt;code>lbl_BarCurrent&lt;/code>, which shows the progress&lt;/li>
&lt;li>&lt;code>lbl_barTotal&lt;/code>, which shows the total&lt;/li>
&lt;/ul>
&lt;h3 id="create-the-custom-properties">Create the custom properties&lt;/h3>
&lt;p>We will need a bunch of custom properties so the component is more flexible&lt;/p>
&lt;ul>
&lt;li>&lt;strong>barMaxValue&lt;/strong> (Number), set default to &lt;code>100&lt;/code> - its the maximum value of the progress bar&lt;/li>
&lt;li>&lt;strong>barCurrentValue&lt;/strong> (Number), set the default &lt;code>gbl_barValue&lt;/code> - this is the variable that controls how much progress is being made&lt;/li>
&lt;li>&lt;strong>barMaxFill&lt;/strong> (Color), I set the default to &lt;code>ColorValue(&amp;quot;#1e6091&amp;quot;)&lt;/code> - it&amp;rsquo;s the fill color of the maximum value&lt;/li>
&lt;li>&lt;strong>barCurrentFill&lt;/strong> (Color), I set the default to &lt;code>ColorValue(&amp;quot;#168aad&amp;quot;)&lt;/code> - it&amp;rsquo;s the fill color of the current value&lt;/li>
&lt;li>&lt;strong>barWidth&lt;/strong> (Number), I set the default to &lt;code>200&lt;/code>, it&amp;rsquo;s the width of the progress bar&lt;/li>
&lt;li>&lt;strong>barHeight&lt;/strong> (Number), I set the default to &lt;code>42&lt;/code>, its the height of the progress bar&lt;/li>
&lt;li>&lt;strong>labelColor&lt;/strong> (Color), I set the default to &lt;code>White&lt;/code>, it&amp;rsquo;s the color of the label that shows the percentage of progress&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="custom properties overview" src="https://m365princess.com/images/customproperties_progressbar.png">&lt;/p>
&lt;h3 id="set-properties-for-the-labels">Set properties for the labels&lt;/h3>
&lt;p>For our &lt;code>lbl_BarCurrent&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Color&lt;/strong>: &lt;code>cmp_ProgressBar_hor.labelColor&lt;/code>&lt;/li>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>cmp_ProgressBar_hor.barCurrentFill&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong>: &lt;code>lbl_barTotal.Height&lt;/code>&lt;/li>
&lt;li>&lt;strong>Text&lt;/strong>: &lt;code>RoundUp(100*(cmp_ProgressBar_hor.barCurrentValue/cmp_ProgressBar_hor.barMaxValue),0) &amp;amp; &amp;quot;%&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>lbl_barTotal.Width*cmp_ProgressBar_hor.barCurrentValue/cmp_ProgressBar_hor.barMaxValue&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>lbl_barTotal.X&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>lbl_barTotal.Y&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>For our &lt;code>lbl_barTotal&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>cmp_ProgressBar_hor.barMaxFill&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong> : &lt;code>cmp_ProgressBar_hor.barHeight&lt;/code>&lt;/li>
&lt;li>&lt;strong>Text&lt;/strong>: &lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>cmp_ProgressBar_hor.barWidth&lt;/code>&lt;/li>
&lt;li>&lt;strong>X&lt;/strong>: &lt;code>5&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>5&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Now set the &lt;strong>Width&lt;/strong> of the component to &lt;code>cmp_ProgressBar_hor.barWidth+10&lt;/code> and the &lt;strong>Height&lt;/strong> to &lt;code>cmp_ProgressBar_hor.barHeight+10&lt;/code>.&lt;/p>
&lt;p>If you want to try out the component in your app, you will need to have something that will set the value of our variable &lt;code>gbl_barValue&lt;/code>. You can do this with a slider control. Set the &lt;strong>OnChange&lt;/strong> property to &lt;code>Set(gbl_barValue, Self.Value)&lt;/code>, move the slider and voila, your bar is making progress! You can now insert another component instance and play around with the custom properties and change height and width, the label color as well as the fill colors.&lt;/p>
&lt;h2 id="build-the-component---vertical">Build the component - vertical&lt;/h2>
&lt;p>If you now want to create the same component but vertical, you can duplicate the component and then we only need to adjust a few things. Name your vertical component &lt;code>cmp_ProgressBar_vert&lt;/code>.&lt;/p>
&lt;p>For our &lt;code>lbl_BarCurrent&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Color&lt;/strong>: &lt;code>cmp_ProgressBar_vert.labelColor&lt;/code>&lt;/li>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>cmp_ProgressBar_vert.barCurrentFill&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong>: &lt;code>lbl_barTotal.Height*cmp_ProgressBar_vert.barCurrentValue/cmp_ProgressBar_vert.barMaxValue&lt;/code>&lt;/li>
&lt;li>&lt;strong>Text&lt;/strong>: &lt;code>RoundUp(100*(cmp_ProgressBar_vert.barCurrentValue/cmp_ProgressBar_vert.barMaxValue),0) &amp;amp; &amp;quot;%&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>lbl_barTotal.Width&lt;/code>&lt;/li>
&lt;li>&lt;strong>Y&lt;/strong>: &lt;code>lbl_barTotal.Y+lbl_barTotal.Height-Self.Height&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>For our &lt;code>lbl_barTotal&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Fill&lt;/strong>: &lt;code>cmp_ProgressBar_vert.barMaxFill&lt;/code>&lt;/li>
&lt;li>&lt;strong>Height&lt;/strong>: &lt;code>cmp_ProgressBar_vert.barHeight&lt;/code>&lt;/li>
&lt;li>&lt;strong>Width&lt;/strong>: &lt;code>cmp_ProgressBar_vert.barWidth&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="how-can-we-use-this-component-in-apps">How can we use this component in apps?&lt;/h2>
&lt;p>People like to know the answer to the question &lt;em>Are we there, yet?&lt;/em>. Indicating their progress in long forms is a nice way to improve user experience and satisfaction, as people then know what to expect.&lt;/p>
&lt;p>&lt;img alt="progress bars" src="https://m365princess.com/images/progressbar.png">&lt;/p>
&lt;p>Want to reverse-engineer? You can &lt;a href="https://github.com/LuiseFreese/powerapps-samples/tree/main/samples/progress-bar">download the component library&lt;/a> 🚀✨&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and What&amp;rsquo;s next?&lt;/h2>
&lt;p>I would love to learn about your use cases. Where do you use progress bars or which other scenarios do you see for these bars? &lt;a href="https://twitter.com/LuiseFreese/status/1552323044697423872">Let&amp;rsquo;s talk on twitter&lt;/a> :-)&lt;/p></description></item><item><title>Why your Power Platform service principal doesn't need a Dynamics user_impersonation scope</title><link>https://m365princess.com/blogs/2022-07-25-why-your-service-principal-doesnt-need-a-dynamics-user_impersonation-scope/</link><pubDate>Mon, 25 Jul 2022 13:58:24 +0000</pubDate><guid>https://m365princess.com/blogs/2022-07-25-why-your-service-principal-doesnt-need-a-dynamics-user_impersonation-scope/</guid><description>&lt;p>Auth is hard, for most developers. This is also true when building Power Platform solutions. As I see some blog posts explaining how to use a service principal in Power Platform that contain some inaccuracies/wrong information, I&amp;rsquo;d like to clarify some auth myths :-)&lt;/p>
&lt;p>Usually, Power Automate flows run in the context of a signed-in user. If we for example want to create a row in Dataverse table with a Power Automate flow, we would use the connection of this signed-in user, which can potentially lead to lots of issues. To tackle this, we can create a &lt;a href="https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals#service-principal-object">service principal&lt;/a> that will connect our flow action with Dataverse. To do so, we will&lt;/p>
&lt;ul>
&lt;li>Register an application in Azure Active Directory&lt;/li>
&lt;li>Create an application user in the Power Platform Admin Center and assign a security role to it&lt;/li>
&lt;li>Create a new connection in our flow&lt;/li>
&lt;/ul>
&lt;h2 id="lets-first-tackle-some-auth-basics">Let&amp;rsquo;s first tackle some auth basics&lt;/h2>
&lt;p>We need both the App registration and the security role as we need to cover&lt;/p>
&lt;h3 id="authentication">Authentication&lt;/h3>
&lt;p>Authentication refers to the identity of a user or an app, it&amp;rsquo;s the answer to the question &lt;em>Who is this?&lt;/em>. You can think of it like your identity card or passport.&lt;/p>
&lt;h3 id="authorization">Authorization&lt;/h3>
&lt;p>Authorization refers to privileges of a user or an app, it&amp;rsquo;s answer to the question &lt;em>What are they allowed to do?&lt;/em> You can think of it like a boarding pass for a flight.&lt;/p>
&lt;p>In the end, you will need both documents to board/fly, as you will first need to prove your identity with your ID card/passport and then show your permissions to board a certain flight with your boarding pass (that also has your name/identity on it).&lt;/p>
&lt;h2 id="register-an-application-in-azure-active-directory">Register an application in Azure Active Directory&lt;/h2>
&lt;p>To handle authentication of our service principal, we need to register an application in Azure Active Directory. We will give this application a name and let Azure AD generate a username (App/Client Id) and password (Client secret) for it. If later a user or an application tries to log in, AAD will lookup Client Id and Client Secret and validate the identity. You can think of it like showing your passport with your picture to prove your identity.&lt;/p>
&lt;p>To register an application in Active Directory, follow these steps:&lt;/p>
&lt;ul>
&lt;li>Open &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Azure Active Directory&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Add&lt;/strong>, then &lt;strong>App registration&lt;/strong>&lt;/li>
&lt;li>Enter a name for your app&lt;/li>
&lt;li>Select &lt;strong>Accounts in this organizational directory only&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Register&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Overview&lt;/strong> and copy &lt;code>Directory (tenant) ID&lt;/code> and &lt;strong>Application (client) ID&lt;/strong> - we will need these later&lt;/li>
&lt;li>Select &lt;strong>Certificates &amp;amp; secrets&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New client secret&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Save the value of this secret somewhere - we will need this later&lt;/li>
&lt;li>NO (&lt;em>and I repeat:&lt;/em> NO API PERMISSIONS)&lt;/li>
&lt;/ul>
&lt;p>(Let&amp;rsquo;s revisit my last point a little bit later)&lt;/p>
&lt;h2 id="create-an-application-user-in-the-power-platform-admin-center-and-assign-a-security-role-to-it">Create an application user in the Power Platform Admin Center and assign a security role to it&lt;/h2>
&lt;p>Now that we created our passport, we need to tell Dataverse about this app and relate this to a security role that will ultimately grant the connection access to the tables we want it to have access.&lt;/p>
&lt;ul>
&lt;li>Head over to &lt;a href="https://aka.ms/ppac">aka.ms/ppac&lt;/a> to access the Power Platform Admin center&lt;/li>
&lt;li>Select an environment of your choice&lt;/li>
&lt;li>Select &lt;strong>Settings&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Users + permissions&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Application users&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New app user&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Add an app&lt;/strong>&lt;/li>
&lt;li>Select the app that you registered in the step before&lt;/li>
&lt;li>Select &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Select the appropriate Business unit&lt;/li>
&lt;li>Select the &lt;em>pen&lt;/em> icon next to &lt;strong>Security roles&lt;/strong>&lt;/li>
&lt;li>Add a security role, for example &lt;strong>System Administrator&lt;/strong> (God mode)&lt;/li>
&lt;li>Select &lt;strong>Save&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>What we now did is, we print our boarding pass with name and flight number on it. The &lt;code>System Administrator&lt;/code> role may access every table and we assigned this role to an app that we registered in Active Directory.&lt;/p>
&lt;h3 id="create-a-new-connection-in-our-flow">Create a new connection in our flow&lt;/h3>
&lt;p>Now let&amp;rsquo;s create a flow and test this in action:&lt;/p>
&lt;ul>
&lt;li>Open &lt;a href="https://flow.microsoft.com">flow.microsoft.com&lt;/a>&lt;/li>
&lt;li>Make sure that you are in the same environment as your created your application user in&lt;/li>
&lt;li>Create a new flow, it can be as simple as&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="flow showing a manual trigger with a textinput, and an add a row in Dataverse action mapping the textinput to a Name colummn" src="https://m365princess.com/images/spflow.png">&lt;/p>
&lt;ul>
&lt;li>On the ellipsis menue &lt;code>...&lt;/code> of the Dataverse action, select &lt;strong>Add new connection&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Connect with service principal&lt;/strong>&lt;/li>
&lt;li>Give the connection a name&lt;/li>
&lt;li>Fill in Tenant Id, Client Id, and Client secret&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;li>Save your flow&lt;/li>
&lt;/ul>
&lt;p>By this we created a new connection that uses the Dataverse application user, to which we assigned the security role &lt;code>System Administrator&lt;/code>.&lt;/p>
&lt;h2 id="little-discussion-of-api-permissions">Little discussion of API permissions&lt;/h2>
&lt;p>Next up: Run your flow - you will see, that it runs without any error - especially without any &lt;code>403&lt;/code> error, which would mean Forbidden/missing privileges.&lt;/p>
&lt;p>I saw in some blog posts (and I stumbled over that myself as well 🙄), that authors instruct to assign API permissions to the app registration, specifically &lt;strong>Dynamics CRM&lt;/strong> &amp;ndash;&amp;gt; &lt;strong>user_impersonation&lt;/strong>.&lt;/p>
&lt;p>This is relatable, if you do not realize, that security roles are the only way to control access (permissions) in Dataverse. Especially if you are familiar with app registrations that will enable apps to call Microsoft Graph API for example, you know the concept of an app registration with then one or multiple permissions that need to be assigned to it in order to make things work. It is therefore easy to fall into this trap when its about Dataverse/Dynamics CRM. But, unlike in Microsoft 365, we have security roles in Dataverse, which means that we do not need to take care of authorization (boarding pass) in the app registration but can use pre-defined or custom-tailored roles to give privileges.&lt;/p>
&lt;p>The only permission scope for &lt;strong>Dynamics CRM&lt;/strong> that we can find in the App registration is &lt;strong>user_impersonation&lt;/strong> (Delegated), and it doesn&amp;rsquo;t make sense to select this, as our service principal in fact does application-only auth. Still I have been guilty of doing the same thing, as I was not really aware of this and didn&amp;rsquo;t fully understand security roles.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>Let me know, were you aware of how security roles of an application user and a service principal connection in a Power Automate flow interact with each other? Or did you also use &lt;strong>user_impersonation&lt;/strong> scope? &lt;a href="https://twitter.com/LuiseFreese/status/1551611634799579136">Let&amp;rsquo;s talk on twitter&lt;/a> :-)&lt;/p></description></item><item><title>How to get the hex color value from a control in Power Apps</title><link>https://m365princess.com/blogs/hex-color-control-power-apps/</link><pubDate>Mon, 25 Jul 2022 09:43:56 +0000</pubDate><guid>https://m365princess.com/blogs/hex-color-control-power-apps/</guid><description>&lt;p>Sometimes, we want to output a color of a control as a hex value in a Power Apps canvas app. Unfortunately, there is no easy built-in function to convert an RGBA value into a hex color value in Power Apps. Still it is doable!&lt;/p>
&lt;h2 id="how-do-hexadecimal-colors-work">How do hexadecimal colors work?&lt;/h2>
&lt;p>To understand better what we want to convert our &lt;strong>button1.Fill&lt;/strong> into, let&amp;rsquo;s first cover how hex colors work. Hex is the abbreviation of hexadecimal, which means Base 16. We humans normally count Base 10, as we have 10 digits - 0-9. To count Base 16, we need to have 6 digits more than 0-9, as as we ran out of digits (10, 11, 12 etc can&amp;rsquo;t do that job as they are not single digits but already 2-digit numbers), we use a trick and use A, B, C, D, E, F in addition to 0-9.&lt;/p>
&lt;p>Hex colors can either br written in 6 digit or 8 digit notation.&lt;/p>
&lt;ul>
&lt;li>6 digit notation: a pound symbol/hash &lt;code>#&lt;/code> and then 6 digits (2 for the Red (R) value, 2 for the Green (G) value, 2 for the Blue (B) value)&lt;/li>
&lt;li>8 digit notation: a pound symbol/hash &lt;code>#&lt;/code> and then 8 digits (2 for the Red (R) value, 2 for the Green (G) value, 2 for the Blue (B) value, 2 digits for the alpha value that determines the transparency of the color)&lt;/li>
&lt;/ul>
&lt;p>If we now set the &lt;strong>Fill&lt;/strong> property of a &lt;code>button1&lt;/code> to &lt;code>RGBA(100,100,100,1&lt;/code>, we can use the &lt;code>JSON&lt;/code> function to get the 8 digit hex notation:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-PowerFx" data-lang="PowerFx">Set(
gbl_colorAsHex,
JSON(
button1.Fill,
JSONFormat.IgnoreUnsupportedTypes
)
);
&lt;/code>&lt;/pre>&lt;p>This will return &lt;code>&amp;quot;#646464ff&amp;quot;&lt;/code>. Please note that this value&lt;/p>
&lt;ul>
&lt;li>is encapsulated by double quotes&lt;/li>
&lt;li>is written in 8 digit notation&lt;/li>
&lt;/ul>
&lt;p>As we want 6-digit hex notation, we will use some Regex magic and match this value with &lt;code>#[a-fA-F0-9]{6}&lt;/code> which will return &lt;code>#646464&lt;/code>.&lt;/p>
&lt;p>To unpack what &lt;code>#[a-fA-F0-9]{6}&lt;/code> means:&lt;/p>
&lt;ul>
&lt;li>&lt;code>#&lt;/code> means that our string needs to start with a pound symbol &lt;code>#&lt;/code>&lt;/li>
&lt;li>&lt;code>[a-fA-F0-9]{6}&lt;/code> represents the letters from a-f, A-F, or digit from 0-9 with a length of 6&lt;/li>
&lt;/ul>
&lt;p>Code looks like this then:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-PowerFX" data-lang="PowerFX">Set(
gbl_colorAsHex,
Match(
JSON(
button1.Fill,
JSONFormat.IgnoreUnsupportedTypes
),
&amp;#34;#[a-fA-F0-9]{6}&amp;#34;
).FullMatch
);
&lt;/code>&lt;/pre>&lt;p>Now we can use the &lt;code>gbl_colorAsHex&lt;/code> variable to color other controls or to output the hex value on a text label&lt;/p>
&lt;p>I used this in the &lt;a href="https://github.com/pnp/powerapps-samples/tree/main/samples/accessibility-color-contrast-checker">Power Apps Color Contrast Checker&lt;/a>, the result looks like this:&lt;/p>
&lt;p>&lt;img alt="Power Apps button showing hex color value of the button itself" src="https://m365princess.com/images/HexOutput.png">&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and What&amp;rsquo;s next?&lt;/h2>
&lt;p>I am curious which use cases you see? Let me know on twitter!&lt;/p></description></item><item><title>How to build a Power Apps Likert component</title><link>https://m365princess.com/blogs/build-powerapps-likert-component/</link><pubDate>Tue, 19 Jul 2022 16:11:14 +0000</pubDate><guid>https://m365princess.com/blogs/build-powerapps-likert-component/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Power Apps components allow makers to reuse fragments and patterns of their work to ensure design consistency and developer productivity. This Likert component showcases also custom properties and is an alternative way to gather data from users.&lt;/p>
&lt;h2 id="what-are-components-and-why-would-you-want-them">What are components and why would you want them?&lt;/h2>
&lt;p>Makers can create reusable building blocks and save them as components to either use them in a specific app or to use them across apps with a component library. Benefits of creating components:&lt;/p>
&lt;ul>
&lt;li>build once, use repeatedly - no need to reinvent the wheel over and over again&lt;/li>
&lt;li>if a component needs to be updated, it is updated once which will be reflected in all instances that are used inside of an app&lt;/li>
&lt;li>design consistency&lt;/li>
&lt;li>enhanced user experience due to standardization&lt;/li>
&lt;li>increased developer velocity&lt;/li>
&lt;li>improved productivity&lt;/li>
&lt;/ul>
&lt;p>Creating components is an investment into better performing, good looking, easy debuggable and maintainable apps. It&amp;rsquo;s true, that the initial effort of building them is a bit higher than using regular controls (when only used one time), but the more a component is used and the more complex an app gets, the better is the return on investment of creating components.&lt;/p>
&lt;h2 id="likert-component-result">Likert component: result&lt;/h2>
&lt;p>To give you an idea how the likert component looks like, you can watch it here in action&lt;/p>
&lt;p>&lt;img alt="gif showing the Likert component in action" src="https://m365princess.com/images/likert.gif">&lt;/p>
&lt;p>Nice thing about it: It is already working in three different themes (default, dark, high contrast) and the design matches Microsoft Teams. To make this happen, we need to adjust the &lt;strong>OnStart&lt;/strong> property of our app to this code:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-PowerFx" data-lang="PowerFx">// THEME
Set(
gblThemeDark,
Param(&amp;#34;theme&amp;#34;) = &amp;#34;dark&amp;#34; Or drp_dropdown.SelectedText.Value = &amp;#34;dark&amp;#34;
);
Set(
gblThemeHiCo,
Param(&amp;#34;theme&amp;#34;) = &amp;#34;contrast&amp;#34; Or drp_dropdown.SelectedText.Value = &amp;#34;high contrast&amp;#34;
);
Set(
gblAppColors,
{
// Fluent Design Colors
HighContrastDisabled: ColorValue(&amp;#34;#2EF149&amp;#34;),
HighContrastHyperlinks: ColorValue(&amp;#34;#FFFD39&amp;#34;),
HighContrastSelectedBackground: ColorValue(&amp;#34;#00EBFD&amp;#34;),
HighContrastSelectedText: ColorValue(&amp;#34;#000000&amp;#34;),
NeutralWebGray30: ColorValue(&amp;#34;#EDEBE9&amp;#34;),
OverlayTransparent: RGBA(0,0,0 0),
TeamsDarkTint10: ColorValue(&amp;#34;#6264A7&amp;#34;),
TeamsDefaultPrimary: ColorValue(&amp;#34;#6264A7&amp;#34;)
}
);
&lt;/code>&lt;/pre>&lt;h2 id="build-the-component">Build the component&lt;/h2>
&lt;ol>
&lt;li>Create a new canvas component and add&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>a slider control&lt;/li>
&lt;li>a gallery with
&lt;ul>
&lt;li>a textlabel&lt;/li>
&lt;li>an icon&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="likert component overview" src="https://m365princess.com/images/likert_component.png">&lt;/p>
&lt;ol>
&lt;li>Create custom properties:&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="custom properties" src="https://m365princess.com/images/likert_component-properties.png">&lt;/p>
&lt;ul>
&lt;li>&lt;code>SliderStyles&lt;/code> as Record, add this code:&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-PowerFx" data-lang="PowerFx">{
BorderColor:
If(gblThemeHiCo,gblAppColors.OverlayTransparent,gblThemeDark,gblAppColors.OverlayTransparent,gblAppColors.OverlayTransparent),
DisabledBorderColor:
If(gblThemeHiCo,gblAppColors.HighContrastDisabled,gblThemeDark,gblAppColors.NeutralWebGray150,gblAppColors.NeutralWebGray30),
FocusedBorderColor:
If(gblThemeHiCo,gblAppColors.HighContrastSelectedBackground,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
HandleActiveFill:
If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
HandleFill:
If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
HandleHoverFill:If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
RailFill:
If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
RailHoverFill:
If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
ValueFill:If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
ValueHoverFill:
If(gblThemeHiCo,gblAppColors.HighContrastHyperlinks,gblThemeDark,gblAppColors.TeamsDarkTint10,gblAppColors.TeamsDefaultPrimary),
Default:3
}
&lt;/code>&lt;/pre>&lt;p>which references the colors of the slider control to the ones that we set in &lt;strong>OnStart&lt;/strong> of the app and sets the default value to 3.&lt;/p>
&lt;ul>
&lt;li>&lt;code>GalleryStyles&lt;/code> as Record, add this code:&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-PowerFx" data-lang="PowerFx">{Color: gblAppStyles.Label.Color}
&lt;/code>&lt;/pre>&lt;p>which references the colors of textlabel and icon&lt;/p>
&lt;ul>
&lt;li>&lt;code>LikertContent&lt;/code> as Table, add this code:&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-PowerFx" data-lang="PowerFx">Table(
{
Id: 1,
Body: &amp;#34;Strongly Disagree&amp;#34;,
Icon: Icon.EmojiFrown
},
{
Id: 2,
Body: &amp;#34;Disagree&amp;#34;,
Icon: Icon.EmojiSad
},
{
Id: 3,
Body: &amp;#34;Neutral&amp;#34;,
Icon: Icon.EmojiNeutral
},
{
Id: 4,
Body: &amp;#34;Agree&amp;#34;,
Icon: Icon.EmojiSmile
},
{
Id: 5,
Body: &amp;#34;Strongly Agree&amp;#34;,
Icon: Icon.EmojiHappy
}
)
&lt;/code>&lt;/pre>&lt;p>which populates our gallery with an id (serves also as rating), the text to be displayed in the label and the icon to be displayed.&lt;/p>
&lt;ul>
&lt;li>&lt;code>OnChange&lt;/code> Behavior as Text, add one parameter &lt;code>Rating&lt;/code> as Number. When we later ad an instance of our component to an app, we will call this function and update it to pass the current value from the slider.&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>Work on the Slider&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Set the &lt;strong>Default&lt;/strong> property to &lt;code>cmp_Likert.SliderStyles.Default&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>OnChange&lt;/strong> property to &lt;code>cmp_Likert.OnChange(sli_Rating.Value)&lt;/code>&lt;/li>
&lt;/ul>
&lt;ol start="4">
&lt;li>Work on the Gallery&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Set the &lt;strong>Items&lt;/strong> property to &lt;code>cmp_Likert.LikertContent&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Icon&lt;/strong> property of the icon to &lt;code>ThisItem.Icon&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Text&lt;/strong> property of the textlabel to &lt;code>ThisItem.Body&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Height&lt;/strong> and &lt;strong>Width&lt;/strong> properties of the icon to &lt;code>If(sli_Rating.Value= ThisItem.Id, 30, 20)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Size&lt;/strong> property of the textlabel to &lt;code>If(sli_Rating.Value= ThisItem.Id, 11, 9)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>FontWeight&lt;/strong> property of the textlabel to &lt;code>If(sli_Rating.Value= ThisItem.Id, Bold, Normal)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Color&lt;/strong> property of the icon to &lt;code>If(sli_Rating.Value= ThisItem.Id, cmp_Likert.SliderStyles.HandleActiveFill, cmp_Likert.GalleryStyles.Color)&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="add-the-component-to-your-app">Add the component to your app&lt;/h2>
&lt;p>Now that our component is complete&lt;/p>
&lt;ul>
&lt;li>Add it to the app&lt;/li>
&lt;li>Set the &lt;strong>OnChange&lt;/strong> property to &lt;code>Set(gbl_likeValue, Rating)&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>What happens now is, that when you move the slider, the current value label and emoji are emphasized and a we store the current value in a variable.&lt;/p>
&lt;h2 id="feedback--whats-next">Feedback &amp;amp; What&amp;rsquo;s next&lt;/h2>
&lt;p>I am curios - where would you use such a component? Can it replace rating DataCards? Which enhancement would you like to see? &lt;a href="https://twitter.com/LuiseFreese/status/1549660685948407809">Tell me on twitter&lt;/a>.&lt;/p></description></item><item><title>How to implement a Power Automate progress indicator in Power Apps</title><link>https://m365princess.com/blogs/implement-power-automate-progress-indicator-power-apps/</link><pubDate>Mon, 11 Jul 2022 17:37:29 +0000</pubDate><guid>https://m365princess.com/blogs/implement-power-automate-progress-indicator-power-apps/</guid><description>&lt;h2 id="tldr-an-indicator-to-show-the-progress-of-a-power-automate-flow">tl;dr: An indicator to show the progress of a Power Automate flow&lt;/h2>
&lt;p>Especially when users need to perform several tasks to complete a workload, they like to know, if the thing worked. Simple notifications like &amp;ldquo;your request has been submitted&amp;rdquo; still leave them in some uncertainty in regards of the progress of said process. Learn here how a simple SharePoint list can help you to achieve exactly that.&lt;/p>
&lt;p>&lt;img alt="walkthrough in Power Apps" src="https://m365princess.com/images/flowstate.gif">&lt;/p>
&lt;h2 id="first-idea-respond-to-power-apps">First idea: Respond to Power Apps&lt;/h2>
&lt;p>In Power Apps, we can start Power Automate flows and even get a response back, &lt;a href="https://twitter.com/lowcodelewis">Lewis Baybutt&lt;/a> recently blogged about how to &lt;a href="https://www.lowcodelewis.com/blog/notify-users-of-successful-flow-runs">Notify users if the flow ran successfully&lt;/a>. But what if the flow has several big steps and we want to inform users along the way in Power Apps? Unfortunately, we can&amp;rsquo;t use the &lt;strong>Respond to PowerApps&lt;/strong> action more than one time in a flow, which means that this is already out of the game.&lt;/p>
&lt;h2 id="second-idea-external-datasource-to-the-rescue">Second idea: External datasource to the rescue&lt;/h2>
&lt;p>But what if we logged steps/status of our flow into an external table and update our app from there? This could be a Dataverse table, a SharePoint list or whatever makes you happy.&lt;/p>
&lt;p>In total we will need&lt;/p>
&lt;ul>
&lt;li>A &lt;a href="https://m365princess.com/blogs/implement-power-automate-progress-indicator-power-apps/#sharepoint-list-setup">SharePoint list&lt;/a>&lt;/li>
&lt;li>A &lt;a href="https://m365princess.com/blogs/implement-power-automate-progress-indicator-power-apps/#power-apps-setup">Power Apps canvas app&lt;/a>&lt;/li>
&lt;li>A &lt;a href="https://m365princess.com/blogs/implement-power-automate-progress-indicator-power-apps/#power-automate-flow-setup">Power Automate flow&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="sharepoint-list-setup">SharePoint list setup&lt;/h3>
&lt;p>I used a SharePoint list called &lt;code>LogList&lt;/code> in which I need 3 columns:&lt;/p>
&lt;ul>
&lt;li>Title (comes already out of the box)&lt;/li>
&lt;li>GUID (single line of text, to hold a GUID that we get from Power Apps as we want to use &lt;code>LookUp&lt;/code> to get the right flow run)&lt;/li>
&lt;li>Status (number, to hold the status/step of the flow progress)&lt;/li>
&lt;/ul>
&lt;h3 id="power-apps-setup">Power Apps setup&lt;/h3>
&lt;p>In our Power App, we need&lt;/p>
&lt;ul>
&lt;li>a button which will start the flow&lt;/li>
&lt;li>a timer which will take care of refreshing&lt;/li>
&lt;li>a gallery to indicate progress&lt;/li>
&lt;/ul>
&lt;h4 id="gallery">Gallery&lt;/h4>
&lt;p>For everything, that needs to be repeated, I use a gallery in Power Apps. The effect on time to build, design consistency and performance is really amazing.&lt;/p>
&lt;ul>
&lt;li>Set the &lt;strong>Items&lt;/strong> property to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-Power" data-lang="Power">Table(
{
Id: 1,
Value: &amp;#34;validated data&amp;#34;,
Icon: Icon.Check
},
{
Id: 2,
Value: &amp;#34;approval pending&amp;#34;,
Icon: Icon.Check
},
{
Id: 3,
Value: &amp;#34;approval granted&amp;#34;,
Icon: Icon.Check
}
)
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Add a Label, a Rectangle, an Icon, a Circle to the Gallery&lt;/li>
&lt;li>Set the &lt;strong>Text&lt;/strong> property of the Label to &lt;code>ThisItem.Value&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Visible&lt;/strong> property of the Rectangle to &lt;code>If(loc_currentStatus.Status&amp;gt;=ThisItem.Id, true, false)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Fill&lt;/strong> property of the Circle to &lt;code>If(loc_currentStatus.Status&amp;gt;=ThisItem.Id, Self.BorderColor, Transparent)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Visible&lt;/strong> property of the Icon to &lt;code>loc_currentStatus.Status&amp;gt;=ThisItem.Id&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Icon&lt;/strong> property of the Icon to &lt;code>Icon.Check&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="button">Button&lt;/h4>
&lt;p>Our Button shall kick of the flow and pass in a unique ID&lt;/p>
&lt;ul>
&lt;li>Set the &lt;code>OnSelect&lt;/code> property of the button to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-Power" data-lang="Power">//create GUID
Set(gbl_stateGUID,GUID());
//start Power Automate Flow
flowStatus.Run(gbl_stateGUID)
&lt;/code>&lt;/pre>&lt;h4 id="timer">Timer&lt;/h4>
&lt;p>We could of course build a refresh button as well for all impatient users, but a hidden timer is way more elegant.&lt;/p>
&lt;ul>
&lt;li>Set the &lt;strong>OnTimerEnd&lt;/strong> property to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-Power" data-lang="Power">Refresh(LogList);
UpdateContext({loc_currentStatus: LookUp(LogList,GUID=Text(gbl_stateGUID))})
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Set the &lt;strong>AutoStart&lt;/strong> property to &lt;code>!IsBlank(gbl_stateGUID)&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Duration&lt;/strong> property to &lt;code>500&lt;/code> (ms)&lt;/li>
&lt;li>Set the &lt;strong>Repeat&lt;/strong> property to &lt;code>loc_currentStatus.Status&amp;lt;&amp;gt;3&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="reset-button">Reset button&lt;/h4>
&lt;p>For testing purposes its really nice as well to have a Reset button&lt;/p>
&lt;ul>
&lt;li>Set its &lt;strong>OnSelect&lt;/strong> button to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-Power" data-lang="Power">UpdateContext(
{
loc_currentStatus: Patch(loc_currentStatus,
{
Status: 0,
Title: &amp;#34;you submitted&amp;#34;
}
)
}
);
Set(gbl_stateGUID,&amp;#34;&amp;#34;);
&lt;/code>&lt;/pre>&lt;p>&lt;img alt="Power Apps canvas app" src="https://m365princess.com/images/stateflow.png">&lt;/p>
&lt;h3 id="power-automate-flow-setup">Power Automate flow setup&lt;/h3>
&lt;ul>
&lt;li>Create a new flow from within Power Apps Studio, which automatically gives it the right trigger&lt;/li>
&lt;li>Create an item in your SharePoint list, pass in the &lt;strong>GUID&lt;/strong> from Power Apps in the GUID column and set the status to &lt;code>0&lt;/code>&lt;/li>
&lt;li>Now add a scope and perform the actions you want to perform in this scope. To mock this, I added a delay&lt;/li>
&lt;li>After this first scope, update the list item with a status message in the Title and set the Status to &lt;code>1&lt;/code>&lt;/li>
&lt;li>Add another scope and perform the actions you want to perform in this scope. To mock this, I added another delay&lt;/li>
&lt;li>After this second scope, update the list item with a status message in the Title ands et the Status to &lt;code>2&lt;/code>&lt;/li>
&lt;li>Add another scope and perform the actions you want to perform in this scope. To mock this, I added another delay&lt;/li>
&lt;li>After this third scope, update the list item with a status message in the Title and set the Status to &lt;code>3&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="flow overview" src="https://m365princess.com/images/statelogflow.png">&lt;/p>
&lt;h2 id="conclusion-and-whats-next">Conclusion and What&amp;rsquo;s next&lt;/h2>
&lt;p>This is how you pass values between Power Apps and Power Automate and hook them into a nice looking UI. For flows that run for quite a while, its a nice way to inform users in-app and not to rely on sending Adaptive Cards or other notifications. I am curious: Which scenarios do you see? &lt;a href="https://twitter.com/LuiseFreese/status/1546598926186434564">Let me know on twitter&lt;/a>&lt;/p>
&lt;p>&lt;img alt="UI" src="https://m365princess.com/images/flowstateprocess.gif">&lt;/p></description></item><item><title>How to enhance maker experience with a custom theme for Teams apps</title><link>https://m365princess.com/blogs/2022-07-06-how-to-enhance-maker-experience-with-a-custom-theme-for-teams-apps-in-power-apps-studio/</link><pubDate>Wed, 06 Jul 2022 11:51:35 +0000</pubDate><guid>https://m365princess.com/blogs/2022-07-06-how-to-enhance-maker-experience-with-a-custom-theme-for-teams-apps-in-power-apps-studio/</guid><description>&lt;h2 id="tldr">tl;dr&lt;/h2>
&lt;p>Creating beautiful UI in Power Apps canvas apps is hard - even more when the app shall run in the context of Microsoft Teams, as there is no theme out-of-the-box that matches Teams look and feel or supports accessibility in terms of adapting to default, dark, and high-contrast mode.&lt;/p>
&lt;h2 id="design">Design&lt;/h2>
&lt;p>When building UI in canvas apps, we have some options to apply a certain design to our app. If we want to build an app that runs in Teams, we &lt;em>should&lt;/em> aim for a Teams native looks &amp;amp; feel. Let me explain, the good, bad an ugly :-)&lt;/p>
&lt;h3 id="the-ugly">The ugly&lt;/h3>
&lt;p>Unimaginable, but still the very ugly truth: People live with the default styles or only apply minimal styling to any controls. Their apps then look like this:&lt;/p>
&lt;p>&lt;img alt="screenshot of a very ugly Power Apps canvas app" src="https://m365princess.com/images/uglyPowerApp.png">&lt;/p>
&lt;p>Now this is not just about my personal taste, but very obviously, this app neither matches the Teams Design, nor is it inclusive and accessible to all people, as it lacks of default, dark and high-contrast mode.&lt;/p>
&lt;h3 id="the-bad">The bad&lt;/h3>
&lt;p>We can style each and every single button, text label, input, dropdown etc. to match our design requirements. This will often result in styling one of each correctly and then copy-paste them. It is a tedious task and very often makers tend to try to shortcut this task which then results in poor DevX and inconsistent UI.&lt;/p>
&lt;h3 id="the-good">The Good&lt;/h3>
&lt;p>Of course, there are better options. The default style of all controls is determined by the &lt;code>themes.json&lt;/code> file that we can find in the unpacked &lt;code>.msapp&lt;/code> file of the app. We can unpack the &lt;code>.msapp&lt;/code> with &lt;a href="https://docs.microsoft.com/power-apps/developer/data-platform/powerapps-cli">Power Platform CLI&lt;/a>, then manipulate the file by changing values for all the properties that we want to change and then re- pack the source code files so that they make up an &lt;code>.msapp&lt;/code> again.
By applying all the Teams-appropriate colors, shapes, sizes, fonts, we can achieve an &lt;em>almost&lt;/em>-native look and feel just with the (new) default controls.&lt;/p>
&lt;h3 id="the-better">The Better&lt;/h3>
&lt;p>However, you will have stumbled upon the &lt;em>almost&lt;/em> :-) To match the Teams look, some controls need styling that can neither be applied by manipulating &lt;code>themes.json&lt;/code> nor be achieved in Power Apps Studio. Let me give you two examples:&lt;/p>
&lt;h4 id="example-input-field">Example: Input field&lt;/h4>
&lt;p>For an input field, we need a border only at the bottom of the control, for which we don&amp;rsquo;t have a property in Power Apps Studio, nor in the &lt;code>themes.json&lt;/code>.&lt;/p>
&lt;h4 id="example-calendar-control">Example: Calendar control&lt;/h4>
&lt;p>Even with manipulated values in &lt;code>themes.json&lt;/code>, the calendar pop-up of the datepicker control doesn&amp;rsquo;t fully match Teams&amp;rsquo; design when we apply dark mode or high contrast - which means that this is design-inconsistent and not accessible.&lt;/p>
&lt;p>To illustrate this: On the left is the default calendar, the fill color of the header can not be styled within Power Apps studio. In the middle is the calendar already styled with the properties that we can find in theme.json, however, the background fill color can&amp;rsquo;t be changed. On the right is the custom canvas component, that nicely adapts to all three Teams themes.&lt;/p>
&lt;p>&lt;img alt="calendar comparison" src="https://m365princess.com/images/calendar-compare.png">&lt;/p>
&lt;p>Even if we do not need these particular controls, usually we can only see very basic UI in PowerApps, which is a big contrast to the advanced and slick UI of Microsoft Teams. Ever saw such a carousel in a Power App?&lt;/p>
&lt;p>&lt;img alt="caroussel component" src="https://m365princess.com/images/Carousel-component.gif">&lt;/p>
&lt;h4 id="solution-teams-theme--reference-app-and-10-components">Solution: Teams theme &amp;amp; reference app and 10 components&lt;/h4>
&lt;p>To tackle this, I did not only create a Teams-design-matching &lt;code>themes.json&lt;/code> for you, but also a reference app, that allows you to switch between default, dark, and high-contrast mode and also 10 canvas components for regular controls like input field or dropdown menu but also for more advanced UI snippets like the carousel component (as shown above), the calendar component, and more.&lt;/p>
&lt;p>&lt;img alt="example screen" src="https://m365princess.com/images/ListScreen.png">&lt;/p>
&lt;p>All components adapt to default, dark, and high-contrast mode and are also used in example screens that serve as inspiration how a Teams app can look like. You can find this in the &lt;a href="https://github.com/pnp/powerapps-samples/tree/main/samples/fluentui-for-teams-theme">PowerApps-samples repo&lt;/a> on GitHub.&lt;/p>
&lt;p>&lt;img alt="Fluent UI for Teams theme" src="https://m365princess.com/images/FluentUiforTeams.gif">&lt;/p>
&lt;h2 id="but-arent-there-ootb-alternatives">But aren&amp;rsquo;t there OOTB alternatives?&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>Power Apps Studio knows the concept of themes like PowerPoint does - but apparently no one thought about implementing a Teams theme into Power Apps studio directly so that this goodness is available to all makers just via UI.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Microsoft&amp;rsquo;s recently released &lt;a href="https://docs.microsoft.com/power-platform/guidance/creator-kit/overview">Creator Kit for makers&lt;/a> doesn&amp;rsquo;t offer Microsoft Teams specific Fluent UI components, but only Office specific Fluent UI components.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Within Power Apps Studio in Microsoft Teams we have access to Fluent UI controls but they neither match Office (Fluent UI) nor Teams (Fluent UI Northstar), as a button for example has no rounded corner (Office) but has a default &lt;strong>Fill&lt;/strong> of &lt;code>6264A7&lt;/code>, which is &amp;lsquo;Teams blurple&amp;rsquo;. This means, that this is a very weird mix of both design approaches :-(&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>In my opinion, a dedicated Teams theme, a component library with common UI elements and guidance how to work with it should&amp;rsquo;ve been in the product - as of day 1.&lt;/p>
&lt;p>Let me know what you think on &lt;a href="https://twitter.com/LuiseFreese/status/1544740027082706946">twitter&lt;/a>.&lt;/p></description></item><item><title>How to build a color-contrast-ratio checker for improved accessibility in Power Apps</title><link>https://m365princess.com/blogs/color-contrast-ratio-checker/</link><pubDate>Sun, 03 Jul 2022 12:53:04 +0000</pubDate><guid>https://m365princess.com/blogs/color-contrast-ratio-checker/</guid><description>&lt;h2 id="tldr">tl:dr&lt;/h2>
&lt;p>Color contrast ratio is important for accessibility - here is how to build an in-app checker to build more accessible Power Apps&lt;/p>
&lt;h2 id="what-is-an-color-contrast-ratio-checker-and-why-would-i-care">What is an color-contrast-ratio checker and why would I care?&lt;/h2>
&lt;p>Although &lt;a href="https://make.powerapps.com">Power Apps studio&lt;/a> has a built-in Accessibility checker, which warns makers if they for example forgot an &lt;code>AccessibleLabel&lt;/code> which is used by screen reader applications to announce what a control is about, it doesn&amp;rsquo;t cover any issues that might occur regarding the contrast of used colors.&lt;/p>
&lt;p>This means, that makers don&amp;rsquo;t get a warning when they choose colors that don&amp;rsquo;t match accessibility standards. The &lt;a href="https://docs.microsoft.com/power-apps/maker/canvas-apps/accessible-apps-color">Power Apps documentation&lt;/a> recommends a color contrast ratio for texts and backgrounds of at least 4.5:1 and for large text of at least 3:1, however there is no guidance on how to calculate this ratio and even then, the information is not where makers need them - in their app.&lt;/p>
&lt;blockquote>
&lt;p>Accessibilty is one of the most crucial design goals for every app and every website as we do not aim to proactively exclude people because we do not match their needs with appropriate features.&lt;/p>
&lt;/blockquote>
&lt;h2 id="idea-color-contrast-ratio-checker-for-power-apps--in-power-apps">Idea: Color-Contrast-Ratio checker for Power Apps -in Power Apps&lt;/h2>
&lt;p>What if makers had an app or even a hidden screen in all of their apps where they can check contrast ratio in the early stage of app development to avoid accessibility issues that are not addressed by the Power Apps Accessibility Checker?&lt;/p>
&lt;p>&lt;img alt="walthrough a color contrast ratio checker built in Power Apps" src="https://m365princess.com/images/contrastcheck.gif">&lt;/p>
&lt;h3 id="requirements">Requirements&lt;/h3>
&lt;ul>
&lt;li>Let makers easily change the primary color and background color of their app&lt;/li>
&lt;li>Automatically calculates if the color contrast ratio is sufficient to fulfill &lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/understanding-techniques">WCAG 2.1 Success Criteria&lt;/a>&lt;/li>
&lt;li>Informs maker about the outcome of that calculation&lt;/li>
&lt;/ul>
&lt;h3 id="how-to-let-makers-easily-change-the-primary-color-and-background-color-of-their-app">How to let makers easily change the primary color and background color of their app&lt;/h3>
&lt;ol>
&lt;li>Insert some controls (buttons, TextLabels, Icons, TextInput, etc.) on your screen&lt;/li>
&lt;li>Insert 3 slider controls reflecting the RGB values for the primary color&lt;/li>
&lt;li>Set the &lt;code>Max&lt;/code> property of all 3 sliders to 255&lt;/li>
&lt;li>Rename the sliders to &lt;strong>sli_Red&lt;/strong>, &lt;strong>sli_Green&lt;/strong>, and &lt;strong>sli_Blue&lt;/strong>&lt;/li>
&lt;li>Insert a Button on the screen, rename it to &lt;strong>btn_primaryColor&lt;/strong>&lt;/li>
&lt;li>Set its &lt;code>Fill&lt;/code> property to &lt;code>RGBA(sli_Red.Value, sli_Green.Value, sli_Blue.Value,1)&lt;/code> - This button will now be our reference for the primary color the entire app&lt;/li>
&lt;li>Insert another 3 slider controls reflecting the RGB values for the Background&lt;/li>
&lt;li>Set the &lt;code>Max&lt;/code> property of all 3 sliders to 255&lt;/li>
&lt;li>Rename them accordingly&lt;/li>
&lt;li>Insert another button on the screen, rename it to &lt;strong>btn_backgroundColor&lt;/strong>&lt;/li>
&lt;li>Set its &lt;code>Fill&lt;/code> property to &lt;code>RGBA(sli_Red_1.Value, sli_Green_1.Value, sli_Blue_1.Value,1)&lt;/code> - This button will now be our reference for background color in the entire app&lt;/li>
&lt;li>Set the &lt;code>Text&lt;/code> property of the &lt;strong>btn_primaryColor&lt;/strong> to &lt;code>&amp;quot;primary color: RGB(&amp;quot; &amp;amp; sli_Red.Value &amp;amp; &amp;quot;, &amp;quot; &amp;amp; sli_Green.Value &amp;amp; &amp;quot;, &amp;quot; &amp;amp; sli_Blue.Value &amp;amp; &amp;quot; ,1) &amp;quot;&lt;/code>&lt;/li>
&lt;li>Set the &lt;code>text&lt;/code> property of the &lt;strong>btn_backgroundColor&lt;/strong> to &lt;code>&amp;quot;background: RGB(&amp;quot; &amp;amp; sli_Red_1.Value &amp;amp; &amp;quot;, &amp;quot; &amp;amp; sli_Green_1.Value &amp;amp; &amp;quot;, &amp;quot; &amp;amp; sli_Blue_1.Value &amp;amp; &amp;quot; ,1) &amp;quot;&lt;/code>&lt;/li>
&lt;li>Set the &lt;code>Fill&lt;/code> property of your screens background to &lt;code>btn_backgroundColor.Fill&lt;/code>&lt;/li>
&lt;li>Set the &lt;code>Fill&lt;/code> property of the buttons you inserted in step 1 to &lt;code>btn_primaryColor.Fill&lt;/code>&lt;/li>
&lt;li>Set the &lt;code>Color&lt;/code> property of the TextLabel you inserted in step 1 to &lt;code>btn_primaryColor.Fill&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>If you now use the sliders for background color and primary color, you can see that the color of your two reference buttons changes and with them the entire color scheme of your app.&lt;/p>
&lt;h3 id="how-to-automatically-calculate-if-the-color-contrast-ratio-is-sufficient-to-fulfill-wcag-21-success-criteria">How to automatically calculate if the color contrast ratio is sufficient to fulfill WCAG 2.1 Success Criteria&lt;/h3>
&lt;p>Let&amp;rsquo;s first understand what needs to happen so that we can implement this in our app: For matching the WCAG 2.1 Success Criteria we need to take care of two important values:&lt;/p>
&lt;ol>
&lt;li>brightness difference&lt;/li>
&lt;li>color difference&lt;/li>
&lt;/ol>
&lt;p>The brightness difference is calculated as &lt;code>(299*R_pc + 587*G_pc + 114*B_pc)/1000 - (299*R_bg + 587*G_bg + 114*B_bg)/1000&lt;/code>, where as &lt;code>R&lt;/code> , &lt;code>G&lt;/code>, &lt;code>B&lt;/code> stands for Red, Green, Blue and &lt;code>_pc&lt;/code> stand for primaryColor and &lt;code>_bg&lt;/code> stands for BackgroundColor. The result of this calculation needs to be a number &amp;gt; 125.&lt;/p>
&lt;p>In our app we will therefore calculate the brightness difference with &lt;code>(299*sli_Red.Value + 587*sli_Green.Value + 114*sli_Blue.Value)/1000 - (299*sli_Red_1.Value + 587*sli_Green_1.Value + sli_Blue_1.Value*114)/1000&lt;/code>&lt;/p>
&lt;p>The color difference is calculated as &lt;code>-((R_pc - R_bg) + (G_pc - G_bg + (B_pc - B_bg))&lt;/code>. The result of this calculation needs to be a number &amp;gt; 500.&lt;/p>
&lt;p>In our app we will therefore calculate the color difference with &lt;code>-((sli_Red.Value -sli_Red_1.Value) + (sli_Green.Value-sli_Green_1.Value ) + (sli_Blue.Value-sli_Blue_1.Value))&lt;/code>&lt;/p>
&lt;p>💡 &lt;strong>Important: Both conditions need to be fulfilled.&lt;/strong>&lt;/p>
&lt;h3 id="how-to-indicate-the-outcome-of-that-calculation">How to indicate the outcome of that calculation&lt;/h3>
&lt;p>Easiest part is now to make the result of these calculations visible to makers:&lt;/p>
&lt;ol>
&lt;li>Insert a text label for &lt;code>lblbrightnessDifference&lt;/code> and set the &lt;code>Text&lt;/code> property to the brightness formula shown above&lt;/li>
&lt;li>Set the &lt;code>Color&lt;/code> property to &lt;code>If(Value(Self.Text) &amp;lt; 125, Red, Black)&lt;/code>&lt;/li>
&lt;li>Insert a text label for &lt;code>lblColorDifference&lt;/code> and set the &lt;code>Text&lt;/code> property to the color formula shown above&lt;/li>
&lt;li>Set the &lt;code>If(Value(Self.Text) &amp;lt; 500, Red, Black)&lt;/code>&lt;/li>
&lt;li>Insert another text label and set the &lt;code>Text&lt;/code> property to &lt;code>If(Value(lblColorDifference.Text) &amp;lt; 500 Or Value(lblBrightnessDifference.Text) &amp;lt;125, &amp;quot;failed color contrast check according to W3.org&amp;quot;, &amp;quot;passed color contrast check according to W3.org&amp;quot;)&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="update-v11-of-the-color-contrast-checker-is-now-available-on-github">Update: v1.1 of the color contrast checker is now available on GitHub&lt;/h2>
&lt;p>I worked on additional features:&lt;/p>
&lt;ul>
&lt;li>display the hex color value of an RGBA value&lt;/li>
&lt;li>have Textinput fields for accurate input of RGBA values&lt;/li>
&lt;/ul>
&lt;h3 id="hexcolor---regex-to-the-rescue">hexcolor - Regex to the rescue&lt;/h3>
&lt;p>Unfortunately, there is no easy built-in function to convert an RGBA value into a hex color value in Power Apps, still it is doable! I wrote in a separate post on &lt;a href="https://m365princess.com/hex-color-control-power-apps">how to get hex-color value in Power Apps&lt;/a>&lt;/p>
&lt;h3 id="input-fields">Input fields&lt;/h3>
&lt;ul>
&lt;li>Create an input field for every slider, I will use the &lt;code>sli_Red&lt;/code> as an example:&lt;/li>
&lt;li>Set the &lt;strong>OnChange&lt;/strong> of the input field to&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-Power" data-lang="Power">If(
Value(Self.Text) &amp;gt; 255 || Value(Self.Text) &amp;lt; 0,
Notify(
&amp;#34;please enter a value between 0 and 255&amp;#34;,
NotificationType.Error
)
);
Set(
defaultRed,
Value(Self.Text)
);
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Set the &lt;strong>Default&lt;/strong> property of the input field to &lt;code>sli_Red.Value&lt;/code>&lt;/li>
&lt;li>Set the &lt;strong>Default&lt;/strong> property of the slider &lt;code>defaultRed&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The result can look something like this:&lt;/p>
&lt;p>&lt;img alt="v1.1 of color contrast ratio checker for Power Apps" src="https://m365princess.com/images/colorcontrastratiochecker11.png">&lt;/p>
&lt;p>You can find the updated source code and solution &lt;a href="https://github.com/LuiseFreese/powerapps-samples/tree/main/samples/accessibility-color-contrast-checker">in my personal GitHub account&lt;/a>, a PR to &lt;a href="https://github.com/pnp/powerapps-samples">PNP/powerapps-samples&lt;/a> is pending, I will update accordingly once this is processed.&lt;/p>
&lt;h2 id="what-are-your-thoughts">What are your thoughts?&lt;/h2>
&lt;p>How do you improve color contrast ratio in your apps and make sure that you comply to WCAG 2.1 Success Criteria? &lt;a href="https://twitter.com/LuiseFreese/status/1543608968551043073">Let me know on twitter&lt;/a>.&lt;/p></description></item><item><title>Get insights about Power Apps usage with Microsoft Clarity PCF code component</title><link>https://m365princess.com/blogs/power-apps-microsoft-clarity-pcf/</link><pubDate>Tue, 21 Jun 2022 11:00:28 +0000</pubDate><guid>https://m365princess.com/blogs/power-apps-microsoft-clarity-pcf/</guid><description>&lt;p>Getting insights on user behavior in Power Apps canvas apps can be in various ways - for instance, you can &lt;a href="https://docs.microsoft.com/power-apps/maker/canvas-apps/application-insights">analyze app telemetry with Azure Application Insights&lt;/a> and trace events. If you are more interested in visual heatmaps and session recordings, you might already have had a look at &lt;a href="https://clarity.microsoft.com/">Microsoft Clarity&lt;/a>, which exactly provides that:&lt;/p>
&lt;h2 id="what-is-microsoft-clarity-and-how-do-i-connect-it-to-a-power-apps-canvas-app">What is Microsoft Clarity and how do I connect it to a Power Apps canvas app?&lt;/h2>
&lt;p>Clarity is a user behavior analytics tool that helps you understand how users are interacting with a website through features such as session replays and heatmaps.
It is installed by adding a tracking code into the &lt;code>&amp;lt;head&amp;gt;&lt;/code> section of the website.&lt;/p>
&lt;p>As a Power Apps canvas app running in the browser is also still just a website, we only need to find a way on how to inject the Clarity JavaScript script into the head of our canvas app.&lt;/p>
&lt;p>Unfortunately, there is no connector for Microsoft Clarity, but we can leverage the Power Apps component framework for that.&lt;/p>
&lt;h2 id="power-apps-component-framework-to-the-rescue">Power Apps component framework to the rescue&lt;/h2>
&lt;p>With &lt;a href="https://docs.microsoft.com/power-apps/developer/component-framework/overview">Power Apps component framework&lt;/a> we can create code components for Power Apps. I created a code component for Microsoft Clarity, so that we can use Clarity in our canvas apps.&lt;/p>
&lt;h3 id="high-level-overview">High level overview&lt;/h3>
&lt;p>To give you an idea on how to create this, here is high level overview- it&amp;rsquo;s not meant to replace extensive learning with very detailed step-by-step instructions though. I will link to resources though if this is your first code component - it was my first :-)&lt;/p>
&lt;ul>
&lt;li>Sign up for a free Clarity account&lt;/li>
&lt;li>Set up your project&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="new Clarity project" src="https://m365princess.com/images/Clarity-new.png">&lt;/p>
&lt;ul>
&lt;li>Use Power Platform CLI to &lt;a href="https://docs.microsoft.com/power-apps/developer/component-framework/create-custom-controls-using-pcf">create the code component&lt;/a>&lt;/li>
&lt;li>In the scaffolded project, locate &lt;code>ControlManifest.Input.xml&lt;/code> and &lt;code>index.ts&lt;/code>&lt;/li>
&lt;li>Update the &lt;code>ControlManifest.Input.xml&lt;/code> file to&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34; ?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;manifest&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;control&lt;/span> &lt;span class="na">namespace=&lt;/span>&lt;span class="s">&amp;#34;ClarityNameSpace&amp;#34;&lt;/span> &lt;span class="na">constructor=&lt;/span>&lt;span class="s">&amp;#34;ClarityCodeSnippet&amp;#34;&lt;/span> &lt;span class="na">version=&lt;/span>&lt;span class="s">&amp;#34;1.0.0&amp;#34;&lt;/span> &lt;span class="na">display-name-key=&lt;/span>&lt;span class="s">&amp;#34;ClarityLovesPowerApps&amp;#34;&lt;/span> &lt;span class="na">description-key=&lt;/span>&lt;span class="s">&amp;#34;Clarity Code Component to analyze user behavior&amp;#34;&lt;/span> &lt;span class="na">control-type=&lt;/span>&lt;span class="s">&amp;#34;standard&amp;#34;&lt;/span> &lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;property&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;YourClarityProjectID&amp;#34;&lt;/span> &lt;span class="na">display-name-key=&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;your-clarity-project-id&amp;gt;&amp;#34;&lt;/span> &lt;span class="na">description-key=&lt;/span>&lt;span class="s">&amp;#34;description&amp;#34;&lt;/span> &lt;span class="na">of-type=&lt;/span>&lt;span class="s">&amp;#34;SingleLine.Text&amp;#34;&lt;/span> &lt;span class="na">usage=&lt;/span>&lt;span class="s">&amp;#34;bound&amp;#34;&lt;/span> &lt;span class="na">required=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;external-service-usage&lt;/span> &lt;span class="na">enabled=&lt;/span>&lt;span class="s">&amp;#34;false&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/external-service-usage&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;code&lt;/span> &lt;span class="na">path=&lt;/span>&lt;span class="s">&amp;#34;index.ts&amp;#34;&lt;/span> &lt;span class="na">order=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/control&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/manifest&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>In the index.ts file, we first need a &lt;code>private _Container: HTMLDivElement;&lt;/code> above the constructor&lt;/li>
&lt;li>then add the following code to the &lt;code>init method&lt;/code>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">public&lt;/span> &lt;span class="nx">init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>: &lt;span class="kt">ComponentFramework.Context&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">IInputs&lt;/span>&lt;span class="p">&amp;gt;,&lt;/span> &lt;span class="nx">notifyOutputChanged&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">void&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">state&lt;/span>: &lt;span class="kt">ComponentFramework.Dictionary&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">container&lt;/span>:&lt;span class="kt">HTMLDivElement&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">head&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementsByTagName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;head&amp;#39;&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">script&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;script&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">script&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};t=l.createElement(r);t.async=1;t.src=&amp;#34;https://www.clarity.ms/tag/&amp;#34;+i;y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);})(window, document, &amp;#34;clarity&amp;#34;, &amp;#34;script&amp;#34;, &amp;#34;&amp;lt;your clarity project id&amp;gt;&amp;#34;);&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">head&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">insertBefore&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">script&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">head&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">firstChild&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">textDiv&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;div&amp;gt;We are tracking this app with Microsoft clarity, have a lovely day!&amp;lt;/div&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">_Container&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">_Container&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ClarityDivID&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">_Container&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">textDiv&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">container&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">appendChild&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">_Container&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;a href="https://docs.microsoft.com/power-apps/developer/component-framework/import-custom-controls">Package&lt;/a>and &lt;a href="https://docs.microsoft.com/power-apps/developer/component-framework/create-custom-controls-using-pcf#build-your-component">Build&lt;/a> your component&lt;/li>
&lt;li>Import your code component solution&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/power-apps/developer/component-framework/component-framework-for-canvas-apps#add-components-to-a-canvas-app">Add your code component into a Canvas App&lt;/a>&lt;/li>
&lt;li>Don&amp;rsquo;t forget to (re-) publish your app ;-)&lt;/li>
&lt;/ul>
&lt;p>When you now use the app, you can open Edge Dev tools on the Network tab and see the work Clarity is doing:&lt;/p>
&lt;p>&lt;img alt="Clarity in Edge Dev tools" src="https://m365princess.com/images/Clarity-Devtools.png">&lt;/p>
&lt;h3 id="how-does-this-now-look-like-in-our-canvas-app">How does this now look like in our Canvas app?&lt;/h3>
&lt;p>In your Canvas app, you can either style your code component or you can set the &lt;strong>Visible&lt;/strong> property to &lt;code>false&lt;/code> to hide it.&lt;/p>
&lt;p>&lt;img alt="Clarity properties" src="https://m365princess.com/images/Clarity_properties.png">&lt;/p>
&lt;p>It takes a decent while (I guess about 90 minutes) for data to arrive for the first time in Clarity, but after that it runs smoothly - here is how the dashboard then looks like:&lt;/p>
&lt;p>&lt;img alt="Clarity dashboard" src="https://m365princess.com/images/Clarity_dashboard.png">&lt;/p>
&lt;p>If we now want get more into the details of users are interacting with our apps (sometimes easier than asking them), we can watch a replay of the session recordings:&lt;/p>
&lt;p>&lt;img alt="Clarity recording" src="https://m365princess.com/images/Clarity_sessionrecording.png">&lt;/p>
&lt;p>This gives us information on where users click, scroll and type.&lt;/p>
&lt;p>Heatmaps are a valuable feature to determine which parts of an app are most selected:&lt;/p>
&lt;p>&lt;img alt="Clarity heatmap" src="https://m365princess.com/images/Clarity_heatmap.png">&lt;/p>
&lt;h2 id="where-to-find-the-component">Where to find the component&lt;/h2>
&lt;p>You can find this component on &lt;a href="https://github.com/LuiseFreese/ClarityLovesPowerApps">my GitHub&lt;/a> - Let me know if you have questions!&lt;/p></description></item><item><title>How to rename files in SharePoint with Power Automate</title><link>https://m365princess.com/blogs/rename-files-sharepoint-power-automate/</link><pubDate>Wed, 27 Apr 2022 10:48:16 +0000</pubDate><guid>https://m365princess.com/blogs/rename-files-sharepoint-power-automate/</guid><description>&lt;h2 id="the-issue">the issue&lt;/h2>
&lt;p>Although SharePoint and Power Automate are heavily interwoven, there is no &amp;ldquo;rename file&amp;rdquo; action in Power Automate - if the file lives in a SharePoint library. Out of the box that is only possible for files living in OneDrive.&lt;/p>
&lt;p>I recently saw a cry for help on &lt;a href="https://twitter.com/_achu/status/1518786319907901442?s=20&amp;t=XHVS1oXjKIC_cksmztVWDg">twitter&lt;/a> and decided to share here how my solution looks like.&lt;/p>
&lt;p>In order to change a filename, we will need to use SharePoint REST API, which is accessible to us in Power Automate via the &lt;strong>Send an HTTP request to SharePoint&lt;/strong> action.&lt;/p>
&lt;h2 id="the-power-automate-flow">the Power Automate flow&lt;/h2>
&lt;p>&lt;img alt="Power Automate flow to rename a file" src="https://m365princess.com/images/PowerAutomate-renameFile.png">&lt;/p>
&lt;ol>
&lt;li>
&lt;p>To make things easy, we start with a &lt;strong>For selected file&lt;/strong> trigger. (In a real-world scenario, you&amp;rsquo;d typically rename a file after something else has happened.)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>We then &lt;strong>Get file properties&lt;/strong> (not needed as we already have the &lt;code>ID&lt;/code> from the &lt;strong>For selected file&lt;/strong> trigger, but if you run this after another action you will need the file properties to get the &lt;code>ID&lt;/code> so that you can rename the right file).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>We now need to use the &lt;strong>Send an HTTP request to SharePoint&lt;/strong> action:&lt;/p>
&lt;ul>
&lt;li>Site Adress: choose from Dropdown&lt;/li>
&lt;li>Method: &lt;code>Get&lt;/code>&lt;/li>
&lt;li>Uri: &lt;code>_api/lists/getbytitle('&amp;lt;DisplayName of your Library goes here&amp;gt;')&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>This will re turn the &lt;code>ListItemEntityTypeFullName&lt;/code> property, which we will need for the next step.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>As a last step, use another &lt;strong>Send an HTTP request to SharePoint&lt;/strong> action:&lt;/p>
&lt;ul>
&lt;li>Site Adress: choose from Dropdown&lt;/li>
&lt;li>Method: &lt;code>Post&lt;/code>&lt;/li>
&lt;li>Uri: &lt;code>_api/lists/GetByTitle('&amp;lt;DisplayName of your Library goes here&amp;gt;')/Items(@{outputs('Get_file_properties')?['body/ID']})&lt;/code>&lt;/li>
&lt;li>Headers:
&lt;ul>
&lt;li>&lt;strong>Content-type&lt;/strong>: &lt;code>application/json&lt;/code>&lt;/li>
&lt;li>&lt;strong>IF-MATCH&lt;/strong>: &lt;code>*&lt;/code>&lt;/li>
&lt;li>&lt;strong>X-HTTP-METHOD&lt;/strong>: &lt;code>PATCH&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Body:&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>{&amp;#39;__metadata&amp;#39;:
{&amp;#39;type&amp;#39;:&amp;#39;@{outputs(&amp;#39;Send_an_HTTP_request_to_SharePoint_to_get_type&amp;#39;)?[&amp;#39;body&amp;#39;]?[&amp;#39;d&amp;#39;]?[&amp;#39;ListItemEntityTypeFullName&amp;#39;]}&amp;#39;},
&amp;#39;FileLeafRef&amp;#39;:&amp;#39;&amp;lt;your new filename goes here&amp;gt;&amp;#39;}
&lt;/code>&lt;/pre>&lt;blockquote>
&lt;p>For the type, we are selecting the &lt;code>ListItemEntityTypeFullName&lt;/code> property from the output of the previous action. You could also use &lt;strong>Parse JSON&lt;/strong> action to obtain that value.&lt;/p>
&lt;/blockquote>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Once again, the &lt;strong>Send an HTTP request to SharePoint&lt;/strong> action in Power Automate is a lifesaver for everything that doesn&amp;rsquo;t work with the built-in actions. Also, no additional license than your Microsoft 365 license is required to use it.&lt;/p>
&lt;h2 id="resources">Resources&lt;/h2>
&lt;p>To learn more about SharePoint REST, use these resources:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.microsoft.com/sharepoint/dev/sp-add-ins/get-to-know-the-sharepoint-rest-service?tabs=csom">Get to know the SharePoint REST service&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/working-with-lists-and-list-items-with-rest">Working with lists and list items with REST&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="feedback--whats-next">Feedback &amp;amp; what&amp;rsquo;s next?&lt;/h2>
&lt;p>I&amp;rsquo;d love to know what are your renaming-scenarios and also what you use the Send an HTTP request to SharePoint action for! Let me know on &lt;a href="https://twitter.com/LuiseFreese/status/1519299770141089795">twitter&lt;/a>.&lt;/p></description></item><item><title>how to use oh-my-posh with PnP PowerShell</title><link>https://m365princess.com/blogs/ohmyposh-pnp-powershell/</link><pubDate>Sun, 17 Apr 2022 07:53:14 +0000</pubDate><guid>https://m365princess.com/blogs/ohmyposh-pnp-powershell/</guid><description>&lt;h2 id="what-is-oh-my-posh">What is oh my posh?&lt;/h2>
&lt;p>&lt;a href="https://ohmyposh.dev/">Oh-my-posh&lt;/a> is an amazing prompt engine that does not only pretty up the terminal you use, but it will ease your work. By using an &lt;a href="https://ohmyposh.dev/docs/themes">established theme&lt;/a> or creating a new one, you get important information directly in the context of your work, highlighted in the way that works best for you. For example:&lt;/p>
&lt;ul>
&lt;li>In which repository am I working in?&lt;/li>
&lt;li>Which branch?&lt;/li>
&lt;li>When did I execute a command?&lt;/li>
&lt;li>On which node.js version am I working?&lt;/li>
&lt;/ul>
&lt;h3 id="the-m365princess-theme-by-luise-plus-environment-variables-by-anoop">the M365Princess theme by Luise plus environment variables by Anoop&lt;/h3>
&lt;p>Making good things even better: A while ago, &lt;a href="https://twitter.com/LuiseFreese">Luise&lt;/a> created her own theme and now &lt;a href="https://twitter.com/anooptells">Anoop&lt;/a> had the idea to level it up:&lt;/p>
&lt;p>When interacting with Microsoft 365, most of the times we use the Microsoft 365 platform community driven PowerShell module called &lt;a href="https://pnp.github.io/powershell/">&lt;strong>PnP PowerShell&lt;/strong>&lt;/a>. One of the first commands we execute while using PnP PowerShell, is &lt;code>Connect-PnPOnline&lt;/code> to connect to a SharePoint site. We can now see right in the terminal which SharePoint site we are connected to and which Microsoft 365 tenant the SharePoint site lives in.&lt;/p>
&lt;p>These are displayed with the help of a couple of environment variables that are set by PnP PowerShell after running the &lt;code>Connect-PnPOnline&lt;/code> command.&lt;/p>
&lt;p>In the preview below, the connected SharePoint site is &lt;code>/sites/yoursite&lt;/code> which is in the tenant named &lt;code>yourtenant.sharepoint.com&lt;/code>&lt;/p>
&lt;p>&lt;img alt="oh my posh M365Princess theme PnP" src="https://m365princess.com/images/oh-my-posh.png">&lt;/p>
&lt;p>The original idea to show these values comes from &lt;a href="https://twitter.com/erwinvnhunen">Erwin van Hunen&lt;/a>, the father of PnP PowerShell.&lt;/p>
&lt;h2 id="how-to-install-oh-my-posh-and-the-theme">How to install oh-my-posh and the theme&lt;/h2>
&lt;p>To have this experience in Visual Studio Code, complete the following steps:&lt;/p>
&lt;ol>
&lt;li>Install oh-my-posh and posh-git
&lt;ul>
&lt;li>Open the the terminal and run
&lt;ul>
&lt;li>&lt;code>Install-Module oh-my-posh -Scope CurrentUser&lt;/code>&lt;/li>
&lt;li>&lt;code>Install-Module posh-git -Scope CurrentUser&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Edit your PowerShell profile in VS Code
&lt;ul>
&lt;li>Open it with &lt;code>code $PROFILE&lt;/code>&lt;/li>
&lt;li>Insert this:&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ps1" data-lang="ps1">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Import-Module&lt;/span> &lt;span class="nb">oh-my&lt;/span>&lt;span class="n">-posh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Import-Module&lt;/span> &lt;span class="nb">posh-git&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Set-PoshPrompt&lt;/span> &lt;span class="n">-Theme&lt;/span> &lt;span class="n">M365Princess&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>Install a font
&lt;ul>
&lt;li>Download a font of your choice from &lt;a href="https://www.nerdfonts.com/font-downloads">Nerdfonts&lt;/a>&lt;/li>
&lt;li>Install a monospace version of that font&lt;/li>
&lt;li>Open settings in VS Code with CTRL + SHIFT + P and select &lt;strong>Preferences: Open settings (JSON)&lt;/strong>&lt;/li>
&lt;li>Add (for example) &lt;code>&amp;quot;terminal.integrated.fontFamily&amp;quot;: &amp;quot;CaskaydiaCove Nerd Font&amp;quot;&lt;/code>&lt;/li>
&lt;li>Save settings and restart VS Code&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="slow-prompt">Slow prompt?&lt;/h3>
&lt;p>In case the prompt is responding slow on Windows OS, then one of the reasons might be the oh-my-posh process being blocked by Windows defender. To overcome that, please run PowerShell with elevated permissions (Run as administrator) and execute the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ps1" data-lang="ps1">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Add-MpPreference&lt;/span> &lt;span class="n">-ExclusionProcess&lt;/span> &lt;span class="s2">&amp;#34;oh-my-posh.exe&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If that doesn&amp;rsquo;t solve the problem then, execute the following command&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ps1" data-lang="ps1">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Add-MpPreference&lt;/span> &lt;span class="n">-ExclusionPath&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:POSH_PATH&lt;/span>&lt;span class="s2">\oh-my-posh.exe&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For more information see the comments &lt;a href="https://github.com/JanDeDobbeleer/oh-my-posh/issues/1904">this issue&lt;/a>.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>It&amp;rsquo;s the little things! Of course this is just one puzzle tile, but better overview, crucial information presented in a minimal but nerdy and compelling way helps people to ease their workloads. When working with PnP PowerShell, it&amp;rsquo;s a game changer to know to which of your tenants and sites you are connected to.&lt;/p>
&lt;p>If you like the oh-my-posh and can afford it, go buy Jan, who builds and maintains this awesome open source project, a coffee ☕: &lt;a href="https://github.com/JanDeDobbeleer/oh-my-posh">oh-my-posh repository&lt;/a>&lt;/p>
&lt;p>&lt;em>sharing is caring&lt;/em>&lt;/p>
&lt;p>&lt;em>This post was written by Luise Freese and Anoop Tatti&lt;/em>&lt;/p></description></item><item><title>How to query Azure Monitor Log Analytics in Logic Apps with a Managed Identity and output results in a SharePoint list</title><link>https://m365princess.com/blogs/query-azure-monitor/</link><pubDate>Wed, 13 Apr 2022 12:30:36 +0000</pubDate><guid>https://m365princess.com/blogs/query-azure-monitor/</guid><description>&lt;p>&lt;a href="https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-workspace-overview">Azure Monitor Log Analytics&lt;/a> is super powerful to collect data and give you insights on what&amp;rsquo;s going on with your apps and resources. There is even an Azure Monitor Logs connector for Logic Apps.&lt;/p>
&lt;p>⚡ Very unfortunately, the connector doesn&amp;rsquo;t support authentication with a Managed Identity - which means that you&amp;rsquo;d need either to be a signed-in user or use an app registration, which comes with its own challenges on secret handling. I explained &lt;a href="https://www.m365princess.com/blogs/rid-key-vault-making-good/">in my previous blog posts&lt;/a> why Managed Identities are a way superior way to authenticate.&lt;/p>
&lt;p>&lt;img alt="Azure Monitor connector" src="https://m365princess.com/images/azuremonitorconnector.png">&lt;/p>
&lt;blockquote>
&lt;p>💡 Azure Log Analytics REST API to the rescue!&lt;/p>
&lt;/blockquote>
&lt;p>We can call the &lt;a href="https://docs.microsoft.com/en-us/rest/api/loganalytics/">Azure Log Analytics REST API&lt;/a> from Logic Apps with the generic HTTP action - and authenticate with a Managed Identity. For this example, I chose a user-assigned Managed Identity, so that you can reuse it across resources, but of course you can go with a system-assigned Managed Identity as well.&lt;/p>
&lt;p>I wanted to create a sample on how to create a Logic App that queries Log Analytics with a user-assigned Managed Identity that has the &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#log-analytics-reader">Log Analytics Reader&lt;/a> assigned. The Managed Identity also has Microsoft Graph permissions with scope &lt;code>Sites.ReadWrite.All&lt;/code> assigned in order to create new items in a SharePoint list about the result of a query of Azure Monitor logs. That could be beneficial for users who don&amp;rsquo;t have access to the logs but should be notified or similar.&lt;/p>
&lt;h2 id="deployment">Deployment&lt;/h2>
&lt;ol>
&lt;li>Fork and clone &lt;a href="https://github.com/LuiseFreese/AzureLogAnalytics">this repository&lt;/a>&lt;/li>
&lt;li>Browse to the root folder in your clone&lt;/li>
&lt;li>Connect to the Azure subscription that you want this to run in&lt;/li>
&lt;li>Run the &lt;code>deploy.ps1&lt;/code> script, it will
&lt;ul>
&lt;li>create the resource group&lt;/li>
&lt;li>create the resources&lt;/li>
&lt;li>assign the &lt;code>Log Analytics&lt;/code> role to the Managed Identity&lt;/li>
&lt;li>assign Graph API &lt;code>Sites.ReadWrite.All&lt;/code> permissions to the Managed Identity&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>You will be prompted to provide
&lt;ul>
&lt;li>a location for your resources&lt;/li>
&lt;li>a Resource group name&lt;/li>
&lt;li>the &lt;code>siteID&lt;/code> of the site your list lives in&lt;/li>
&lt;li>the &lt;code>listId&lt;/code> of the list in which new rows shall be created&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="validate-deployment">Validate deployment&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>Check in the Azure Portal after you read the success-message &lt;code>Resources deployed successfully, role assigned&lt;/code>&lt;/p>
&lt;ul>
&lt;li>Resource group&lt;/li>
&lt;li>Logic App&lt;/li>
&lt;li>Azure Log Workspace&lt;/li>
&lt;li>Managed Identity&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="resource group" src="https://m365princess.com/images/resource-group.png">&lt;/p>
&lt;ol>
&lt;li>
&lt;p>In the Logic App, the Authentication works with the Managed Identity
&lt;img alt="LogicApps-auth" src="https://m365princess.com/images/logicapp-auth.png">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Our Managed Identity has the &lt;code>Log Analytics Reader&lt;/code> role assigned
&lt;img alt="Managed Identity" src="https://m365princess.com/images/ManagedIdentity-role.png">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Our Managed Identity has the &lt;code>SitesReadWrite.All&lt;/code> permission assigned
&lt;img alt="Graph permissions" src="https://m365princess.com/images/ManagedIdentity-permissions.png">&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Our Logic App should look like this:&lt;/p>
&lt;p>&lt;img alt="complete Logic App" src="https://m365princess.com/images/LogicApp-full.png">&lt;/p>
&lt;p>💡 Please make sure that you insert a query into the Logic app that makes sense depending on your scenario. Also adjust the &lt;code>body&lt;/code> of the &lt;code>Create list item&lt;/code> action to match your SharePoint list.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Connectors can be a good way to easily achieve what you need in Logic Apps. However, they sometimes don&amp;rsquo;t fit your scenario or don&amp;rsquo;t support your way of doing things - like I really like Managed Identities. Therefore, its always worth a try to call the API directly so that you do not need to rely on the wrapper.&lt;/p>
&lt;h2 id="questions">Questions?&lt;/h2>
&lt;p>If you have questions, want to use this, make it better - please do! Find me either on &lt;a href="https://twitter.com/LuiseFreese">twitter&lt;/a> or on &lt;a href="https://github.com/luisefreese">GitHub&lt;/a>&lt;/p>
&lt;p>&lt;em>sharing is caring&lt;/em>&lt;/p></description></item><item><title>How to get started with deploying Azure resources with Bicep</title><link>https://m365princess.com/blogs/start-deploying-azure-resources-bicep/</link><pubDate>Wed, 09 Mar 2022 18:30:05 +0000</pubDate><guid>https://m365princess.com/blogs/start-deploying-azure-resources-bicep/</guid><description>&lt;h2 id="what-is-bicep">What is Bicep?&lt;/h2>
&lt;p>Good question. First of all, it&amp;rsquo;s most probably Azure&amp;rsquo;s nerdiest dad joke, as it derives from ARM (Azure Resource Manager) and has something to do with the biceps doing the heavy lifting/provides extra power 💪.&lt;/p>
&lt;p>Bicep is a language specific to Azure and is used to provide Infrastructure-as-Code in an easy-to-author way. Syntax is much simpler than regular ARM templates and this results in more readable files.&lt;/p>
&lt;p>This sample shows how to deploy Azure Cognitive services.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">serviceName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;cognitive-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nf">uniqueString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">resourceGroup&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s">&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">resourceGroup&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">sku&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;S0&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">cognitiveService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.CognitiveServices/accounts@2017-04-18&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">serviceName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">sku&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">sku&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;CognitiveServices&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you compare this to the JSON definition, you will notice&lt;/p>
&lt;ul>
&lt;li>less curly brackets&lt;/li>
&lt;li>less quotation marks&lt;/li>
&lt;li>less lines of code&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JSON" data-lang="JSON">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;$schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;contentVersion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;metadata&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;_generator&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;bicep&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0.4.1008.15138&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;templateHash&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;3830258995596078&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;parameters&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;serviceName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;defaultValue&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;[format(&amp;#39;cognitive-{0}&amp;#39;, uniqueString(resourceGroup().id))]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;location&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;defaultValue&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;[resourceGroup().location]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;sku&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;defaultValue&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;S0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;functions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;resources&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Microsoft.CognitiveServices/accounts&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;apiVersion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2017-04-18&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;[parameters(&amp;#39;serviceName&amp;#39;)]&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;location&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;sku&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;[parameters(&amp;#39;sku&amp;#39;)]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;kind&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;CognitiveServices&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Time to play! If you like to familiarize yourself with Bicep, you can use the &lt;a href="https://azure.github.io/bicep/">Bicep Playground&lt;/a> - it&amp;rsquo;s an interactive experience that lets you explore and try out Bicep - similar to &lt;a href="https://mgt.dev/">MGT Playground&lt;/a>, &lt;a href="https://developer.microsoft.com/graph/graph-explorer">Graph Explorer&lt;/a> or &lt;a href="https://adaptivecards.io/designer/">Adaptive Cards Designer&lt;/a> in a safe space where you can&amp;rsquo;t break anything. (I took the example from that Playground)&lt;/p>
&lt;p>&lt;img alt="Bicep Playground" src="https://m365princess.com/images/bicep-playground.png">&lt;/p>
&lt;h2 id="how-do-you-create-a-bicep-template-from-an-azure-resource">How do you create a Bicep template from an Azure resource?&lt;/h2>
&lt;p>In order to create a deployable Bicep file, we will need to use some tools. I will work on Windows and with Azure CLI, but you can of course choose Azure PowerShell as well or work on Linux or Mac.&lt;/p>
&lt;h3 id="tools">Tools&lt;/h3>
&lt;ul>
&lt;li>In &lt;a href="https://code.visualstudio.com/">Visual Studio Code&lt;/a> (VS Code), install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep">Bicep extension&lt;/a>. (Most probably you will need to restart VS Code after installing the extension.)&lt;/li>
&lt;li>Install &lt;a href="https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli">Azure CLI&lt;/a> - You can validate which version you have installed when you run &lt;code>az --version&lt;/code> in your terminal.&lt;/li>
&lt;li>Install &lt;strong>Bicep CLI&lt;/strong> in terminal by running &lt;code>az bicep install&lt;/code>. If you did that already a while ago, it&amp;rsquo;s a good idea to upgrade to the latest version with &lt;code>az bicep upgrade&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="get-the-arm-template">Get the ARM template&lt;/h3>
&lt;p>You &lt;em>could&lt;/em> of course write the entire definition of your resources from scratch (and with the extension installed you get Intellisense, which is really convenient), but as you probably already built resources, you can go to the Azure portal and export the ARM template:&lt;/p>
&lt;ol>
&lt;li>Open the &lt;a href="https://portal.azure.com">Azure portal&lt;/a>&lt;/li>
&lt;li>Select the resource group&lt;/li>
&lt;li>If you want to export a template for the entire resource group including all resources
&lt;ul>
&lt;li>select &lt;strong>Export template&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>If you want to export only the template for a particular resource
&lt;ul>
&lt;li>select the resource&lt;/li>
&lt;li>select &lt;strong>Export template&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Extract the downloaded &lt;code>.zip&lt;/code> file&lt;/li>
&lt;li>Open the extracted &lt;code>template.json&lt;/code> file in VS Code&lt;/li>
&lt;/ol>
&lt;h3 id="decompile">Decompile&lt;/h3>
&lt;ol>
&lt;li>Open the terminal&lt;/li>
&lt;li>Navigate to the folder where your template.json file sits&lt;/li>
&lt;li>Run &lt;code>az bicep decompile --file template.json&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>This will create a new file &lt;code>template.bicep&lt;/code>. To make this template file better, we will do a few things:&lt;/p>
&lt;h3 id="modules">Modules&lt;/h3>
&lt;p>If you want to deploy more than one resource, you will end up with a very lengthy file, which makes it hard to gain overview - also collaboration and debugging is hard with that. Luckily, Bicep knows a concept that is called modules, which are also Bicep files that can be deployed from a root Bicep file. You can even &lt;a href="https://docs.microsoft.com/azure/azure-resource-manager/bicep/private-module-registry?tabs=azure-powershell">share modules&lt;/a> for reusing modules in your organization.&lt;/p>
&lt;p>This is how we do it:&lt;/p>
&lt;ol>
&lt;li>Select the resource in the template.bicep file&lt;/li>
&lt;li>Cut it and paste it into a new Bicep file (e.g. &lt;code>My-managedIdentity.bicep&lt;/code>)&lt;/li>
&lt;li>Repeat this with the other resources as well&lt;/li>
&lt;li>Now create modules in &lt;code>template.bicep&lt;/code> like this:&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityDeployment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;My-managedIdentity.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;managedIdentityDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_My_Identity_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_My_Identity_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Make sure that you declare the parameters in the file as well. Repeat this until have a module for each resource that is defined in a corresponding Bicep file.&lt;/p>
&lt;h3 id="few-tweaks-and-quirks">Few tweaks and quirks&lt;/h3>
&lt;p>If - in your exported template you had hard coded values that you still want to get rid of - this is a good time to do that:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">workflows_MyWorkflow_name_resource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Microsoft.Logic/workflows@2017-07-01&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">workflows_MyWorkflow_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">location&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;westeurope&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">identity&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;UserAssigned&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;/subscriptions/fdf0XXX-0726-404c-XXX-23d183XXX/resourceGroups/MyResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/My-ManagedIdentity&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We would replace the hard coded value of the userAssignedIdentities that contains our Subscription Id with&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nf">resourceId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Microsoft.ManagedIdentity/userAssignedIdentities/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nv">userAssignedIdentities_My_Identity_name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s">&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also, we would replace the hard coded value &lt;code>'westeurope'&lt;/code> with a parameter.&lt;/p>
&lt;p>Sometimes when decompiling, we don&amp;rsquo;t get the right API version - in this case we got &lt;code>2017-07-01&lt;/code> - but in fact &lt;code>2019-05-01&lt;/code> is correct. How would we know? Bicep extension warns us with yellow squiggly lines :-)&lt;/p>
&lt;p>&lt;img alt="Bicep warning" src="https://m365princess.com/images/bicep-warning.png">&lt;/p>
&lt;h3 id="deploy-with-azure-cli">Deploy with Azure CLI&lt;/h3>
&lt;p>Now let&amp;rsquo;s deploy this to Azure! Again, we will be using Azure CLI&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bicep" data-lang="Bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="err">$&lt;/span>&lt;span class="nv">DeployTimestamp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">Get&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nv">Date&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">ToUniversalTime&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">ToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="nv">yyyyMMdTHmZ&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nv">az&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">deployment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">group&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="nv">DeployLinkedTemplate&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="nv">DeployTimestamp&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="kd">resource&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nv">group&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">$&lt;/span>&lt;span class="nv">ResourceGroupName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="nv">template&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nv">file&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">path&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nv">to&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nv">template&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nv">bicep&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">`&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="nv">verbose&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it 🚀&lt;/p>
&lt;h2 id="what-do-you-think">What do you think?&lt;/h2>
&lt;p>&lt;a href="https://twitter.com/LuiseFreese/status/1501632661084950532">Let me know on twitter&lt;/a>&lt;/p></description></item><item><title>How to be better at twitter - part 2</title><link>https://m365princess.com/blogs/twitter-part-2/</link><pubDate>Wed, 09 Mar 2022 10:35:59 +0000</pubDate><guid>https://m365princess.com/blogs/twitter-part-2/</guid><description>&lt;p>In one of my last blog posts, I covered &lt;a href="https://www.m365princess.com/blogs/twitter">How to be better at twitter&lt;/a>, sharing some insight about tools and when to mention people and I got a lot of good feedback for that, which is why I want to share some more tips here in part 2.&lt;/p>
&lt;h2 id="de-clutter-your-feed">De-clutter your feed&lt;/h2>
&lt;p>Are you following lots of people? Like, hundreds, or even thousands? Then most probably, your feed looks like this&lt;/p>
&lt;ul>
&lt;li>tweet of a person you follow&lt;/li>
&lt;li>advertisement&lt;/li>
&lt;li>reply to a tweet of a person you follow&lt;/li>
&lt;li>tweet of which twitter thinks it is interesting for you&lt;/li>
&lt;li>tweet of a person you follow&lt;/li>
&lt;li>tweet that is liked by a person you follow&lt;/li>
&lt;li>tweet by a person a lot of accounts you follow follow&lt;/li>
&lt;/ul>
&lt;p>If you are bit like me, not all of that is relevant to you. If you use the twitter web app, type into the search bar: &lt;code>filter:follows -filter:replies&lt;/code> - which means that you only get tweets by accounts you follow, but not the replies into your feed. This of course does not mean that you can&amp;rsquo;t read replies.&lt;/p>
&lt;p>You can now save this search&lt;/p>
&lt;p>&lt;img alt="save twitter search" src="https://m365princess.com/images/twitter-savesearch.png">&lt;/p>
&lt;p>Alternative: type in this as the URL&lt;/p>
&lt;p>&lt;code>https://twitter.com/search?q=filter%3Afollows%20-filter%3Areplies&amp;amp;src=saved_search_click&lt;/code>&lt;/p>
&lt;p>or&lt;/p>
&lt;p>&lt;code>https://twitter.com/search?q=filter%3Afollows%20-filter%3Areplies&amp;amp;src=saved_search_click&amp;amp;f=live&lt;/code>&lt;/p>
&lt;p>if you want the latest tweets on top.&lt;/p>
&lt;h2 id="when-to-use-what">When to use what&lt;/h2>
&lt;p>Do you know when you should like, reply, retweet or quote-retweet (QT)?&lt;/p>
&lt;p>The official consultant answer of course it - &lt;em>it depends&lt;/em>&lt;/p>
&lt;p>&lt;img alt="it depends" src="https://m365princess.com/images/itdepends.png">
&lt;em>(sticker by &lt;a href="https://pimpyourowndevice.com/stickers/it-depends/">PimpYourOwnDevice.com&lt;/a>&lt;/em>)&lt;/p>
&lt;blockquote>
&lt;p>💡 &lt;strong>tl;dr: replies have the biggest impact&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;p>Lets explore our motivation and what we want to achieve:&lt;/p>
&lt;h3 id="like--support">like = support&lt;/h3>
&lt;p>With a like you show support for someone&amp;rsquo;s tweet or reply. You also let them know that you read their tweet. Those likes only shows up in people&amp;rsquo;s feed if they don&amp;rsquo;t filter their feed. If they do not filter, then those tweets do show up in their feeds, but most probably so do hundreds or thousands of other likes of people they follow. This means that they will be overwhelmed and don&amp;rsquo;t really read but doom-scroll.&lt;/p>
&lt;p>A like therefore is a good way to show someone: I saw and support this, but it is not a good way to engage with an audience beyond the person who tweeted what you liked.&lt;/p>
&lt;h3 id="retweet--attention-for-a-tweet">retweet = attention for a tweet&lt;/h3>
&lt;p>With a retweet, you share a tweet to your followers. This makes sense, if you feel a tweet doesn&amp;rsquo;t get enough or the attention of the right (?) people. Please be aware that if you overuse retweets by only retweeting other people&amp;rsquo;s tweets or by only retweeting tweets inside of a very focused filter-bubble, people will turn off your retweets&lt;/p>
&lt;p>(select the &lt;code>...&lt;/code> on a person&amp;rsquo;s profile and select &lt;code>Turn of Retweets&lt;/code>)&lt;/p>
&lt;p>because it is too noisy. While retweets (depending on your follower count) bring attention, they don&amp;rsquo;t drive engagement, which means that you broadcast in a one-way manner without encouraging others to get involved or join the discussion.&lt;/p>
&lt;h3 id="quote-retweet---attention-for-yourself">quote-retweet = attention for yourself&lt;/h3>
&lt;p>With a quote-retweet you take the discussion out of its original context and make it about your thoughts. To give you an analogy: Imagine you are in a big room full of people, there are small groups everywhere and you discuss something with a few people. Now one person walks by, and decides to talk about the exactly same topic - but somewhere else and without the people that already joined your talk. This person separates the discussion (and the people) for the sake of &lt;em>leading&lt;/em> the conversation. Use this very carefully and only if you feel you really give the conversation a new direction which you couldn&amp;rsquo;t do with a reply. Quote-retweets make it unnecessary hard for people to follow a conversation, as it now takes part in several threads.&lt;/p>
&lt;h3 id="reply--engagement">reply = engagement&lt;/h3>
&lt;p>With a reply you add your thoughts to a tweet. This could be a validation of the original tweet, or you being in disagreement or also bringing in a new perspective. Without replies, twitter would be just a big rook where everyone just uses a megaphone to broadcast their opinions but without listening. If you want to show that you listen and are open for a two way-communication, this is what you should do mostly.&lt;/p>
&lt;p>Also, the amount of replies is the primary reason so that the Twitter algorithm can rank this tweet higher and it will be displayed to more people - which increases reach and visibility.&lt;/p>
&lt;h4 id="a-few-tips-for-good-replies">A few tips for good replies&lt;/h4>
&lt;p>Don&amp;rsquo;t reply something like &amp;ldquo;cool/awesome/great&amp;rdquo;. It shows that you didn&amp;rsquo;t put any effort into your reply - and it doesn&amp;rsquo;t add value to the conversation as the discussion ends now.&lt;/p>
&lt;p>Instead, share why you think the idea which wa share is cool/awesome/great. Also if you don&amp;rsquo;t agree, don&amp;rsquo;t be like &amp;ldquo;nah/meh/this is bad&amp;rdquo;, but share your reasons (respectfully).&lt;/p>
&lt;p>Rule of thumbs: If you don&amp;rsquo;t have time/mental capacity to do this, yuo shouldn&amp;rsquo;t reply as you are only causing noise.&lt;/p>
&lt;p>Also a good way to engage is asking questions, because that is what keeps a conversation going.&lt;/p>
&lt;h3 id="i-want-it-all">I want it all&lt;/h3>
&lt;p>Now what if you what attention for the original tweet &lt;em>and&lt;/em> drive engagement? Well, you can combine the methods:&lt;/p>
&lt;ol>
&lt;li>add a reply, sharing what you think&lt;/li>
&lt;li>retweet (not quote tweet)&lt;/li>
&lt;/ol>
&lt;h2 id="what-do-you-think">What do you think?&lt;/h2>
&lt;p>&lt;a href="https://twitter.com/LuiseFreese/status/1501508996632662017">Share your thoughts with me on twitter :-)&lt;/a>&lt;/p></description></item><item><title>Get rid of Key Vault! (Making good things even better)</title><link>https://m365princess.com/blogs/rid-key-vault-making-good/</link><pubDate>Tue, 08 Mar 2022 14:31:10 +0000</pubDate><guid>https://m365princess.com/blogs/rid-key-vault-making-good/</guid><description>&lt;p>I love open-source, because it is a fantastic way to learn and share. I recently saw &lt;a href="https://twitter.com/inthecloud_247/status/1500035293684060161">this tweet by Peter Klapwijk&lt;/a>, who built a Logic App to monitor licenses of your Microsoft 365 tenant. The solutions uses&lt;/p>
&lt;ol>
&lt;li>a Logic App with an Office 365 API connection (to send emails with the built-in Outlook connector)&lt;/li>
&lt;li>a Key Vault (to protect the secret that is generated for the Azure AD app registration)&lt;/li>
&lt;li>manual deployment with a custom template&lt;/li>
&lt;/ol>
&lt;p>As much as I love the idea of the solution, I felt this can be improved - and this is of course complaining on a very high level.&lt;/p>
&lt;p>I wanted to do two things:&lt;/p>
&lt;ol>
&lt;li>Get rid of the secret and the Key Vault&lt;/li>
&lt;li>Automate deployment&lt;/li>
&lt;/ol>
&lt;h2 id="get-rid-of-the-app-registration">Get rid of the app registration&lt;/h2>
&lt;p>You may ask- How does this work? At least that was Peter&amp;rsquo;s first question :-) App registrations in Azure Active Directory (which handles identity and access management) give your app an identity and we can assign (and consent to) permissions in different APIs (like Microsoft Graph). To make things secure, we can protect this app with a secret. Together with the app id (client id) this is username/password for the app.&lt;/p>
&lt;p>As we now want to protect this secret, we store can store it in an Azure Key Vault, but to log into Key Vault, we again need credentials - it&amp;rsquo;s a chicken/egg problem. Also, rotating the secret and taking care when it expires is a tedious task.&lt;/p>
&lt;p>But even if you feel that Key Vault is fine - There is still an security hole:&lt;/p>
&lt;p>⚠ If you have a contributor role assignment on the app (not on the Key Vault!), &lt;a href="https://github.com/MicrosoftDocs/azure-docs/issues/39518">you can read the value of the secret that is stored in Key Vault in Kudu&lt;/a>.⚠&lt;/p>
&lt;blockquote>
&lt;p>Let&amp;rsquo;s face it: where there are secrets, there will be leaks.&lt;/p>
&lt;/blockquote>
&lt;p>Now there is a solution to this problem: &lt;a href="https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview">Azure Managed Identities&lt;/a> - give your app an identity &lt;em>without&lt;/em> an app registration. It&amp;rsquo;s an amazing example on how Azure abstracts away the complex stuff (creating, storing, rotating secrets) so you can focus on the nicer parts of development. I decided to use a user-assigned Managed Identity, which is a standalone Azure resource and can be shared in case we want to add more resources to an extended version of the solution. This Managed Identity of course will need to have the permissions assigned that the previous app registration had assigned.&lt;/p>
&lt;h2 id="automate-deployment">Automate deployment&lt;/h2>
&lt;p>I created Bicep 💪 files (super cool way to provide an ARM template) and split the template so that I can call every resource (Logic App, Managed Identity, API connection) as a module from the root deployment file and end up with separate files for each resource. That is more convenient to work with during development and also makes debugging easier.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bicep" data-lang="bicep">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">connections_office365_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;office365&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">workflows_Monitor_main_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Monitor-LogicApp&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_Monitor_Identity_name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Monitor-ManagedIdentity&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">ResourceGroupName&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">string&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityDeployment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Monitor-ManagedIdentity.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;managedIdentityDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_Monitor_Identity_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_Monitor_Identity_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">connectionsDeployment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Monitor-connections.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;connectionsDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">connections_office365_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">connections_office365_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">ResourceGroupName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">ResourceGroupName&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">module&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">MainDeployment&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;Monitor-main.bicep&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#39;MainDeployment&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">resourceLocation&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_Monitor_Identity_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">userAssignedIdentities_Monitor_Identity_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">workflows_Monitor_main_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">workflows_Monitor_main_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">connections_office365_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">connections_office365_name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">dependsOn&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">connectionsDeployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">managedIdentityDeployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I learned this one the hard way: Providing Infrastructure as Code (even if it is just for a small solution) is a faster, more sustainable and better way to deploy resources. It is painful how often GUIs change and results are not repeatable then. Also, it is more convenient to run a script rather than having to click yourself through a lengthy README file. &lt;em>(Been there as well)&lt;/em>&lt;/p>
&lt;p>The script will create the resource group that holds the resources and assign the correct Microsoft Graph permission scope to the managed identity:&lt;/p>
&lt;p>&lt;em>Here is the interesting part that assigns the permission, full script on GitHub 💚:&lt;/em>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-#" data-lang="#">$ManagedIdentity = az identity show --name Monitor-ManagedIdentity --resource-group $ResourceGroupName | ConvertFrom-Json
$principalId = $ManagedIdentity.principalId
# Get current role assignments
$currentRoles = (az rest `
--method get `
--uri https://graph.microsoft.com/v1.0/servicePrincipals/$principalId/appRoleAssignments `
| ConvertFrom-Json).value `
| ForEach-Object { $_.appRoleId }
$graphResourceId = az ad sp list --display-name &amp;#34;Microsoft Graph&amp;#34; --query [0].objectId
#Get appRoleIds for Organization.Read.All
$graphId = az ad sp list --query &amp;#34;[?appDisplayName==&amp;#39;Microsoft Graph&amp;#39;].appId | [0]&amp;#34; --all
$orgReadAll = az ad sp show --id $graphId --query &amp;#34;appRoles[?value==&amp;#39;Organization.Read.All&amp;#39;].id | [0]&amp;#34; -o tsv
$appRoleIds = $orgReadAll
#Loop over all appRoleIds - in case we later extend and need more than permission
foreach ($appRoleId in $appRoleIds) {
$roleMatch = $currentRoles -match $appRoleId
if ($roleMatch.Length -eq 0) {
# Add the role assignment to the principal
$body = &amp;#34;{&amp;#39;principalId&amp;#39;:&amp;#39;$principalId&amp;#39;,&amp;#39;resourceId&amp;#39;:&amp;#39;$graphResourceId&amp;#39;,&amp;#39;appRoleId&amp;#39;:&amp;#39;$appRoleId&amp;#39;}&amp;#34;;
az rest `
--method post `
--uri https://graph.microsoft.com/v1.0/servicePrincipals/$principalId/appRoleAssignments `
--body $body `
--headers Content-Type=application/json
}
}
Write-Host &amp;#34;🚀 -Deployment completed&amp;#34;
&lt;/code>&lt;/pre>&lt;p>&lt;em>(I re-used the script that we use at &lt;a href="https://provisiongenie.com">ProvisionGenie&lt;/a> 🧞)&lt;/em>&lt;/p>
&lt;p>As a result, we can see the the assigned permission scope in Azure Active Directory&lt;/p>
&lt;p>(Navigate to &lt;strong>Enterprise Applications&lt;/strong>, then filter by &lt;strong>Managed Identities&lt;/strong>, select the created Managed Identity and select &lt;strong>Permissions&lt;/strong>):&lt;/p>
&lt;p>&lt;img alt="hm2" src="https://m365princess.com/images/AzurePortalMIPermissions.png">&lt;/p>
&lt;p>I did a PR on &lt;a href="https://github.com/PeterKlapwijk/Microsoft-Logic-Apps/tree/main/Monitor%20your%20Microsoft%20365%20licenses%20with%20Logic%20Apps">Peter Klapwijks repository&lt;/a> and added my approach there as well - communityrocks ✨&lt;/p>
&lt;h2 id="what-do-you-think">What do you think?&lt;/h2>
&lt;p>You like it? Let me know!
You don&amp;rsquo;t like it? Let&amp;rsquo;s talk!&lt;/p>
&lt;p>👉🏻 &lt;a href="https://twitter.com/LuiseFreese/status/1501474516756799488?s=20&amp;t=I7zCQMgauvNsP3y8AZo36w">Find the discussion on twitter&lt;/a>&lt;/p></description></item><item><title>How to create a custom connector for your own Azure hosted API</title><link>https://m365princess.com/blogs/create-custom-connector-azure-hosted-api/</link><pubDate>Sun, 27 Feb 2022 18:28:39 +0000</pubDate><guid>https://m365princess.com/blogs/create-custom-connector-azure-hosted-api/</guid><description>&lt;p>In this blog post I am going to cover&lt;/p>
&lt;ul>
&lt;li>How to create an API with JavaScript&lt;/li>
&lt;li>How to deploy this to Azure&lt;/li>
&lt;li>How to wrap the API into a custom connector&lt;/li>
&lt;li>How to use the connector in Power Apps&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Overview" src="https://m365princess.com/images/diagram.png">&lt;/p>
&lt;p>Don&amp;rsquo;t know what is an API? Don&amp;rsquo;t worry, here is help:&lt;/p>
&lt;blockquote>
&lt;p>Imagine, you are dining in a restaurant (this is the app/client). You do not talk to the kitchen (server/database) directly, but you can send requests to the API, which is our waiter*waitress. They will request dishes from the kitchen and get them in return and then respond back to you.&lt;/p>
&lt;/blockquote>
&lt;p>Operations you could do are&lt;/p>
&lt;ul>
&lt;li>GET (request data from a server)&lt;/li>
&lt;li>POST (send new information to a server)&lt;/li>
&lt;li>PUT (make changes to existing data on a server by replacing the entire entity)&lt;/li>
&lt;li>PATCH (make changes to existing data on a server by updating provided fields)&lt;/li>
&lt;li>DELETE (remove existing information from a server)&lt;/li>
&lt;/ul>
&lt;h2 id="how-to-approach-things">How to approach things&lt;/h2>
&lt;p>In this example, we only want to do GET requests, as we want to read news from different newspaper websites such as BBC, New York Times and New York Post. In order to achieve that, we will scrape the websites of these sources and extract title and URL of the articles. We will later deploy this to Azure as a web app and wrap the API into a custom connector for Power Platform. Last thing is then consuming this custom connector in a Canvas app.&lt;/p>
&lt;h3 id="1-make-it-work-on-your-machine">1. make it work on your machine&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>Create a new directory on your computer and navigate into it&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Make sure you have &lt;a href="https://nodejs.org/en/">node.js&lt;/a> installed&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Run &lt;code>npm init&lt;/code>, which prompts you to answer some questions about your project (you can just hit return). It will then create a &lt;code>package.json&lt;/code> file in your project for you. It contains some metadata about your app such as your name, the version and the license. You can change the file according to your needs.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create a index.js file as an entrypoint for the app&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Install the following dependencies&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.npmjs.com/package/cheerio">cheerio&lt;/a>, which allows us to parse HTML to get exactly the data we need from a website&lt;/li>
&lt;li>&lt;a href="https://www.npmjs.com/package/axios">axios&lt;/a>, to ease the process of HTTP requests for CRUD operation&lt;/li>
&lt;li>&lt;a href="https://www.npmjs.com/package/express">express&lt;/a>, as backend framework for node.js - this will be our listener 👂&lt;/li>
&lt;li>&lt;a href="https://www.npmjs.com/package/nodemon">nodemon&lt;/a>, which helps us with restarting our nodejs app on change&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>💡 Dependencies will show up in your &lt;code>package.json&lt;/code> file.&lt;/p>
&lt;p>Please delete the &amp;ldquo;scripts&amp;rdquo; object in this file and replace it by this object:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JSON" data-lang="JSON">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;start&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nodemon index.js&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can later start our app with &lt;code>nodemon index.js&lt;/code>&lt;/p>
&lt;p>💡 You can also see now, that you have a &lt;code>package-lock.json&lt;/code> file - you may want to have a look, but we won&amp;rsquo;t touch this file.&lt;/p>
&lt;p>We wil now go ahead, open our &lt;code>index.js&lt;/code> file and first create the listener 👂&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">PORT&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">PORT&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="mi">8000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">axios&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;axios&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">cheerio&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;cheerio&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;express&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">PORT&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`my server is running on PORT &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">PORT&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb"> 🚀`&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can of course use a port of your choice.&lt;/p>
&lt;p>We can now create our first endpoint:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Welcome to my 📰 News API&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This endpoint sits at the root &lt;code>/&lt;/code> and displays just a little welcome message. You can later delete that, it&amp;rsquo;s just to see if things already work.&lt;/p>
&lt;p>Now we want to create an array of newspapers/sources that we later want to loop over.&lt;/p>
&lt;p>💡 As some of them don&amp;rsquo;t provide their baseURL when linking to an article on their website but only a relative path, it&amp;rsquo;s a good idea to have a baseURL property.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">newspapers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;nyt&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">address&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;https://www.nytimes.com/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">baseURL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;un&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">address&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;https://www.un.org/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">baseURL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;bbc&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">address&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;https://www.bbc.co.uk/news/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">baseURL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;https://www.bbc.co.uk&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;nyp&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">address&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;https://nypost.com/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">baseURL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next step is to create an empty array of articles and then loop over all newspapers to fill the array with articles&lt;/p>
&lt;ul>
&lt;li>Visit the address using axios,&lt;/li>
&lt;li>Save the response (this is the entire HTML of the website)&lt;/li>
&lt;li>Pass this HTML into cheerio&lt;/li>
&lt;li>Look for elements with an &lt;code>a&lt;/code> tag that contains &amp;ldquo;Ukraine&amp;rdquo;&lt;/li>
&lt;li>Grab the title (text) and the url (which us the &lt;code>href&lt;/code> attribute)&lt;/li>
&lt;li>Push this as an object into our articles array&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">articles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">newspapers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newspaper&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">axios&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newspaper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">address&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">pageHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">$&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">cheerio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pageHTML&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;a:contains(&amp;#34;Ukraine&amp;#34;)&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pageHTML&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">each&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">title&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">attr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;href&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">articles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">title&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">newspaper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">baseURL&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">source&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">newspaper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We now want to see this work in action - and create an endpoint &lt;code>/news&lt;/code> for it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/news&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">articles&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you now visit &lt;code>localhost:8000/news&lt;/code> ,you should see an array of of objects displaying title, source and URL of articles of all your sources. So far, so good. but what if we want to filter the sources with &lt;code>/news/nyt&lt;/code> or &lt;code>/news/bbc&lt;/code>?&lt;/p>
&lt;p>Let&amp;rsquo;s introduce the &lt;code>newspaperId&lt;/code>. We will again do the same axios and cheerio magic once again - Our new array is now called &lt;code>specificArticles&lt;/code> and will also contain the id of the source.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/news/:newspaperId&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newspaperId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">newspaperId&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newspaperAddress&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">newspapers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newspaper&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">newspaper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">newspaperId&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">address&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newspaperBase&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">newspapers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newspaper&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">newspaper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">newspaperId&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">axios&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newspaperAddress&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">pageHTML&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">$&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">cheerio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pageHTML&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">specificArticles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;a:contains(&amp;#34;Ukraine&amp;#34;)&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pageHTML&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">each&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">title&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">attr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;href&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">specificArticles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">title&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">newspaperBase&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">source&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">newspaperId&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">specificArticles&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}).&lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can now test this with &lt;code>localhost:8000/news/bbc&lt;/code> or &lt;code>localhost:8000/news/nyt&lt;/code>&lt;/p>
&lt;p>👉 Please do yourself a favor and create a &lt;code>.gitignore&lt;/code> file with&lt;/p>
&lt;pre tabindex="0">&lt;code>node_modules
&lt;/code>&lt;/pre>&lt;p>so that you later on don&amp;rsquo;t commit the node modules.&lt;/p>
&lt;h3 id="2-make-it-work-on-azure">2. Make it work on Azure&lt;/h3>
&lt;p>Cool, we made a service work on our machine, but more people could benefit if we deployed this API now to Azure. The service we will be using here is an Azure Web app. As I am a Windows person, I&amp;rsquo;d like to try more things on Linux, so we will be creating a Linux app 🤓&lt;/p>
&lt;ol>
&lt;li>Open VS Code or the terminal of your choice&lt;/li>
&lt;li>make sure you have &lt;a href="https://docs.microsoft.com/cli/azure/install-azure-cli">Azure CLI&lt;/a> installed&lt;/li>
&lt;li>Create a new web app with the following command: &lt;code>az webapp up --sku F1 --name &amp;lt;mywebapp42&amp;gt; --location &amp;lt;location-name&amp;gt;&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>💡 please note:&lt;/p>
&lt;ul>
&lt;li>the name of the webapp needs to be globally unique&lt;/li>
&lt;li>the location is optional - if you don&amp;rsquo;t know which region you want to use, you can get a list with &lt;code>az appservice list-locations --sku F1&lt;/code>&lt;/li>
&lt;li>if you don&amp;rsquo;t like Linux, use &lt;code>--os-type Windows&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The web app will be created for you, this can take a few moments, as it does not only create the web app for you, but also automatically creates a resource group and an App service plan. It also packs and deploys your local project.&lt;/p>
&lt;p>In the output, you will see something like:&lt;/p>
&lt;pre tabindex="0">&lt;code>Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://&amp;lt;mywebapp42&amp;gt;.azurewebsites.net
&lt;/code>&lt;/pre>&lt;p>Select that link! In case you didn&amp;rsquo;t delete the root endpoint, you should now see the greeting ❤ Test your other endpoints as well! Also go to the &lt;a href="https://portal.azure.com">Azure portal&lt;/a> and check the resources that have been created for you.&lt;/p>
&lt;p>It should look something like this:&lt;/p>
&lt;p>&lt;img alt="Resource group" src="https://m365princess.com/images/Azure_RG-NewsApi.png">&lt;/p>
&lt;h3 id="3-wrap-your-api-into-a-custom-connector">3. Wrap your API into a custom connector&lt;/h3>
&lt;p>Now that we have a cool API, let&amp;rsquo;s use it in Power Platform!&lt;/p>
&lt;ol>
&lt;li>Open &lt;a href="https://flow.microsoft.com">flow.microsoft.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Data&lt;/strong>, select &lt;strong>Custom Connectors&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New&lt;/strong>&lt;/li>
&lt;li>in the &lt;strong>General&lt;/strong> for &lt;strong>Host&lt;/strong> enter your new app URL &lt;code>http://&amp;lt;mywebapp42&amp;gt;.azurewebsites.net&lt;/code>&lt;/li>
&lt;li>Don&amp;rsquo;t do anything in the Security tab - this is a public API and we don&amp;rsquo;t need authenticated access&lt;/li>
&lt;li>On the &lt;strong>Definition&lt;/strong> tab, select &lt;strong>New action&lt;/strong>&lt;/li>
&lt;li>For &lt;strong>Summary&lt;/strong>, &lt;strong>Description&lt;/strong>, and &lt;strong>Operation ID&lt;/strong> enter something like &lt;code>GetNewsAll&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Import from sample&lt;/strong> for &lt;strong>Request&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Get&lt;/strong> and paste in the URL &lt;code>http://&amp;lt;mywebapp42&amp;gt;.azurewebsites.net/news&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Add a default response&lt;/strong> and paste an array in it - you don&amp;rsquo;t need actual values, the schema is enough.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Json" data-lang="Json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;source&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;source&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="11">
&lt;li>Select &lt;strong>Create connector&lt;/strong>&lt;/li>
&lt;li>you may want to test your connector on the &lt;strong>Test&lt;/strong> tab. To do so, select &lt;strong>New Connection&lt;/strong> and then select &lt;strong>Test operation&lt;/strong> - This should return an HTTP code 200 and you should see some real data.&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Custom Connector Test" src="https://m365princess.com/images/CustomConnectorTest.png">&lt;/p>
&lt;p>Repeat the steps 6-12 for the other endpoints that you created (i.e. &lt;code>/news/bbc)&lt;/code> You can also pass in the newspaperId as a parameter.&lt;/p>
&lt;h3 id="4-consume-the-custom-connector-in-a-canvas-app">4. Consume the custom connector in a Canvas app&lt;/h3>
&lt;ol>
&lt;li>Open &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a>&lt;/li>
&lt;li>Create a new canvas app from blank - don&amp;rsquo;t forget to save it&lt;/li>
&lt;li>In the left hand bar, select &lt;strong>Data&lt;/strong> and add your custom connector&lt;/li>
&lt;li>Create a gallery and set its &lt;strong>Items&lt;/strong> property to &lt;code>Defaulttitle.GetNewsAll()&lt;/code>&lt;/li>
&lt;li>Set the
&lt;ul>
&lt;li>&lt;strong>Text&lt;/strong> property of the &lt;strong>Title&lt;/strong> label of the gallery to &lt;code>ThisItem.title&lt;/code>,&lt;/li>
&lt;li>&lt;strong>Text&lt;/strong> property of the &lt;strong>Subtitle&lt;/strong> label of the gallery to &lt;code>ThisItem.source&lt;/code>,&lt;/li>
&lt;li>&lt;strong>Onselect&lt;/strong> property of the icon of the gallery to &lt;code>Launch(ThisItem.url)&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>You can even create a dropdown menu that holds the different sources so that users can change what they want to consume.&lt;/p>
&lt;p>&lt;img alt="canvas App" src="https://m365princess.com/images/AppUkraine.png">&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>You can wrap ANY API into a custom connector for Power Platform - even the ones that you create.&lt;/p>
&lt;h3 id="resources">Resources&lt;/h3>
&lt;p>Some helpful resources&lt;/p>
&lt;p>&lt;a href="https://docs.microsoft.com/en-us/azure/app-service/quickstart-nodejs?pivots=development-environment-cli&amp;tabs=linux">Quickstart: Create a Node.js web app - Azure App Service | Microsoft Docs&lt;/a>
&lt;a href="https://www.freecodecamp.org/news/how-to-scrape-websites-with-node-js-and-cheerio/">How to Scrape Websites with Node.js and Cheerio (freecodecamp.org)&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://docs.microsoft.com/en-us/connectors/custom-connectors/define-blank">Create a custom connector from scratch | Microsoft Docs&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.m365princess.com/blogs/2021-02-23-how-to-use-a-custom-connector-in-power-automate/">How to use a custom connector in Power Automate (m365princess.com)&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://www.michaelroth42.com/post/2022-01-03-working-with-apis-in-power-platform-for-beginners-part-2/">Working with Custom Connectors in Power Platform for beginners (michaelroth42.com)&lt;/a>&lt;/p>
&lt;p>#SharingIsCaring 💖&lt;/p></description></item><item><title>How to be better at twitter</title><link>https://m365princess.com/blogs/twitter/</link><pubDate>Fri, 28 Jan 2022 14:29:48 +0000</pubDate><guid>https://m365princess.com/blogs/twitter/</guid><description>&lt;p>I use socials every day to engage, get the news I need to know and send out the messaging that helps me building a community. I want to share some good practices about using twitter and explain some do&amp;rsquo;s and don&amp;rsquo;ts. Also I want to suggest some tools that help you keep track and some approaches to mute some white noise.&lt;/p>
&lt;h2 id="dos--donts">Do&amp;rsquo;s &amp;amp; Don&amp;rsquo;ts&lt;/h2>
&lt;h3 id="-mentions">@ mentions&lt;/h3>
&lt;p>Don&amp;rsquo;t @-mention &amp;lsquo;big accounts&amp;rsquo; like &amp;lsquo;@Microsoft&amp;rsquo;, &amp;lsquo;@SharePoint&amp;rsquo; and also not the personal accounts of leaders in this space.&lt;/p>
&lt;p>These official accounts are managed by people in marketing and there are typically strict rules in place how to engage, as in what do like, if and what reply and what to retweet. If you are now tweeting your questions that should go to helpdesk, your feature requests, your &lt;em>Look at me, I am hosting an event&lt;/em> tweets and mention these accounts, nothing will happen - other than that you wasted characters on @ mentioning accounts that won&amp;rsquo;t engage with you.&lt;/p>
&lt;blockquote>
&lt;p>⚠ It is a common misunderstanding, that if you @ mention an account, their followers would see that in their feed. They don&amp;rsquo;t.&lt;/p>
&lt;/blockquote>
&lt;p>Also: Can you imagine how many @ mentions personal accounts of leaders in the space get each and every day? Do you really want to spam them?&lt;/p>
&lt;p>Instead of @ mentions, use appropriate hashtags, 1-3 will do the job. Don&amp;rsquo;t invent new hashtags, don&amp;rsquo;t have more hashtags than actual words in your tweet. Why? People might not follow you, but they monitor hashtags. Hashtags are &lt;strong>the&lt;/strong> way to tame the information overload and get the content you want. And if you are a content creator on twitter, you should make use of these. Consistency is key here.&lt;/p>
&lt;h3 id="-mentions-1">.@ mentions&lt;/h3>
&lt;p>before 2017, if you posted an @ mention at the beginning of a tweet, twitter interpreted this as a reply to this account, which meant, that your tweet was only in the feed of accounts that followed you and the account you were @ mentioning. People therefore chose to put a character before the @mention, for example a dot &lt;code>.&lt;/code>- Twitter changed this so that it doesn&amp;rsquo;t make any difference anymore if you put a dot &lt;code>.&lt;/code> or not. It doesn&amp;rsquo;t harm, but it let&amp;rsquo;s you look as if your last update on how twitter works is already too long ago.&lt;/p>
&lt;h3 id="media">Media&lt;/h3>
&lt;p>Tweets not containing media such as links (with preview image), an image or a gif usually don&amp;rsquo;t get a lot of engagement. Most people are strongly visually oriented and they click when they skim over something that &lt;strong>looks&lt;/strong> interesting.&lt;/p>
&lt;p>Pro Tip: use the &lt;a href="https://studio.twitter.com/library">Twitter Media Studio&lt;/a> to manage your media&lt;/p>
&lt;p>Enriching your texts with media is a great idea to make them more compelling. You can also structure your tweet with emojis and paragraphs if you have a lot to say.&lt;/p>
&lt;p>You can check the preview of your Open ID card with the &lt;a href="https://cards-dev.twitter.com/validator">Twitter Card validator&lt;/a> tool.&lt;/p>
&lt;p>&lt;img alt="screnshot of twitter card validator tool" src="https://m365princess.com/images/twittercardvalidator.png">&lt;/p>
&lt;h3 id="url-shortener">URL shortener&lt;/h3>
&lt;p>Are you using a fancy URL shortener to have more characters left for your tweet when you share a URL? URL shorteners have some benefits - you get the stats, they look nice, people could even remember them, but they do nothing for the length of your tweet. A tweet may contain up to 280 characters, if there is no URL in it. If there is a URL, the &lt;a href="https://developer.twitter.com/en/docs/counting-characters">URL counts as 23 characters&lt;/a>, regardless if it has more or less characters.&lt;/p>
&lt;h3 id="accessibility">Accessibility&lt;/h3>
&lt;p>Not caring about accessibility is a luxury that we indulge on the expense of others. Most twitter clients have an Alt text feature so that you can describe your images. If the client you use doesn&amp;rsquo;t have this feature, either consider to change to another client (as it is not feature complete) or add another tweet to your original tweet that then adds the Alt text.&lt;/p>
&lt;p>Please don&amp;rsquo;t do any fancy fonts or special characters or try be extra funny/nerdy like &lt;code>D:\ebbie&lt;/code> in your Twitter Name - screen readers will not be able to interpret this correctly.&lt;/p>
&lt;h2 id="tools-and-how-to-use-them">Tools and how to use them&lt;/h2>
&lt;p>Tools that help me keeping track of things:&lt;/p>
&lt;h3 id="tweetdeck">Tweetdeck&lt;/h3>
&lt;p>&lt;a href="https://tweetdeck.twitter.com/">Tweetdeck&lt;/a> is most probably the best invention since twitter. Its an application by twitter that allows you to organize content in columns. You can display hashtags, lists, users, your scheduled tweets just right next to each other. It also allows you to filter in these columns by engagement and you can include/exclude authors.&lt;/p>
&lt;p>&lt;img alt="screenshot of tweetdeck showing which columns can be added" src="https://m365princess.com/images/tweetdeck.png">&lt;/p>
&lt;p>Pro Tip - you can add Tweetdeck as a PWA :-)&lt;/p>
&lt;p>&lt;img alt="add Tweetdeck as an app" src="https://m365princess.com/images/tweetdeck.gif">&lt;/p>
&lt;h3 id="lists">Lists&lt;/h3>
&lt;p>You don&amp;rsquo;t have lists? You should probably change that. Lists allow you to curate content based on what is important to you. This way, you don&amp;rsquo;t need to necessarily follow each account that is somewhat interesting for you. You can put them in a list. Think about list as channels in Teams - they give you content in a certain context - but you don&amp;rsquo;t need everything in your home feed.&lt;/p>
&lt;p>I use &lt;a href="https://twitterlistmanager.com/">Twitter List Manager&lt;/a> to organize my lists - not the most compelling UI, but it does the job :-)&lt;/p>
&lt;h3 id="bookmarks---or-the-i-will-never-look-at-that-again-list-">Bookmarks - or the &amp;lsquo;I will never look at that again&amp;rsquo;-list 😥&lt;/h3>
&lt;p>Twitter has a bookmark feature, but until I discovered &lt;a href="https://getdewey.co/">dewey&lt;/a> I didn&amp;rsquo;t find it useful. dewey allow me to organize my bookmarks so that unsactually make sense of that pile of guilt. It&amp;rsquo;s a Chrome/Edge extension and I love it!
&lt;img alt="screenshot of dewey bookmark tool" src="https://m365princess.com/images/dewey.png">&lt;/p>
&lt;h3 id="search">Search&lt;/h3>
&lt;p>Want to find a tweet again and keep on scrolling? Twitter web client offers you an &lt;a href="https://twitter.com/search-advanced">advanced search&lt;/a>&lt;/p>
&lt;p>&lt;img alt="shows twitter advanced search popup" src="https://m365princess.com/images/twittersearch.gif">&lt;/p>
&lt;h3 id="analytics">Analytics&lt;/h3>
&lt;p>If you are interested in how you are doing on twitter, there are several tools to track that Twitter has built-in analytics, which you will find in the web client. You can also access it at &lt;code>https://analytics.twitter.com/user/&amp;lt;your user name here&amp;gt;/home&lt;/code>&lt;/p>
&lt;h2 id="stay-focused">Stay focused&lt;/h2>
&lt;p>Although Twitter is important we all need to make sure to not get overwhelmed by this constant stream of news and call to actions.&lt;/p>
&lt;h3 id="the-art-of-muting">The art of muting&lt;/h3>
&lt;p>You follow some accounts but there are subjects that keep on annoying you? Instead of blocking people, you can mute words. Also, you can mute Retweets of people that retweet a lot of content that you already have in your feed.&lt;/p>
&lt;h3 id="notifications--other-settings">Notifications &amp;amp; other settings&lt;/h3>
&lt;p>My best advice is to have an intense look at your notification settings and see if you really need all of them. If you use a tool like Tweetdeck or analytics to monitor and manage your content, you don&amp;rsquo;t necessarily need real time distractifications 🔔&lt;/p>
&lt;p>Last but not least - Make sure that you see tweets in chronological order and not by whatever Twitter thinks you should see now. This way you know when you read everything.&lt;/p>
&lt;h2 id="drive-engagement">Drive Engagement&lt;/h2>
&lt;p>Want a deeper connection with your community? Best way to achieve this is to have meaningful conversation, sharing your insights and keep on listening.&lt;/p>
&lt;h3 id="when-to-tweet">When to tweet?&lt;/h3>
&lt;p>As a consultant, I need to say: It depends! - But there is a sweet built-in tool called &lt;a href="https://studio.twitter.com/audience-insights">twitter Insights&lt;/a> that allows us to see when you get the highest engagement.&lt;/p>
&lt;p>I also use &lt;a href="https://socialbearing.com/">Social Bearing&lt;/a> which gives me some insights on how my tweets are doing.&lt;/p>
&lt;h2 id="feedback--whats-next">Feedback &amp;amp; What&amp;rsquo;s next&lt;/h2>
&lt;p>What are your twitter hacks? &lt;a href="https://twitter.com/LuiseFreese/status/1487082925581557764">Let me know on twitter&lt;/a>&lt;/p></description></item><item><title>be brave enough to suck at something new</title><link>https://m365princess.com/blogs/brave-suck/</link><pubDate>Mon, 27 Dec 2021 11:16:07 +0000</pubDate><guid>https://m365princess.com/blogs/brave-suck/</guid><description>&lt;p>Tis the season where we all look back to reflect what happened in the year, what we achieved, where we grew and where we helped others. At the beginning of this year, I told myself to be brave enough to suck at something new, which meant to me that I wanted to make more courages decisions - what to learn, what to build, what to dare. Turns out, that this was a good approach for me.&lt;/p>
&lt;p>Instead of listing now each and every little thing I did, (well this ain&amp;rsquo;t the MVP community contribution list that I STILL need to update 😇), I&amp;rsquo;d rather like to emphasize a few initiatives that are near and dear to my heart - and that consumed not only a lot of my time but also of my mental capacity. Like a lot of us, I felt affected (yet another year) by the global world is on fire situation.&lt;/p>
&lt;p>&lt;img alt="This is fine meme" src="https://m365princess.com/images/thisisfine.jpg">&lt;/p>
&lt;h2 id="open-source">Open Source&lt;/h2>
&lt;p>Giving back to community balances this feeling, it lifts me up to see others thrive and I find joy and calm in collaborating with community for a greater good. This is why open-source became so important to me.&lt;/p>
&lt;h3 id="microsoft-365-pnp">Microsoft 365 PnP&lt;/h3>
&lt;p>In December 2020, I joined the &lt;a href="https://aka.ms/m365pnp#team">Microsoft 365 PnP&lt;/a> team, which is &amp;lsquo;a virtual team consisting of Microsoft employees and community members focused on helping the community make the best of Microsoft products&amp;rsquo;. We help others to extend Microsoft 365 with tools, guidance, community calls and lots of initiatives. If you never heard about it, its worth checking it out.&lt;/p>
&lt;p>In January 2021 I launched the &lt;a href="https://aka.ms/m365pnp/community/blog">Microsoft 365 PnP community blog&lt;/a>, where everyone can share their knowledge about building things with Microsoft 365. This hub is also the home for community call recordings, the Microsoft 365 Developer podcast, and the PnP Weekly podcast. As of today, we managed to publish almost 1 article for each day. I&amp;rsquo;ve even been rewarded as MVP of the month April 😁 for this initiative. But please stay tuned -more exciting things here will happen in early 2022 🎉&lt;/p>
&lt;p>&lt;img alt="MVP of the month" src="https://m365princess.com/images/mvpofthemonth.png">&lt;/p>
&lt;p>Also, I am part of the &lt;a href="https://pnp.github.io/sharing-is-caring/">Sharing is Caring&lt;/a> initiative, which aims to make it easier for new contributors to find their way into our open source projects. We run a lot of sessions, on how to help with code and documentation, established a buddy system for new presenters in community calls and even run Office hours to help everyone achieve more together. I like to say a big thanks to the &lt;a href="https://pnp.github.io/sharing-is-caring/#team">entire team&lt;/a>&lt;/p>
&lt;h3 id="provisiongenie">ProvisionGenie&lt;/h3>
&lt;p>Throughout the entire year, I&amp;rsquo;ve been working together with &lt;a href="https://twitter.com/carmenysewijn">Carmen Ysewijn&lt;/a> on our open-source Microsoft Teams provisioning engine called &lt;a href="https://provisiongenie.com">ProvisionGenie&lt;/a>. &lt;a href="https://provisiongenie.com/about/sessions/">We got quite some attention&lt;/a> and are pretty happy with it, but like any other application: it&amp;rsquo;s never finished and there is always work to do. Huge 🧞💜 thanks to our all-time supporter and Chief Debugging Expert &lt;a href="https://twitter.com/yannickreekmans">Yannick Reekmans&lt;/a>&lt;/p>
&lt;h3 id="even-more-open-source">even more open-source&lt;/h3>
&lt;ul>
&lt;li>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/manekinekko">Wassim Chegham&lt;/a> invited me to consult the team during the Microsoft Global Hackathon - and WE WON 🎉🎉🎉&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>I participated in the Hackathon held at SouthCoastSummit with my amazing team mates &lt;a href="https://www.twitter.com/carmenysewijn">Carmen Ysewijn&lt;/a>, &lt;a href="https://www.twitter.com/Lee_ford">Lee Ford&lt;/a>, &lt;a href="https://www.twitter.com/michaelroth42">Michael Roth&lt;/a>, &lt;a href="https://www.twitter.com/tomszposzytek">Tomasz Poszytek&lt;/a> and &lt;a href="https://www.twitter.com/yannickreekmans">Yannick Reekmans&lt;/a> - &lt;a href="https://www.m365princess.com/blogs/2021-10-23-how-2-makers-2-devs-and-a-princess-came-together-to-save-kittens-for-a-hackathon/">read about it here&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>In the beginning of the year, &lt;a href="https://twitter.com/eddiejaoude">Eddie Jaoude&lt;/a> asked me to join his #Eddiehub guild and to participate in the &lt;a href="https://mlh.io/">Major League Hacking Hackathon&lt;/a> - and guess what? We won!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>I worked with &lt;a href="https://twitter.com/marcduiker">Marc Duiker&lt;/a> on his awesome initiative &lt;a href="https://github.com/marcduiker/azure-functions-university">Azure Functions University&lt;/a> - this will be an ongoing effort in 2022 as well&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="conferences">Conferences&lt;/h2>
&lt;p>I know, most of us stayed at home for yet another year, but I was very fortunate to be able to attend and speak at two in-person events:&lt;/p>
&lt;p>At &lt;a href="https://www.southcoastsummit.com/">SouthCoastSummit&lt;/a> I spoke together with &lt;a href="https://twitter.com/yannickreekmans">Yannick Reekmans&lt;/a> about &amp;ldquo;FusionDev - hello from the other side&amp;rdquo;, which reflected our thoughts about makers and developers needing to join forces. Although totally overwhelming (people!), we had a blast and I was truly happy to see lots of my friends again.&lt;/p>
&lt;p>Later on in November, it was &lt;a href="https://collabsummit.eu">Collabsummit&lt;/a>, the community conference that is so close to my heart. One more special thing was, that it took place in Duesseldorf, where I happen to live. I delivered a session on &amp;ldquo;Azure Managed Identity and Microsoft Graph&amp;rdquo;, &lt;a href="https://twitter.com/LuiseFreese/status/1465650839331221507?s=20">attendees seemed to like it&lt;/a> 😇&lt;/p>
&lt;h2 id="more-learning-and-sharing">more learning and sharing&lt;/h2>
&lt;ul>
&lt;li>
&lt;ul>
&lt;li>I published 36 blog posts on &lt;a href="https://m365princess.com">m365princess.com&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>I learned 100+ hours JavaScript but missed my goal of doing this for 100 consecutive days&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>I completed &lt;a href="https://pll.harvard.edu/course/cs50-introduction-computer-science?delta=0">Harvard&amp;rsquo;s CS50 Introduction to Computer Science&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>I completed the &lt;a href="https://www.postman.com/postman/workspace/30-days-of-postman-for-developers/overview">30 days of Postman challenge&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>I delivered countless online sessions (for real, I lost track)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;ul>
&lt;li>As a special xmas present to myself, I passed the &lt;a href="https://www.credly.com/users/luise-freese/badges">MS-600 exam - Microsoft 365 Developer Associate&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>Good question! I want to continue to learn and share and grow with all of you! If you have a cool idea in mind, please reach out! I am always happy to help!&lt;/p>
&lt;p>Have a happy new year - hope to see you soon somewhere!&lt;/p></description></item><item><title>How to move your blog from Wordpress to Hugo</title><link>https://m365princess.com/blogs/move-blog-wordpress-hugo/</link><pubDate>Sat, 18 Dec 2021 17:51:35 +0000</pubDate><guid>https://m365princess.com/blogs/move-blog-wordpress-hugo/</guid><description>&lt;p>If you are a little bit like me, you started to blog some years ago on Wordpress and now don&amp;rsquo;t like it anymore. It&amp;rsquo;s clumsy and slow and kinda uncool and you would like to have a better experience for authoring your blog posts. A couple of months ago, I moved my own blog &lt;a href="https://m365princess.com">m365princess.com&lt;/a> from WordPress to Hugo. This blog post explains what you need to do to do the same and how you can manage your website then in Visual Studio Code.&lt;/p>
&lt;p>I will guide you how to&lt;/p>
&lt;ul>
&lt;li>convert your WordPress HTML posts into markdown files with &lt;a href="https://github.com/palaniraja/blog2md">blog2md&lt;/a>&lt;/li>
&lt;li>how to host your site with git and &lt;a href="https://github.com">GitHub&lt;/a>&lt;/li>
&lt;li>host the files with &lt;a href="https://netlify.com">Netlify&lt;/a>&lt;/li>
&lt;li>author, manage and publish your blog posts with &lt;a href="https://code.visualstudio.com/">VS Code&lt;/a> and &lt;a href="https://frontmatter.codes">Front Matter&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>This means, that you need to have&lt;/p>
&lt;ul>
&lt;li>git installed&lt;/li>
&lt;li>a GitHub account&lt;/li>
&lt;li>a Netlify account&lt;/li>
&lt;li>Hugo installed&lt;/li>
&lt;li>Visual Studio Code installed&lt;/li>
&lt;li>Front Matter installed&lt;/li>
&lt;/ul>
&lt;p>All of the above is free/ has a Freemium model. In case you are not familiar with git and GitHub, I got your back, read &lt;a href="https://www.m365princess.com/blogs/started-github-git/">How to get started with GitHub and Git&lt;/a>, then continue here.&lt;/p>
&lt;h2 id="git-and-github">git and GitHub&lt;/h2>
&lt;p>After you read that post I will assume that you have git installed and that you are the proud owner of a GitHub account.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Create a new repository on GitHub&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Clone this repository locally&lt;/p>
&lt;ul>
&lt;li>Select the green &lt;strong>Code&lt;/strong> button on your repository&lt;/li>
&lt;li>Select the &lt;strong>Copy&lt;/strong> button&lt;/li>
&lt;li>Open VS Code&lt;/li>
&lt;li>Open a new terminal&lt;/li>
&lt;li>Navigate to a parent folder where you like to have have your repository in&lt;/li>
&lt;li>Type in &lt;code>git clone &amp;lt;the copied value from the repository&amp;gt;&lt;/code> to get a local copy&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Navigate into this folder &lt;code>cd &amp;lt;repositoryname&amp;gt;&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>create a new folder either &lt;code>mkdir &amp;lt;hugoblog&amp;gt;&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create a new branch so that you have a stage to deploy from with &lt;code>git checkout -b &amp;lt;your branch name&amp;gt;&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="netlify">Netlify&lt;/h2>
&lt;p>Once you created your Netlify account, you can connect your GitHub repository to it. In the dashboard&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Add new site&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Import an existing project&lt;/strong>&lt;/li>
&lt;li>Select the &lt;strong>GitHub&lt;/strong> button to connect&lt;/li>
&lt;li>Log in&lt;/li>
&lt;li>Select &lt;strong>Configure Netlify on GitHub&lt;/strong>&lt;/li>
&lt;li>Log in&lt;/li>
&lt;li>Select the repository you created earlier&lt;/li>
&lt;li>Select save&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="repo access" src="https://m365princess.com/images/repository-access.png">&lt;/p>
&lt;p>The repository should now appear on the Netlify site.&lt;/p>
&lt;ul>
&lt;li>Select it&lt;/li>
&lt;li>Ignore all the fields (for now, we can set this up later)&lt;/li>
&lt;li>Select &lt;strong>Deploy site&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>You will see a 404 - this normal!&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Site information&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Change site name&lt;/strong> and change it to something more friendly, like &lt;code>Luise-new-blog&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>With this, the new address for your site is &lt;code>https://luise-new-blog.netlify.app&lt;/code>. Yet again, if you click on it - don&amp;rsquo;t be disappointed about the 404 error.&lt;/p>
&lt;p>You can later change the CNAME record of your website so that your preferred URL points to that new site.&lt;/p>
&lt;h2 id="hugo">Hugo&lt;/h2>
&lt;p>We will now install HUGO on your computer. I&amp;rsquo;m on Windows 10, pretty sure it works similar to that on Windows 11 that way as well. If you are on MaC/Linux, please look up your preferred way to install on &lt;a href="https://gohugo.io/">Hugo&lt;/a>&lt;/p>
&lt;ul>
&lt;li>Create a folder &lt;code>Hugo&lt;/code>&lt;/li>
&lt;li>Create two subfolders in the &lt;code>Hugo&lt;/code> folder and name them &lt;code>bin&lt;/code> and &lt;code>Sites&lt;/code>&lt;/li>
&lt;li>Download the latest .zip file from &lt;a href="https://github.com/gohugoio/hugo/releases">Hugo releases&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="hugo-releases" src="https://m365princess.com/images/hugo-release.png">&lt;/p>
&lt;ul>
&lt;li>Extract the zip file into the &lt;code>bin&lt;/code> folder&lt;/li>
&lt;li>Add the &lt;code>bin&lt;/code> folder to PATH:
&lt;ul>
&lt;li>Open the Windows settings&lt;/li>
&lt;li>Select &lt;strong>Advanced system settings&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Windows 10 system settings" src="https://m365princess.com/images/system-settings.png">
- Select &lt;code>Environment variables&lt;/code>
- Select &lt;strong>Path&lt;/strong> in the user variables
- Select &lt;strong>Edit&lt;/strong>
- Select &lt;strong>New&lt;/strong>
- Type in the Path to your bin folder&lt;/p>
&lt;p>&lt;img alt="system properties" src="https://m365princess.com/images/system-properties.png">
- Select &lt;strong>OK&lt;/strong>
- Select &lt;strong>OK&lt;/strong>
- You may close settings&lt;/p>
&lt;ul>
&lt;li>Test if you did things right by opening a terminal (I use VS Code) and type &lt;code>hugo help&lt;/code> - it should respond with this:&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="hugo help in terminal" src="https://m365princess.com/images/hugo-help.png">&lt;/p>
&lt;h3 id="build-your-site">build your site&lt;/h3>
&lt;ul>
&lt;li>navigate to your &lt;code>Sites&lt;/code> dir&lt;/li>
&lt;li>type in &lt;code>hugo new site &amp;lt;your sitenamegoes here&amp;gt; --force&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="new site success" src="https://m365princess.com/images/hugo-newsite.png">&lt;/p>
&lt;p>Hugo has a built-in live server, so that you can work locally. You can start it with &lt;code>hugo server&lt;/code> - it will tell you on which localhost address you can preview your empty site - for me it &lt;code>localhost:1313&lt;/code>.&lt;/p>
&lt;p>To have a cool site, you need to&lt;/p>
&lt;ul>
&lt;li>define its structure in the &lt;code>config.toml&lt;/code> file&lt;/li>
&lt;li>have your blog post .md files in the &lt;code>contents&lt;/code> folder&lt;/li>
&lt;li>have your images in the &lt;code>static/images&lt;/code> folder&lt;/li>
&lt;li>either create a new theme or install a theme&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s tackle this one step at a time:&lt;/p>
&lt;h4 id="configtoml">config.toml&lt;/h4>
&lt;p>Let&amp;rsquo;s make sure we have the site name and blog URL (the friendly URL from Netlify)&lt;/p>
&lt;pre tabindex="0">&lt;code>title = &amp;#34;Luise&amp;#39;s new blog&amp;#34;
baseURL = &amp;#34;https://luise-new-blog.netlify.app&amp;#34;
&lt;/code>&lt;/pre>&lt;p>Later, you can set up navigation in this file as well.&lt;/p>
&lt;h4 id="contents">contents&lt;/h4>
&lt;h5 id="blog-posts">blog posts&lt;/h5>
&lt;p>To have your (old) blog posts in here, we will first need to convert the HTML pages to .md files. You can either use a browser-based tool like &lt;a href="https://codebeautify.org/html-to-markdown">Code-Beautify&lt;/a> or run blog2md, which is what I prefer.&lt;/p>
&lt;ul>
&lt;li>Export all blog posts from WordPress
&lt;ul>
&lt;li>Select &lt;strong>Tools&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Export&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Posts&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Download Export File&lt;/strong>&lt;/li>
&lt;li>This will output an xml file&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Install blog2md&lt;/li>
&lt;li>Transform the xml file to md files with blog2md&lt;/li>
&lt;li>&lt;code>node index.js w &amp;lt;Path to your xml file&amp;gt; &amp;lt;folder where you want your old blog posts to live.&amp;gt;&lt;/code>&lt;/li>
&lt;li>Do a little cleanup, and delete the blog2md repository clone and the npm dependency&lt;/li>
&lt;/ul>
&lt;h5 id="handle-metadata-with-front-matter">handle metadata with Front Matter&lt;/h5>
&lt;p>You will notice, that in each and every .md file, you will not only find the content of your previous HTML page, but also metadata, encapsulated by &lt;code>---&lt;/code> - this part is called front matter. Front matter is the first section of a book and gives information on author, publishing house and so on. These preliminary matter made its way to digital content as well, as a blog post usually has an author, publishing date, URL, preview image and so on. As every new blog post will need front matter, you could come up with some awkward workaround of copy/paste that and then adjust it every time to your needs. But lets face it: Thats not how we want to work.&lt;/p>
&lt;p>Lucky you, there is an amazing extension for Visual Studio Code to handle the front matter - and more. It&amp;rsquo;s called &lt;a href="https://frontmatter.codes/">Front Matter&lt;/a>. It&amp;rsquo;s a headless CMS and works with every static site generator, also with Hugo. It allows you to author and preview your content in VS Code and lets you set the front matter with ease. It gives recommendations for SEO and lets you easily preview and insert images into your blog posts. The media dashboard also lets you add metadata to the images so that you can manage the alt tags for them. Best thing: Its open-source. If you like it, consider to sponsor its creator and &lt;a href="https://twitter.com/eliostruyf">give Elio a shout out&lt;/a>.&lt;/p>
&lt;p>&lt;img alt="Front Matter media" src="https://m365princess.com/images/FM-media.png">&lt;/p>
&lt;h4 id="static">static&lt;/h4>
&lt;p>Images need to be in the static folder, I have a subfolder &lt;code>images&lt;/code> in here. If you use Front Matter, you can insert images into your blog post by selecting the &lt;strong>Insert image&lt;/strong> button or link to the image.&lt;/p>
&lt;h4 id="a-theme">a theme&lt;/h4>
&lt;p>If you don&amp;rsquo;t like to create your own theme, you can install a Hugo theme and then adjust it to your needs. I can recommend &lt;a href="https://gethugothemes.com/">getHugoThemes.com&lt;/a>, but there are free themes available on the thing called internet as well. Follow the documentation to install the theme.&lt;/p>
&lt;p>Now its time to actually create your site. Depending on your skills you can copy/paste from example sits of the docs of your theme and then replace with the values you need for navigation, site, content, or write the site from scratch.&lt;/p>
&lt;h2 id="deploy">Deploy&lt;/h2>
&lt;p>If you now want to create a new post, I&amp;rsquo;d recommend to do this with Front Matter. You will need to do this in the &lt;code>contents&lt;/code> folder. Once your blog post is ready to be published, make sure that the &lt;strong>draft&lt;/strong> property is set to &lt;code>false&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>Push your changes towards your stage branch&lt;/li>
&lt;li>Change Site settings on Netlify
&lt;ul>
&lt;li>Build command = &lt;code>hugo&lt;/code>&lt;/li>
&lt;li>Production branch = &lt;code>main&lt;/code> (in case you used an old repository it could also be that the default repository is called &lt;code>master&lt;/code>)&lt;/li>
&lt;li>Branch deploys = &lt;code>Deploy only the production branch&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Merge from &lt;code>stage&lt;/code> into main which deploys your site&lt;/li>
&lt;li>Time for a happy dance&lt;/li>
&lt;/ul>
&lt;p>The beauty of having several branches now is that you have more control - with only one branch each and every push will rebuild your site.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Either way, you can now author and manage your blog post straight from VS Code. You will use git to commit and push your changes to re-deploy your site.&lt;/p>
&lt;p>You can benefit from the entire cool ecosystem around VS Code with all its extensions, great docs and awesome community around.&lt;/p>
&lt;p>Let me know, what you use to blog? If you don&amp;rsquo;t use Hugo, would you now like to migrate?&lt;/p></description></item><item><title>How to deal with many to many relationships in Dataverse</title><link>https://m365princess.com/blogs/deal-relationships-dataverse/</link><pubDate>Thu, 18 Nov 2021 08:39:22 +0000</pubDate><guid>https://m365princess.com/blogs/deal-relationships-dataverse/</guid><description>&lt;p>While building an app that stores data in Dataverse, I came across a not easy to resolve problem. If you already follow me for a bit longer, you may assume that this is about &lt;a href="https://provisionGenie.com">ProvisionGenie&lt;/a>, and so I will use this use case of provisioning Microsoft Teams teams and storing data about that as an example. To explain the issue, we first need to understand some basics, which I will cover here first:&lt;/p>
&lt;h2 id="what-is-dataverse">What is Dataverse&lt;/h2>
&lt;p>&lt;img alt="Overview on Power Platform" src="https://m365princess.com/images/platform.png">&lt;/p>
&lt;p>Dataverse is a is a secure and scalable SaaS data service, that sits right in Power Platform. Datverse&amp;rsquo;s database is Azure SQL, and often, people refer to Dataverse just as &amp;lsquo;a database&amp;rsquo;, but it is so much more:&lt;/p>
&lt;p>&lt;img alt="Dataverse as a SaaS" src="https://m365princess.com/images/dataverse-saas.png">&lt;/p>
&lt;p>I will not go into full detail in this blog post, but cover something that people with a background in Microsoft 365/SharePoint might not be aware of:&lt;/p>
&lt;h2 id="how-does-dataverse-distinguish-from-sharepoint-lists-and-what-makes-it-a-real-database">How does Dataverse distinguish from SharePoint lists and what makes it a &amp;lsquo;real database&amp;rsquo;?&lt;/h2>
&lt;p>In Dataverse, we store data in tables, we can either use predefined ones or we can creates our own tables. We can choose from different kind of column types to store data just as needed. The beautiful thing that get our Dynamics 365 colleagues excited is, that Dataverse can serve as a &amp;lsquo;relational database&amp;rsquo;, which means that we can create all kinds of relationships between data, which gives us a better overview on data as we can put data into context.&lt;/p>
&lt;p>Also, we have role-based access control (RBAC), which means that we can granularly control who can view, edit, delete etc. data, while this isn&amp;rsquo;t possible with SharePoint lists. If we use a list as data source for a Power Apps, we need to share the entire list with all users of that application, which means that users can even bypass the app and manipulate and delete data directly on the SharePoint site.&lt;/p>
&lt;p>Now you may ask, what is it about relationships in Dataverse that makes it so special?&lt;/p>
&lt;h2 id="what-kind-of-relationships-do-exist-in-dataverse">What kind of relationships do exist in Dataverse?&lt;/h2>
&lt;p>&lt;img alt="relationships" src="https://m365princess.com/images/relationships.jpg">&lt;/p>
&lt;h3 id="1-to-many-relationship">1-to-many relationship&lt;/h3>
&lt;p>In a 1:N (1-to-many) relationship we associate a (1) row of a table to many other rows in a related table with a lookup column. We can see a list of the related rows that are associated with our primary table.&lt;/p>
&lt;p>You will come across the term &amp;lsquo;N:1(many-to-1)&amp;rsquo; as well - it is the same thing as a 1:N relationship- just viewed from the related table, not from the parent/primary table.&lt;/p>
&lt;p>As an example, please imagine a table &lt;code>Teams Requests&lt;/code> and another related table &lt;code>Teams Channels&lt;/code>. Each Team can have many channels, but a channel can be only associated with one Team (represented in the &lt;code>Teams Request&lt;/code> table). This means, that we need to have a 1:N relationship between &lt;code>Teams Requests&lt;/code> table and the &lt;code>Teams Channels&lt;/code> table. We reference this like this:&lt;/p>
&lt;p>&lt;img alt="get related channels" src="https://m365princess.com/images/TeamsChannel1N.png">&lt;/p>
&lt;p>We get the correct environment, fetch the table &lt;code>Team Channels&lt;/code> and filter by &lt;code>TeamsRequestId&lt;/code> so that only the related channels to that Teams request will be returned.&lt;/p>
&lt;h3 id="many-to-many-relationships">Many-to-many relationships&lt;/h3>
&lt;p>An N:N (many-to-many) relationship depends on a special relationship table (intersect table), so that many rows of one table can be related to many rows of another table.&lt;/p>
&lt;p>We can see a list of all rows in the related table that our primary table is associated with.&lt;/p>
&lt;p>As an example, please image now the &lt;code>Teams Requests&lt;/code> table again that needs to be related to a &lt;code>Teams Users&lt;/code> table. In this &lt;code>Teams Users&lt;/code> table we want to store information on members and owners of the teams in the &lt;code>Teams Requests&lt;/code> table.&lt;/p>
&lt;p>A Teams can have many users as members, and each user can be a member of different Teams.&lt;/p>
&lt;p>Also, a team can have many users as owners and these users can be owners of several different teams.&lt;/p>
&lt;p>This means, that we need to have two N:N relationships between &lt;code>teams Requests&lt;/code> table and &lt;code>Teams Users&lt;/code> table.&lt;/p>
&lt;p>&lt;img alt="N:N relationships" src="https://m365princess.com/images/TeamsRequests-TeamsUser.png">&lt;/p>
&lt;p>Now the intersection tables come into play: They make sure that we can associate many rows of the related table to the primary table.&lt;/p>
&lt;p>In Dataverse, we don&amp;rsquo;t get to see these intersection tables. but we can customize their name:&lt;/p>
&lt;p>&lt;img alt="name of the intersection table" src="https://m365princess.com/images/TeamsRequests-TeamsUserNN.png">&lt;/p>
&lt;h2 id="how-can-you-reference-many-to-many-relationships-in-azure-logic-apps">How can you reference Many-to-Many Relationships in Azure Logic Apps?&lt;/h2>
&lt;p>Now that the distinguishing element between an Owner of a Team and a Member of that Team is in the relationship, we need to reference that intersection table in a Logic App flow in order to fetch the right rows from the Teams User table to add them with the correct role to the Team that we want to provision.&lt;/p>
&lt;p>&lt;img alt="List rows for Members" src="https://m365princess.com/images/ListRowsForMembers.png">&lt;/p>
&lt;p>In order to do so, we select the correct environment, and type the name of the relationship table followed by a &lt;code>set&lt;/code>, then we filter for the correct Teams Request ID so that only members for that specific team will be returned.&lt;/p>
&lt;p>&lt;img alt="List rows for Owners" src="https://m365princess.com/images/ListRowsForOwners.png">&lt;/p>
&lt;p>The very same applies to the relationship table for the owners.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Before you build your Power Platform solution, its absolutely worth it to spend a couple of thoughts on the data model. If you&lt;/p>
&lt;ul>
&lt;li>need more relationships than a simple lookup column&lt;/li>
&lt;li>need granular control who can access which rows&lt;/li>
&lt;li>a performant place to store data&lt;/li>
&lt;li>a highly scalable solution&lt;/li>
&lt;/ul>
&lt;p>then its very likely, that Dataverse is a service that you should consider.&lt;/p>
&lt;p>In order to take full advantage of Dataverse&amp;rsquo;s capabilities in terms of being a &amp;lsquo;relational database&amp;rsquo; its worth it to understand what is an intersection table and how you can reference it. Let me know what you think in the comments&lt;/p></description></item><item><title>How 3 makers, 2 devs and a princess came together to save kittens for a hackathon</title><link>https://m365princess.com/blogs/2021-10-23-how-2-makers-2-devs-and-a-princess-came-together-to-save-kittens-for-a-hackathon/</link><pubDate>Sat, 23 Oct 2021 10:40:45 +0000</pubDate><guid>https://m365princess.com/blogs/2021-10-23-how-2-makers-2-devs-and-a-princess-came-together-to-save-kittens-for-a-hackathon/</guid><description>&lt;h1 id="how-3-makers-2-devs-and-a-princess-came-together-to-save-kittens-for-a-hackathon">How 3 makers, 2 devs and a princess came together to save kittens for a hackathon&lt;/h1>
&lt;h2 id="the-story-michael">The Story (Michael)&lt;/h2>
&lt;p>Just before Southcoast Summit 2021 got started, the organizers hosted the &lt;strong>Automate Everything - SS2021 Hackathon&lt;/strong> where every solution revolves around Flic buttons. Wait, you don&amp;rsquo;t know what a Flic button is? It&amp;rsquo;s basically a wireless smart button that lets you control devices, apps and services. Push once, twice or hold the button and let each variant trigger a different action. There are multiple use cases in business but also in personal life in which Flic buttons make your life easier. Check out the &lt;a href="https://Flic.io/">Flic homepage&lt;/a> to learn more.&lt;/p>
&lt;p>&lt;img alt="Logo of Petrol Push" src="https://m365princess.com/images/PetrolPushTitle.png">&lt;/p>
&lt;p>Meet Petrol Push. A modern day organization that has a clear mission: Save kittens. There are hundreds of kittens all over Britain that get stuck in trees, get lost within the urban jungle or need help in any other kind of way. Luckily Petrol Push underholds a huge fleet of volunteers to rescue kittens every day.&lt;/p>
&lt;p>&lt;strong>the challenge&lt;/strong>
As you may know there is a petrol shortage happening right now and of course you wonder, how can Petrol Push keep up their noble mission? Flic Buttons and the Microsoft Power Platform gave them the ability to come up with a solutions to help all their volunteers in their day to day work.&lt;/p>
&lt;p>&lt;strong>the solution&lt;/strong>
Every Petrol Push car got a Flic button installed and whenever Petrol Push volunteers pass a gas station, they can indicate with a push of a button, whether the gas station has fuel available or not. This information gets stored on a map so every Petrol Push employee knows where fuel is available and where it&amp;rsquo;s not. This way the volunteers can keep their focus on their mission. They don&amp;rsquo;t need to drive around searching for fuel or worry where to gas up. The community of volunteers takes care of that.&lt;/p>
&lt;p>Petrol Push cares deeply about their volunteers so they don&amp;rsquo;t want to put them in danger in any way. That&amp;rsquo;s why this solution comes with a little extra. Petrol Push workers don&amp;rsquo;t have to check the map over and over again to see whether anything has changed. If one of the volunteers found a gas station where fuel is available, the button gets pushed and the fleet will get notified with a song. That way the drivers know when to check the map for updates.&lt;/p>
&lt;p>Within these times it might happen that our drivers get in trouble themselves, run out of gas, have a flat tire or something else. Once again, Petrol Push cares about their volunteers deeply so the Flic button provides the opportunity to call other volunteers on the road for help. Once again with a song, so no other driver needs to check their phone. The position gets indicated on the map though, so that help can be arranged quickly. It&amp;rsquo;s only the supervisor that gets an additional text message in order to provide further information.&lt;/p>
&lt;p>&lt;em>Note: you will probably know by know, but this use case exemplifies the ability to combine geographic location with notifications that are not based on text. In this way, we want to draw attention to how versatile Power Platform solutions are and we also want to think about the people who can only use devices in a limited way. Please use this use case to customize it to your needs. And always remember, only as a community we are strong, so let&amp;rsquo;s be inclusive.&lt;/em>&lt;/p>
&lt;p>Now, let&amp;rsquo;s dive into details and see how this solution actually works&lt;/p>
&lt;h2 id="the-flic-and-the-flow-tomasz">The Flic and the flow (Tomasz)&lt;/h2>
&lt;p>In a big picture, the flow was built to get information about location of a driver who triggered it, next to lookup details of the closest petrol station (by using &lt;strong>Azure Maps API&lt;/strong>). Finally to save the station&amp;rsquo;s data together with status into database, so later it can be displayed with a proper color of a pin, inside the app. But in details, it&amp;rsquo;s much more interesting.&lt;/p>
&lt;p>&lt;img alt="Part 1 of the flow" src="https://m365princess.com/images/PetrolFlow%20-%20part1.png">&lt;/p>
&lt;p>The flow can be triggered by any driver (1️⃣). Also, for any Flic event, but that will be described later. Next, bot looks up details of the button itself (2️⃣), to get its owner (3️⃣). This information will be later used to record data along with information about the driver.&lt;/p>
&lt;p>&lt;img alt="Part 2 of the flow" src="https://m365princess.com/images/PetrolFlow%20-%20part2.png">&lt;/p>
&lt;p>Next the flow calls &lt;strong>Azure Maps custom connector&lt;/strong> via a dedicated child flow (1️⃣), by passing latitude and longitude of a driver&amp;rsquo;s location. Coordinates are obtained using GPS from driver&amp;rsquo;s phone that is paired with Flic button. Obviously this should be done using the action directly within the parent flow, however for some &lt;em>unknown reasons&lt;/em> we were facing an issue while saving process with the action inside, so we decided to move it into a child flow. Don&amp;rsquo;t judge 😁
Data returned by the child flow, that represents details about the nearest petrol station is then parsed (2️⃣).&lt;/p>
&lt;p>Finally bot using postal code is filtering existing stations&amp;rsquo; data to get a match (3️⃣). This is done using ODATA expression: &lt;code>woi_postalcode eq '@{first(body('Parse_JSON')?['results'])?['address']?['extendedPostalCode']}'&lt;/code>. Then it saves its row ID into variable (4️⃣). Naturally, if there&amp;rsquo;s no station for the given postal code, variable will be empty. &lt;strong>We also made an assumption&lt;/strong>, that there can be one station for a given postal code 🤷‍♂️😀&lt;/p>
&lt;p>&lt;img alt="Part 3 of the flow" src="https://m365princess.com/images/PetrolFlow%20-%20part3.png">&lt;/p>
&lt;p>Process now checks, if station&amp;rsquo;s row ID is empty (1️⃣) - if yes, it means it has to be created. Creation (2️⃣) of the record takes all the details returned from Azure Maps API, like full address, station name, lat and lon, information about driver who reported it and finally - the postal code. After that row ID of the created station is being saved into variable.&lt;/p>
&lt;p>&lt;img alt="Part 4 of the flow" src="https://m365princess.com/images/PetrolFlow%20-%20part4.png">&lt;/p>
&lt;p>Now process moves to check what kind of action occurred on the Flic. There are 3 possible activities:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Single click&lt;/strong> - means that there&amp;rsquo;s petrol on the station,&lt;/li>
&lt;li>&lt;strong>Double click&lt;/strong> - means that there&amp;rsquo;s no petrol on the station,&lt;/li>
&lt;li>&lt;strong>Long press&lt;/strong> - means there&amp;rsquo;s an issue and driver requires assistance.&lt;/li>
&lt;/ul>
&lt;p>To check what action occurred, we are using switch action (1️⃣). For each branch process executes the same actions, just with different statuses. First, bot creates an entry in &lt;strong>Activities table&lt;/strong> (2️⃣), to record latest status (to one from Petrol, No petrol, Issue) for the station together with driver details who reported it.&lt;/p>
&lt;p>After that is done, it updates status (again to one from Petrol, No petrol, Issue) of the station record itself (3️⃣). Then it saves created activity record OData id into a variable. And finally it relates records (4️⃣) - petrol station together with the created activity record.&lt;/p>
&lt;p>&lt;img alt="Part 5 of the flow" src="https://m365princess.com/images/PetrolFlow%20-%20part5.png">&lt;/p>
&lt;p>What is also worth to mention is that the whole process is built using the &lt;strong>try-catch pattern&lt;/strong>. All actions that are executed in terms of the business logic are stored in the &amp;ldquo;Try&amp;rdquo; scope (1️⃣). If anything fails within the scope, it is caught by the &amp;ldquo;Catch&amp;rdquo; scope (2️⃣), that has it&amp;rsquo;s &amp;ldquo;Run after&amp;rdquo; settings configured to only be executed if previous actions fails, times out or is skipped.&lt;/p>
&lt;p>Process in the &amp;ldquo;Catch&amp;rdquo; scope first filters (3️⃣) results of the &amp;ldquo;Try&amp;rdquo; scope, using the expression &lt;code>result('Try')&lt;/code> to leave only those entries which contain information about errors: &lt;code>@equals(createArray('Failed', 'TimedOut'), '')&lt;/code>. Next for each such record (4️⃣) it is adding information about the details to a string variable. Finally, variable&amp;rsquo;s contents is sent to admin as a notification (5️⃣) and the whole process ends up with &amp;ldquo;Failed&amp;rdquo; outcome.&lt;/p>
&lt;h2 id="show-me-something-beautiful---the-canvas-app-carmen">Show me something beautiful - The canvas app (Carmen)&lt;/h2>
&lt;p>With the data stored in Dataverse, the canvas app can be created to display the available information and inform the people where they can find fuel. The canvas app consists of a header (with the company logo, name and refresh icon) and a map control.&lt;/p>
&lt;p>We are using the built-in map control, which allows us to display the gas stations with their appropriate color, automatically center on the user&amp;rsquo;s current location and display additional information about each gas station when selecting the location pin.&lt;/p>
&lt;p>To get the location pins on the map, we added the Dataverse table as a source in the &lt;strong>Items&lt;/strong> property of the map control. We are currently not doing any filtering, but this could be added if needed. The latitude, longitude, labels and colors is each contained in a specific column within the data source. These are provided as values for the following properties (where the text between quotes is the name of the column in the Dataverse table):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ItemsLabels&lt;/strong> = &lt;code>&amp;quot;woi_name&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>ItemsLatitudes&lt;/strong> = &lt;code>&amp;quot;woi_latitude&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>ItemsLongitudes&lt;/strong>= &lt;code>&amp;quot;woi_longitude&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>ItemsColors&lt;/strong> = &lt;code>&amp;quot;woi_color&amp;quot;&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The &lt;code>woi_colors&lt;/code> columns is defined as a calculated column that is influenced by the value of the &lt;code>Petrol Status&lt;/code> column in the same table. &lt;code>Petrol Status&lt;/code> contains the last known status of fuel at the respective station. The colors are defined as hex values with the following mapping:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Last Known Status&lt;/th>
&lt;th style="text-align: left">Color&lt;/th>
&lt;th style="text-align: left">Color name&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">Petrol&lt;/td>
&lt;td style="text-align: left">&amp;ldquo;#66FF00&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Light Green&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">No petrol&lt;/td>
&lt;td style="text-align: left">&amp;ldquo;#FF0000&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Red&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">Issue&lt;/td>
&lt;td style="text-align: left">&amp;ldquo;#FFBF00&amp;rdquo;&lt;/td>
&lt;td style="text-align: left">Amber&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The color of the grouped pins is defined by the &lt;strong>PinColor&lt;/strong> property of the map control. It is set to &lt;code>Green&lt;/code>, which is a darker color than the green used for the stations with fuel.&lt;/p>
&lt;p>When a pin is selected, the info card is shown. This is defined by setting the &lt;strong>InfoCards&lt;/strong> property of the map to &lt;code>'Microsoft.Map.InfoCards'.OnClick&lt;/code>. The fields that are shown on the info card are defined by editing the &lt;strong>Fields&lt;/strong> in the properties pane of the map. Four fields are shown on the info card:&lt;/p>
&lt;ul>
&lt;li>Name&lt;/li>
&lt;li>Address&lt;/li>
&lt;li>Postal code&lt;/li>
&lt;li>Modified on (to know when the station&amp;rsquo;s status was last updated)&lt;/li>
&lt;/ul>
&lt;p>This can be seen on the below screenshot.
&lt;img alt="Properties pane of the map control with fields expanded and showing &amp;ldquo;name&amp;rdquo;, &amp;ldquo;address&amp;rdquo;, &amp;ldquo;postal code&amp;rdquo; and &amp;ldquo;modified on&amp;rdquo;" src="https://m365princess.com/images/App-MapFields.png">&lt;/p>
&lt;p>The resulting app shows a map with all identified gas stations and their last known status, indicated with the color of the pin. Selecting a specific gas station provides the user with more information on that station.
&lt;img alt="Petrol Push app result with info card open" src="https://m365princess.com/images/App-Details.png">&lt;/p>
&lt;h2 id="we-need-a-real-map---the-custom-connector-to-azure-maps-lee">We need a real map - The custom connector to Azure Maps (Lee)&lt;/h2>
&lt;p>A key part of the solution is populating a list of petrol stations and their status based on presses of the Flic button. We initially looked to use the built-in Bing maps Power Automate connector and actions to find the current address when a Flic button was used. However, this would return the nearest address, which is not necessarily a petrol station (e.g. it could be a house on the opposite side of the street which is deemed nearer).&lt;/p>
&lt;p>To work around this, we created an Azure Maps resource in Azure. Azure Maps can return a list of addresses within a certain radius that fit a particular &amp;ldquo;POI (point of interest) category&amp;rdquo; - in this case a petrol station. Using the &lt;code>subscription-key&lt;/code> (API key) from the Azure Maps resource, we were able to create a custom connector in Power Automate and query for the nearest petrol stations to the longitude and latitude when the Flic button was pressed.&lt;/p>
&lt;h2 id="bring-me-the-vibes---the-spotify-connector-yannick">Bring me the vibes - The Spotify connector (Yannick)&lt;/h2>
&lt;p>We like to celebrate victories and help each other in times of need, and what better way than use music for this? We have a sound system in the office connected to Spotify so let&amp;rsquo;s use that to keep everyone updated on things that happen on the road!
A new Power Automate flow will trigger every time a new petrol station status is logged, excluding when no petrol was available. In the case someone found Petrol at a gas station, we get super excited for our colleague and play &lt;a href="https://open.spotify.com/track/6FUwPb4mGlUDbx42uspXaZ?si=127b17aad3f2448d">Fuel by Metallica&lt;/a> in the office to have a small party. When someone gets in trouble, for whatever reason, we play &lt;a href="https://open.spotify.com/track/0R8P9KfGJCDULmlEoBagcO?si=97cdd52cd87449c5">Trouble by Coldplay&lt;/a> (so we know we need to rush to rescue) and a text message is sent to the manager.&lt;/p>
&lt;p>Integrating with Spotify isn&amp;rsquo;t too difficult (the API is well-documented) but requires the creation of a custom connector with following API actions:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/get-a-users-available-devices">Get a User&amp;rsquo;s Available Devices&lt;/a>: fetch a list of all devices currently connected to the Spotify service&lt;/li>
&lt;li>&lt;a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/start-a-users-playback">Start/Resume a User&amp;rsquo;s Playback&lt;/a>: play a song on a specific device&lt;/li>
&lt;/ul>
&lt;p>When combining both, we can first fetch all connected devices and then filter them on the device id of our office sound system. If the device is connected, we can play the appropriate song for the occasion with the second API call.&lt;br>
And lastly, for the text message we&amp;rsquo;ll use Twilio. Luckily they have an existing connector within Power Automate so it&amp;rsquo;s only a matter of registering for a Twilio account, getting a number to send messages from and configuring the action in our flow.&lt;/p>
&lt;h2 id="the-princess-and-the-push-luise">The princess and the push (Luise)&lt;/h2>
&lt;p>Straight from the beginning of the hackathon, we took care of documenting our architecture decisions and how we would implement them. We set up a &lt;a href="https://github.com/LuiseFreese/HacksouthCoastSummit">GitHub repository&lt;/a>, invited everyone in the team so they could commit their files. We continued to document all major steps so that everyone could use this as a reference to explain our solution, although each member was only in charge of their workload. Getting all information and documenting while building ensured accuracy but also gave an opportunity to think through the app and reflect on decisions.&lt;/p>
&lt;p>Documentation includes screenshots of the flows, explains the data model and environment variables. We also published the solution itself in this repository to give community the chance to play with our app.&lt;/p>
&lt;h2 id="what-can-we-learn-from-this-epic-quest-everybody">What can we learn from this epic quest? (everybody)&lt;/h2>
&lt;p>As a group, we discussed the hackathon for quite a while even after it had ended, and we came up with, for us, four important lessons this experience has taught us.&lt;/p>
&lt;h3 id="1-do-one-thing-the-right-way-instead-of-a-million-things-in-a-messy-way">1. Do one thing the right way, instead of a million things in a messy way&lt;/h3>
&lt;p>What helped us build this solution in a short timespan, was that each person of the team was responsible for a specific part of the solution. There was no context switching between the app studio and building the cloud flow for example. Instead, we made some agreements in the beginning of the day and let each other know verbally and in the documentation if anything needed to change. This allowed all of us to focus on their own part, resulting in finished pieces to the puzzle.&lt;/p>
&lt;h3 id="2-take-care-of-documentation">2. Take care of documentation&lt;/h3>
&lt;p>Since development was decentralized, it was important we could keep each other up to date on what we were doing. Therefore, we documented from the start. Since we were working against the clock, we had one person who constantly went around the table to see what each of us was working on and to make sure it was captured in the documentation. After the individual pieces were finished, this allowed us to piece them together more easily.&lt;/p>
&lt;h3 id="3-1--1--3">3. 1 + 1 = 3&lt;/h3>
&lt;p>Or in our case 6 x 1 = 10 (or something). Each of us has a different background, no two are the same. Because of this, we were able to share different perspectives and we were able to find the most efficient way to create the different pieces of the puzzle: e.g. Azure maps for station identification, canvas apps for a quick user interface and cloud flows for logic. Since we were not limited to one area of expertise, our solution combines the best of different worlds. In the process, we all learned from each other, either technical skills or an approach how to tackle something. And since we were all eager to learn from and share with each other, we had a lot of fun doing it. 👍🏼👍🏼&lt;/p>
&lt;h3 id="4-we-are-all-developers">4. We are all developers&lt;/h3>
&lt;p>Each of us is building or creating something on a daily basis, be that using no-code, low-code or code-first platforms and tools. During the hackathon, we realized that our commonalities are more important than our differences. We share a common problem-solving and solution-oriented approach. We can define logic and we do it in very similar terms (if - then - else, anyone?). We can conceptualize solutions and explain them to each other. And then each of us can find some way using their own tools to build that solution. This is what makes us developers, not the language or tool set we build things in, but the approach and mindset we share. All of us are developers, and you can be one too.&lt;/p>
&lt;p>&lt;img alt="The team" src="https://m365princess.com/images/workingOnIt.jpg">&lt;/p>
&lt;p>This blog post and hackathon team is a collaboration of&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/carmenysewijn">Carmen Ysewijn&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/lee_ford">Lee Ford&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/michaelroth42">Michael Roth&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/tomszposzytek">Tomasz Poszytek&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://twitter.com/yannickreekmans">Yannick Reekmans&lt;/a>&lt;/li>
&lt;li>and me :-)&lt;/li>
&lt;/ul></description></item><item><title>ProvisionGenie - an open-source provisioning engine for Microsoft Teams</title><link>https://m365princess.com/blogs/2021-09-29-provisiongenie-a-teams-provisioning-engine/</link><pubDate>Wed, 29 Sep 2021 18:33:47 +0000</pubDate><guid>https://m365princess.com/blogs/2021-09-29-provisiongenie-a-teams-provisioning-engine/</guid><description>&lt;h2 id="once-upon-a-time">Once upon a time&lt;/h2>
&lt;p>I teamed up with my friend and partner in crime &lt;a href="https://twitter.com/CarmenYsewijn">Carmen Ysewijn&lt;/a>. We both work as Power Platform developers and Microsoft 365 consultants, and got both tired of doing the same things over and over again:&lt;/p>
&lt;p>Organizations would roll out Microsoft Teams, and either not make too much fuzz on meeting user&amp;rsquo;s needs and just assume that &lt;em>changing working behavior&lt;/em> would magically happen or they would get a business consultant (us) who would then sit with each and every single team and explain&lt;/p>
&lt;ul>
&lt;li>why cascading folder structures in SharePoint are a bad idea&lt;/li>
&lt;li>how to keep track of what really matters&lt;/li>
&lt;li>how we do not drown in information and notification overload&lt;/li>
&lt;li>how to excel at the art of teamwork&lt;/li>
&lt;/ul>
&lt;h3 id="our-idea">Our idea&lt;/h3>
&lt;p>Now, when #poweraddicts really get tired of doing something, they will spend a huge amount of time on automating this. Our idea was to create a process, that&lt;/p>
&lt;ol>
&lt;li>walks users through these considerations&lt;/li>
&lt;li>ask them how the &amp;ldquo;team of their dreams&amp;rdquo; would look like:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Which channels would they need?&lt;/li>
&lt;li>Which columns in an additional SharePoint library could support their work?&lt;/li>
&lt;li>How about Microsoft lists?&lt;/li>
&lt;li>What would they use to keep track of their tasks?&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>as a result provisions the Team for them.&lt;/li>
&lt;/ol>
&lt;p>We agreed on making this both open-source and enterprise-grade, which affected all of our&lt;/p>
&lt;h2 id="architecture-decisions">Architecture decisions&lt;/h2>
&lt;ul>
&lt;li>For our UI, we chose a Power Apps canvas app - following the design guidelines of the Teams UI toolkit so that our app looks and feels like a Teams native app.&lt;/li>
&lt;li>As our database, we decided that Dataverse would be the best option to log all requests in&lt;/li>
&lt;li>We created security roles in Dataverse to ensure that data wouldn&amp;rsquo;t get over-exposed&lt;/li>
&lt;li>For automation, we went with Azure Logic Apps, as this gave us not only a better developer experience, but would also make deployment easier&lt;/li>
&lt;li>Authentication in our Logic Apps is handled by a Managed Identity&lt;/li>
&lt;/ul>
&lt;p>We also needed to make some hard decisions, like&lt;/p>
&lt;h3 id="no-microsoft-planner-provisioning">No Microsoft Planner provisioning&lt;/h3>
&lt;p>As the Microsoft Planner API doesn&amp;rsquo;t support application-level permissions, we chose to not provision a Planner part with ProvisionGenie until Planner comes up with a fully working API. This meant, that we needed to come with an alternative for user to manage their tasks. Optionally, we provision a Microsoft List, that mimics the experience of Planner and introduce users to the gallery view.&lt;/p>
&lt;h3 id="deleting-the-teams-wiki">Deleting the Teams Wiki&lt;/h3>
&lt;p>The Teams Wiki isn&amp;rsquo;t everyone&amp;rsquo;s darling and we don&amp;rsquo;t like it for some reasons- but the fact why we decided to delete it as part of the provisioning process for all channels is that, once the Wiki is (accidentally?) removed, all content is hard-deleted.&lt;/p>
&lt;h2 id="making-provisiongenie-a-deployable-solution">Making ProvisionGenie a deployable solution&lt;/h2>
&lt;p>&lt;img alt="ProvisionGenie xkcd" src="https://m365princess.com/images/xkcd.png">&lt;/p>
&lt;p>To make this solution available, it was not enough to only provide the (opaque) .zip file for the canvas app. We provide&lt;/p>
&lt;ul>
&lt;li>a Power Platform solution including
&lt;ul>
&lt;li>1 canvas app&lt;/li>
&lt;li>5 Dataverse tables&lt;/li>
&lt;li>2 security roles&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>5 Logic Apps, ready to be deployed with ARM template files, including
&lt;ul>
&lt;li>authentication handled by a Managed Identity&lt;/li>
&lt;li>a script to deploy the files&lt;/li>
&lt;li>a script to handle the Microsoft Graph permissions for the Managed Identity&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>proper documentation&lt;/li>
&lt;/ul>
&lt;p>As a result, the app looks like this:&lt;/p>
&lt;p>&lt;img alt="ProvisionGenie in Teams" src="https://m365princess.com/images/TeansResult.png">&lt;/p>
&lt;p>We open-sourced ProvisionGenie 🧞 and just shipped our first release, you can find the repository here - with guidance how to get the app and how to contribute to it: &lt;a href="https://github.com/ProvisionGenie/ProvisionGenie">https://github.com/ProvisionGenie/ProvisionGenie&lt;/a>&lt;/p>
&lt;p>We would love 💜 to get your feedback and improve our solution!&lt;/p>
&lt;p>Special thanks go to&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/YannickReekmans">Yannick Reekmans&lt;/a>, for debugging, guidance, emotional support and introducing me to Carmen&lt;/li>
&lt;li>&lt;a href="https://twitter.com/MichaelRoth42">Michael Roth&lt;/a> for all ProvisionGenie visuals 💜&lt;/li>
&lt;li>&lt;a href="https://twitter.com/lee_ford">Lee Ford&lt;/a> for listening to my endless rants during debugging&lt;/li>
&lt;/ul></description></item><item><title>4 ways to level up your Power Automate flows</title><link>https://m365princess.com/blogs/level-up-flows/</link><pubDate>Sat, 18 Sep 2021 13:26:31 +0000</pubDate><guid>https://m365princess.com/blogs/level-up-flows/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>A while back, I wrote about &lt;a href="https://www.m365princess.com/blogs/2021-02-23-how-to-use-a-custom-connector-in-power-automate/">How to use a custom connector in Power Automate&lt;/a> showing how easy you can create a connector to a cloud service that is not already in the very long list of connectors in Power Automate. I chose to create a connector for Spotify and connected a &lt;code>Get_Current_Song&lt;/code> action with an IOT button and twitter. As a result, information about the song I would be listening to would be tweeted.&lt;/p>
&lt;p>Now I stumbled upon a &lt;a href="https://www.loryanstrant.com/2021/09/16/how-to-display-the-currently-playing-spotify-track-as-your-teams-status-message/">really great blog post&lt;/a> by fellow MVP Loryan Strant who also used this Spotify connector to change the pinned message of your status in Microsoft Teams. To get the most value out of this post, go read Loryans post first- it is written with great clarity and also I love this guys&amp;rsquo; taste of music 🎶! Also please understand his flow first. I love the idea and creativity! The result of such a flow looks like this:&lt;/p>
&lt;p>&lt;img alt="Teams status" src="https://m365princess.com/images/Teams-status.png">&lt;/p>
&lt;p>While some would debate if this flow is necessary, I feel it shows that custom connectors are a great way to extend Microsoft 365. Also: #MusicWasMyFirstLove - case closed :-)&lt;/p>
&lt;p>However when reading this blog post, I saw some patterns that I often see in flows and that could be improved - and as I could already learn so much from Loryan, this time I hope to return the favor :-)&lt;/p>
&lt;p>Loryan created 13 actions and as I seem to be just more lazy than he is, therefore I thinned out his awesome idea to just 5-6 actions: This is what it looks like:&lt;/p>
&lt;p>&lt;img alt="overview" src="https://m365princess.com/images/flow-overview.png">&lt;/p>
&lt;p>The result is about the same - just that I display also a message if I am currently not listening to music (yes, this happens!)&lt;/p>
&lt;h2 id="parse-json">Parse JSON&lt;/h2>
&lt;p>First thing I wanted get rid of was the Parse JSON action. While it is super powerful and lets you easily access properties of objects that you get as response, it is unnecessary sometimes: We can also write the flow without it if we take a look on how we can select properties and return their values in expressions.&lt;/p>
&lt;p>To be successful with that, it is crucial to understand the JSON schema of the response we are interested in. Easiest way to achieve that:&lt;/p>
&lt;p>a) copy the body of the output of that action, paste it into a code editor - I work with Visual Studio Code
b) we make sure that we select &lt;code>JSON&lt;/code> as language - VS Code will then color everything nicely for us and highlight beginning and ends of objects for example
c) we have a look at the code. For the sake of better readability - this schema is about 450 lines long, I already collapsed two arrays called &lt;code>available markets&lt;/code> - it&amp;rsquo;s a long list of country codes in which a particular song is available. We don&amp;rsquo;t need it here. If you aim to rebuild this, its highly recommended to copy the code from YOUR output, not from this blog post, as I shortened the code.&lt;/p>
&lt;pre>&lt;code> {
&amp;quot;timestamp&amp;quot;: 1631969547352,
&amp;quot;progress_ms&amp;quot;: 85903,
&amp;quot;item&amp;quot;: {
&amp;quot;album&amp;quot;: {
&amp;quot;album_type&amp;quot;: &amp;quot;album&amp;quot;,
&amp;quot;artists&amp;quot;: [
{
&amp;quot;external_urls&amp;quot;: {
&amp;quot;spotify&amp;quot;: &amp;quot;https://open.spotify.com/artist/3CQIn7N5CuRDP8wEI7FiDA&amp;quot;
},
&amp;quot;href&amp;quot;: &amp;quot;https://api.spotify.com/v1/artists/3CQIn7N5CuRDP8wEI7FiDA&amp;quot;,
&amp;quot;id&amp;quot;: &amp;quot;3CQIn7N5CuRDP8wEI7FiDA&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;Run–D.M.C.&amp;quot;,
&amp;quot;type&amp;quot;: &amp;quot;artist&amp;quot;,
&amp;quot;uri&amp;quot;: &amp;quot;spotify:artist:3CQIn7N5CuRDP8wEI7FiDA&amp;quot;
}
],
&amp;quot;available_markets&amp;quot;: [
],
&amp;quot;external_urls&amp;quot;: {
&amp;quot;spotify&amp;quot;: &amp;quot;https://open.spotify.com/album/7AFsTiojVaB2I58oZ1tMRg&amp;quot;
},
&amp;quot;href&amp;quot;: &amp;quot;https://api.spotify.com/v1/albums/7AFsTiojVaB2I58oZ1tMRg&amp;quot;,
&amp;quot;id&amp;quot;: &amp;quot;7AFsTiojVaB2I58oZ1tMRg&amp;quot;,
&amp;quot;images&amp;quot;: [
{
&amp;quot;height&amp;quot;: 640,
&amp;quot;url&amp;quot;: &amp;quot;https://i.scdn.co/image/ab67616d0000b273894ae4df775c6b47438991af&amp;quot;,
&amp;quot;width&amp;quot;: 640
},
{
&amp;quot;height&amp;quot;: 300,
&amp;quot;url&amp;quot;: &amp;quot;https://i.scdn.co/image/ab67616d00001e02894ae4df775c6b47438991af&amp;quot;,
&amp;quot;width&amp;quot;: 300
},
{
&amp;quot;height&amp;quot;: 64,
&amp;quot;url&amp;quot;: &amp;quot;https://i.scdn.co/image/ab67616d00004851894ae4df775c6b47438991af&amp;quot;,
&amp;quot;width&amp;quot;: 64
}
],
&amp;quot;name&amp;quot;: &amp;quot;Raising Hell&amp;quot;,
&amp;quot;release_date&amp;quot;: &amp;quot;1986-05-15&amp;quot;,
&amp;quot;release_date_precision&amp;quot;: &amp;quot;day&amp;quot;,
&amp;quot;total_tracks&amp;quot;: 12,
&amp;quot;type&amp;quot;: &amp;quot;album&amp;quot;,
&amp;quot;uri&amp;quot;: &amp;quot;spotify:album:7AFsTiojVaB2I58oZ1tMRg&amp;quot;
},
&amp;quot;artists&amp;quot;: [
{
&amp;quot;external_urls&amp;quot;: {
&amp;quot;spotify&amp;quot;: &amp;quot;https://open.spotify.com/artist/3CQIn7N5CuRDP8wEI7FiDA&amp;quot;
},
&amp;quot;href&amp;quot;: &amp;quot;https://api.spotify.com/v1/artists/3CQIn7N5CuRDP8wEI7FiDA&amp;quot;,
&amp;quot;id&amp;quot;: &amp;quot;3CQIn7N5CuRDP8wEI7FiDA&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;Run–D.M.C.&amp;quot;,
&amp;quot;type&amp;quot;: &amp;quot;artist&amp;quot;,
&amp;quot;uri&amp;quot;: &amp;quot;spotify:artist:3CQIn7N5CuRDP8wEI7FiDA&amp;quot;
},
{
&amp;quot;external_urls&amp;quot;: {
&amp;quot;spotify&amp;quot;: &amp;quot;https://open.spotify.com/artist/7Ey4PD4MYsKc5I2dolUwbH&amp;quot;
},
&amp;quot;href&amp;quot;: &amp;quot;https://api.spotify.com/v1/artists/7Ey4PD4MYsKc5I2dolUwbH&amp;quot;,
&amp;quot;id&amp;quot;: &amp;quot;7Ey4PD4MYsKc5I2dolUwbH&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;Aerosmith&amp;quot;,
&amp;quot;type&amp;quot;: &amp;quot;artist&amp;quot;,
&amp;quot;uri&amp;quot;: &amp;quot;spotify:artist:7Ey4PD4MYsKc5I2dolUwbH&amp;quot;
}
],
&amp;quot;available_markets&amp;quot;: [
],
&amp;quot;disc_number&amp;quot;: 1,
&amp;quot;duration_ms&amp;quot;: 310386,
&amp;quot;explicit&amp;quot;: false,
&amp;quot;external_ids&amp;quot;: {
&amp;quot;isrc&amp;quot;: &amp;quot;USAR19900334&amp;quot;
},
&amp;quot;external_urls&amp;quot;: {
&amp;quot;spotify&amp;quot;: &amp;quot;https://open.spotify.com/track/6qUEOWqOzu1rLPUPQ1ECpx&amp;quot;
},
&amp;quot;href&amp;quot;: &amp;quot;https://api.spotify.com/v1/tracks/6qUEOWqOzu1rLPUPQ1ECpx&amp;quot;,
&amp;quot;id&amp;quot;: &amp;quot;6qUEOWqOzu1rLPUPQ1ECpx&amp;quot;,
&amp;quot;is_local&amp;quot;: false,
&amp;quot;name&amp;quot;: &amp;quot;Walk This Way (feat. Aerosmith)&amp;quot;,
&amp;quot;popularity&amp;quot;: 69,
&amp;quot;preview_url&amp;quot;: &amp;quot;https://p.scdn.co/mp3-preview/c7a8010bbddcd0d793a832de76a24d2cae5ab497?cid=2e75e650d1e74b6a994734ed4aea2ef7&amp;quot;,
&amp;quot;track_number&amp;quot;: 4,
&amp;quot;type&amp;quot;: &amp;quot;track&amp;quot;,
&amp;quot;uri&amp;quot;: &amp;quot;spotify:track:6qUEOWqOzu1rLPUPQ1ECpx&amp;quot;
},
&amp;quot;currently_playing_type&amp;quot;: &amp;quot;track&amp;quot;,
&amp;quot;actions&amp;quot;: {
&amp;quot;disallows&amp;quot;: {
&amp;quot;resuming&amp;quot;: true,
&amp;quot;toggling_repeat_context&amp;quot;: true,
&amp;quot;toggling_repeat_track&amp;quot;: true,
&amp;quot;toggling_shuffle&amp;quot;: true
}
},
&amp;quot;is_playing&amp;quot;: true
}
&lt;/code>&lt;/pre>
&lt;p>&lt;img alt="output" src="https://m365princess.com/images/flow-output.png">&lt;/p>
&lt;p>b) look for the properties you are interested in - for example we want to if a song is playing right now - we will find the &lt;code>is_playing&lt;/code> property, which will either return &lt;code>true&lt;/code> or &lt;code>false&lt;/code>, which makes it perfect to put this into our condition:&lt;/p>
&lt;p>&lt;img alt="condition" src="https://m365princess.com/images/flow-condition.png">&lt;/p>
&lt;p>The expression is &lt;code>outputs('Get_Current_Song')['body']['is_playing']&lt;/code>.&lt;/p>
&lt;p>Why is that? Let&amp;rsquo;s deconstruct this: From the output of the &lt;code>Get_Current_Song&lt;/code>, we are interested in the &lt;code>['body']&lt;/code> and inside of this we want the value of the &lt;code>['is_playing']&lt;/code> property&lt;/p>
&lt;p>Now if we are also interested in the name of the song, we would do a quick search in that file for &lt;code>name&lt;/code> and get four results:&lt;/p>
&lt;ol>
&lt;li>in line 14: this &lt;code>name&lt;/code> property sits in the &lt;code>artists&lt;/code> array, that consists of an &lt;code>album&lt;/code> object and the &lt;code>name&lt;/code> property here refers to the name of the artist of album, not to the name of the song that we are interested in.&lt;/li>
&lt;li>in line 221: this &lt;code>name&lt;/code> property also sits in the &lt;code>album&lt;/code> object and refers to the name of the album.&lt;/li>
&lt;li>line 235: this &lt;code>name&lt;/code> property sits in the &lt;code>artists&lt;/code> object and refers again to the name of the artist.&lt;/li>
&lt;li>finally, in line 432, we find the &lt;code>name&lt;/code> property we were looking for; it sits in the &lt;code>item&lt;/code> property.&lt;/li>
&lt;/ol>
&lt;p>Therefore, we will access the song name with:&lt;/p>
&lt;p>&lt;code>outputs('Get_Current_Song')['body']?['item']?['name]&lt;/code>&lt;/p>
&lt;p>If we now also want to have the name of the artist, we get this with:&lt;/p>
&lt;p>&lt;code>outputs('Get_Current_Song')['body']?['item']?['album]?['artists][0]?['name']&lt;/code>&lt;/p>
&lt;p>Wait, what? These are a lot of properties, so let&amp;rsquo;s slow down for a bit to take a closer look:&lt;/p>
&lt;ol>
&lt;li>we get the &lt;code>Get_Current_Song&lt;/code> action with &lt;code>outputs('Get_Current_Song')&lt;/code>&lt;/li>
&lt;li>now we go ahead and with the &lt;code>?&lt;/code> operator and select the first level property we are interested in: &lt;code>item&lt;/code>&lt;/li>
&lt;li>next up is taking a look inside of the &lt;code>item&lt;/code> property: what do we want to get here? It&amp;rsquo;s the &lt;code>album&lt;/code> property. We do this as before with &lt;code>?&lt;/code> and the name of the property in &lt;code>[]&lt;/code>: &lt;code>?['album']&lt;/code>&lt;/li>
&lt;li>Inside of the &lt;code>album&lt;/code> property we want to get the &lt;code>artists&lt;/code> property and yet again we do this with &lt;code>?&lt;/code> and the name of the property in &lt;code>[]&lt;/code>: &lt;code>?['artists']&lt;/code>&lt;/li>
&lt;li>Now remember that &lt;code>artists&lt;/code> was an array? You can see this by the brackets &lt;code>[]&lt;/code> in the code. We want to return the first element of this array, therefore we put a &lt;code>[0]&lt;/code>. It&amp;rsquo;s a zero, because arrays in JSON are zero-based, which means that the first element of an array has the index 0, the second one has index 1, and so on.&lt;/li>
&lt;li>Now that we returned that first element in the &lt;code>artists&lt;/code> array (it&amp;rsquo;s only one, but Power Automate will yell at you if you don&amp;rsquo;t select just one element and instead return the entire array), we will go ahead and finally select the &lt;code>name&lt;/code> property from it, which refers to the artist.&lt;/li>
&lt;/ol>
&lt;p>You see, it&amp;rsquo;s all about understanding the underlying JSON schema and see, which properties are part of which objects. If you use the Parse JSON action, you don&amp;rsquo;t need to write these expressions, but you face some disadvantages:&lt;/p>
&lt;ol>
&lt;li>you can now select from four &lt;code>name&lt;/code> properties in your dynamic content - and need to select blindly&lt;/li>
&lt;li>You have no clue WHY you get four of those as you don&amp;rsquo;t understand the data structure&lt;/li>
&lt;li>Parse JSON is yet another action which blows up your flow&lt;/li>
&lt;/ol>
&lt;h2 id="unnecessary-apply-to-each-loops">unnecessary Apply-to-each loops&lt;/h2>
&lt;p>You know that moment when you are creating a flow and and out of a sudden Power Automates automatically adds an Apply-to-each for you and you wonder why this happened? Also, you will face some issues later on? Wherever possible it&amp;rsquo;s a good idea to avoid loops that are not necessary.&lt;/p>
&lt;p>The fact that we didn&amp;rsquo;t just parse the JSON output from our &lt;code>Get_Current_Song&lt;/code> action but understood the JSON schema gives us an option to avoid a loop - we did not return an array of (one) artist, that triggered Power Automate to insert an apply-to-each loop, but we only returned the first element of the artist array - this way we don&amp;rsquo;t need to loop over this one-element-array, which means that we got rid of another action!&lt;/p>
&lt;h2 id="variables-and-expressions">variables and expressions&lt;/h2>
&lt;p>Power Automate knows some nice actions for variables - the most important one is &lt;code>initialize variable&lt;/code> - in Power Automate all variables need to be initialized (with or without value) before we can use them.&lt;/p>
&lt;p>Now as we already skipped successfully the Parse JSON action and could also access artist name and song name without the use of variables but in expressions, I want to minimize the other initialize variables and compose actions from Loryans flow:&lt;/p>
&lt;p>Instead of several actions and calculations to&lt;/p>
&lt;p>&amp;ndash; get the timestamp when the song started
&amp;ndash; get the current time
&amp;ndash; add the duration of the currently playing song to the current time,&lt;/p>
&lt;p>we could have one variable called &lt;code>duration&lt;/code> with this expression:
&lt;code>addSeconds(utcnow(),div(sub(outputs('Get_Current_Song')?['body']?['item']?['duration_ms'],outputs('Get_Current_Song')?['body']?['progress_ms']),1000))&lt;/code>&lt;/p>
&lt;p>Explanation:&lt;/p>
&lt;ol>
&lt;li>This adds seconds to utcnow(), which is the current time.&lt;/li>
&lt;li>How many seconds? The return value from the subtraction of the duration in milliseconds &lt;code>['duration_ms']&lt;/code> minus the &lt;code>['progress_ms']&lt;/code> current progress in milliseconds&lt;/li>
&lt;li>With the div function this value is divided by 1000 as we want seconds instead of milliseconds.&lt;/li>
&lt;/ol>
&lt;h2 id="understand-apis">understand APIs&lt;/h2>
&lt;p>The &lt;code>Get_Current_Song&lt;/code> returns the LAST song that was played - and the &lt;code>is_playing&lt;/code> property returns if the song is currently (still) playing or not. This means, that we need to distinguish between a song that played before I needed to turn off the music and a currently playing song. You might say, well, this doesn&amp;rsquo;t really matter - but if we take a closer look to understand, which data is returned when, we would need to redesign our flow: The fact that we get an output of the &lt;code>get_Current_Song&lt;/code> even if the &lt;code>is_Playing&lt;/code> property is &lt;code>false&lt;/code>, means that we don&amp;rsquo;t get a &lt;code>null&lt;/code> when our subsequent actions expect an object, a string, an array or anything else that is NOT &lt;code>null&lt;/code>. Yet again, understanding what happens behind the scenes because we understand the output of an action will make it easier to create flows.&lt;/p></description></item><item><title>How to get started with GitHub and Git</title><link>https://m365princess.com/blogs/started-github-git/</link><pubDate>Thu, 09 Sep 2021 13:26:31 +0000</pubDate><guid>https://m365princess.com/blogs/started-github-git/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>Ever wondered how you could get started in contributing to open-source projects?
Let&amp;rsquo;s say you would like to contribute to an open-source project and the contributing guide tells you that you would need to &lt;em>fork&lt;/em> a &lt;em>repository&lt;/em> and you have no idea what this should mean? Fear not, help is on the way and you already opened it. We will also cover how you connect everything in the code editor Visual Studio Code and where you can find like-minded people.&lt;/p>
&lt;h2 id="what-are-github-and-git">What are GitHub and Git?&lt;/h2>
&lt;p>Don&amp;rsquo;t confuse Git and GitHub- they are related to each other, but not the same:&lt;/p>
&lt;ul>
&lt;li>GitHub is website for hosting projects that use Git.&lt;/li>
&lt;li>Git a software for version control that makes it easy for developers (authors, makers and more) to track even minor (but also big) changes in files.&lt;/li>
&lt;/ul>
&lt;p>At the very heart of GitHub, you will find repositories to host your content in. You can invite specific people to collaborate with or open your project for everyone - while still staying in control. Small changes in files can be made on the &lt;a href="https://github.com">GitHub.com&lt;/a> website. This is very convenient, as it gives you a nice interface and you don&amp;rsquo;t need to store any files on your computer. However, the bigger the change is that you want make, the more you will need a more sophisticated system. For example, if you need to pause your work and want to continue later on, there is no way on the website to save your files without &lt;em>committing&lt;/em> them. So, what is a &lt;em>commit&lt;/em> and how does that work?&lt;/p>
&lt;p>A commit is a change to one or more files. You save your work by committing it, and git (not GitHub) will create and assign unique ID, which is called &lt;strong>hash&lt;/strong> or &lt;strong>SHA&lt;/strong>. This then allows you to answer questions like:&lt;/p>
&lt;ul>
&lt;li>Who changed that? (author)&lt;/li>
&lt;li>What exactly changed? (highlights the changes)&lt;/li>
&lt;li>Why did this change happen? (reading the commit message (should be a short description what changed and why))&lt;/li>
&lt;/ul>
&lt;p>This enables traceability, revertability, and collaboration. Just imagine we would work with code like we did this with Excel sheets and PowerPoint presentations for years&amp;hellip; saving the &lt;code>2021-09-08-project-deathstar-draft_07-super-important_finalFINAL2.pptx (1)&lt;/code> and then mailing copies of copies of that file around 🙄&lt;/p>
&lt;p>As already mentioned, GitHub is a good way to &lt;em>host&lt;/em> your projects, but its website comes with some limitations, as, for example, we can&amp;rsquo;t &lt;em>save this for later but I don&amp;rsquo;t want to commit yet&lt;/em>, and most probably no one wants to write their code on a website&amp;hellip; it means you can&amp;rsquo;t work offline or with poor connectivity, also, what if you accidentally close the browser tab? What if we could write our code, documentation, more in local files on our computer and then use Git to upload them to GitHub once we are ready? To achieve this, we need a couple of things and this article will cover all of that:&lt;/p>
&lt;h3 id="install-git">Install Git&lt;/h3>
&lt;p>First, we will install Git for Windows, if you work on a macOS, &lt;a href="https://git-scm.com/download/mac">download Git for macOs&lt;/a>. To continue with Windows,&lt;/p>
&lt;ul>
&lt;li>Open &lt;a href="https://git-scm.com/download/win">Git for Windows&lt;/a>&lt;/li>
&lt;li>Download the latest version (64-bit or 32-bit).&lt;/li>
&lt;li>Optional: If you are not sure what you need, you can check it:
&lt;ul>
&lt;li>Press WIN key and type &lt;code>about&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>about your PC&lt;/strong>, it shows you the &lt;strong>System type&lt;/strong> in the device specifications&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Open the downloaded file&lt;/li>
&lt;li>Confirm the User Account Control window by selecting &lt;strong>Yes&lt;/strong>&lt;/li>
&lt;li>In the Git Setup window, select &lt;strong>Next&lt;/strong> for 6 times, Git will now extract the files&lt;/li>
&lt;li>Select &lt;strong>Finish&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="create-a-git-account">Create a Git account&lt;/h3>
&lt;p>Now let&amp;rsquo;s first check if you installed Git correctly&lt;/p>
&lt;ul>
&lt;li>Open PowerShell or PowerShell in the terminal of &lt;a href="https://code.visualstudio.com">Visual Studio Code&lt;/a>. (If you don&amp;rsquo;t have VS Code installed please install it- it&amp;rsquo;s an amazing Code editor.)&lt;/li>
&lt;li>Type &lt;code>git --version&lt;/code> in a directory of your choice&lt;/li>
&lt;li>You should see the installed version as a response&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="git version" src="https://m365princess.com/images/git-version.png">&lt;/p>
&lt;p>If so, you can proceed!&lt;/p>
&lt;ul>
&lt;li>Set your user name with &lt;code>git config --global user.name &amp;quot;&amp;lt;your-first-name&amp;gt; &amp;lt;your-last-name&amp;gt;&amp;quot;&lt;/code> - replace the &lt;code>&amp;lt;placeholders&amp;gt;&lt;/code> with the real values&lt;/li>
&lt;li>Set your email address with &lt;code>git config --global user.email &amp;quot;&amp;lt;your-email-here&amp;gt;&amp;quot;&lt;/code> - replace the &lt;code>&amp;lt;placeholder&amp;gt;&lt;/code> with the real values&lt;/li>
&lt;/ul>
&lt;p>Congrats, you now have a Git account.&lt;/p>
&lt;p>To use Git, we will either need a GUI (graphical user interface) client or a CLI (command-line interface). This is somehow a matter of personal preference, but I found GUI clients a bit more confusing.&lt;/p>
&lt;p>Before we learn some Git commands, lets head over to GitHub and get you an account if you don&amp;rsquo;t already have one.&lt;/p>
&lt;h3 id="create-a-github-account">Create a GitHub account&lt;/h3>
&lt;ul>
&lt;li>Open &lt;a href="https://github.com">github.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Sign up&lt;/strong>&lt;/li>
&lt;li>Type in your email address when prompted&lt;/li>
&lt;li>Select &lt;strong>Continue&lt;/strong>&lt;/li>
&lt;li>Type in a password&lt;/li>
&lt;li>Select &lt;strong>Continue&lt;/strong>&lt;/li>
&lt;li>GitHub will generate a username for you, but you may change it&lt;/li>
&lt;li>Select &lt;strong>Continue&lt;/strong>&lt;/li>
&lt;li>Type &lt;code>y&lt;/code> if you want product updates per mail, else type &lt;code>n&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Continue&lt;/strong>&lt;/li>
&lt;li>Verify that you are a human by selecting &lt;strong>Start puzzle&lt;/strong>&lt;/li>
&lt;li>Select the correct image that meets the criteria described - maybe you need to do this twice.&lt;/li>
&lt;li>Select &lt;strong>Create account&lt;/strong>&lt;/li>
&lt;li>Type in the 6 digit code that you received per mail&lt;/li>
&lt;li>Optional: personalize your experience&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="new GitHub account" src="https://m365princess.com/images/github-new.png">&lt;/p>
&lt;h3 id="create-a-repository">Create a repository&lt;/h3>
&lt;p>Now create your first repository and make it available for you in VSCode:&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Create repository&lt;/strong>&lt;/li>
&lt;li>Notice that you will be the owner of said repository&lt;/li>
&lt;li>Type in a name for the repository&lt;/li>
&lt;li>Select &lt;code>add a README file&lt;/code>&lt;/li>
&lt;li>Select &lt;strong>Create repository&lt;/strong>&lt;/li>
&lt;li>Notice that this is already a commit, and more specific the &lt;code>initial commit&lt;/code> - If you select &lt;code>1 commit&lt;/code> you can see the the changes made:
&lt;ul>
&lt;li>additions highlighted in green&lt;/li>
&lt;li>deletion highlighted in red (this time, we only added something and that was the name of the repository)&lt;/li>
&lt;li>author who changed the file (your account)&lt;/li>
&lt;li>time of when the changes were made&lt;/li>
&lt;li>hash of the commit&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="first commit" src="https://m365princess.com/images/first-commit.png">&lt;/p>
&lt;p>Now that you have your own repository, make it available on your computer. We do this by cloning the repository:&lt;/p>
&lt;h3 id="clone-the-repository">Clone the repository&lt;/h3>
&lt;ul>
&lt;li>Select &lt;strong>Code&lt;/strong>&lt;/li>
&lt;li>Copy the URL (it is &lt;code>https://github.com/&amp;lt;YOUR GITHUB ACCOUNT&amp;gt;/&amp;lt;YOUR REPOSITORY NAME&amp;gt;.git&lt;/code>)&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="clone repository" src="https://m365princess.com/images/clone-repository.png">&lt;/p>
&lt;ul>
&lt;li>Open the terminal in VS Code&lt;/li>
&lt;li>navigate to a directory where you want to clone the repository (you can use &lt;code>cd &amp;lt;PATH OF YOUR DIRECTORY&amp;gt;&lt;/code> for this)&lt;/li>
&lt;li>type &lt;code>git clone &amp;lt;COPIED URL HERE&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>This will create a copy of your remote repository in that folder. Now navigate to your local copy with &lt;code>cd &amp;lt;YOUR REPOSITORY NAME HERE&amp;gt;&lt;/code>
Open the project in Visual Studio code by typing &lt;code>code .&lt;/code> in PowerShell or terminal in VS Code (yes, there is a &lt;code> &lt;/code> between &lt;code>code&lt;/code> and &lt;code>.&lt;/code>)&lt;/p>
&lt;h3 id="work-with-your-clone">Work with your clone&lt;/h3>
&lt;p>Now either add some files or change the only existing file (it&amp;rsquo;s the README.md). When you completed your changes, you want 3 things:&lt;/p>
&lt;ul>
&lt;li>add the files you want to commit to a staging area (and only those)&lt;/li>
&lt;li>commit the files with a descriptive commit message&lt;/li>
&lt;li>push the files so that the changes will be reflected on the GitHub website as well.&lt;/li>
&lt;/ul>
&lt;p>As you probably already assumed, there are some specific command to achieve this:&lt;/p>
&lt;ul>
&lt;li>to add a specific file to that staging area from which you can commit it (without touching the other files that are not ready to be committed now), type &lt;code>git add &amp;lt;YOUR FILENAME HERE&amp;gt;&lt;/code> for a specific file or &lt;code>git add &amp;lt;YOUR PATH HERE&amp;gt;&lt;/code> for a specific directory. If you want to add all changes to the staging area, type &lt;code>git add .&lt;/code> (yes, there is a &lt;code> &lt;/code> between &lt;code>add&lt;/code> and &lt;code>.&lt;/code>)&lt;/li>
&lt;li>to commit all files that are now in staging area, type `git commit -m &amp;ldquo;&lt;YOUR DESCRIPTIVE COMMIT MESSAGE HERE>&amp;rdquo;&lt;/li>
&lt;li>to push your changes, type &lt;code>git push&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Your changes are online 🎉&lt;/p>
&lt;p>If you now use both, the GitHub website and your local files, the changes that you make remote won&amp;rsquo;t be reflected automatically in the local files. To do that, you will need to type &lt;code>git pull&lt;/code> to pull all the changes that were made remote. If you forget that, or if others were working on your repository and you want to push next time, git will tell you:&lt;/p>
&lt;pre>&lt;code>! [rejected] main -&amp;gt; main (fetch first)
error: failed to push some refs to 'https://github.com/M365Princess/MyFirstRepository'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
&lt;/code>&lt;/pre>
&lt;p>Read the message carefully and go ahead with &lt;code>git pull&lt;/code> to be up to date in your local files. You can now accept the maybe conflicting changes in the editor and resolve the situation.&lt;/p>
&lt;p>&lt;img alt="git changes" src="https://m365princess.com/images/git-changes.png">&lt;/p>
&lt;p>Then use the commands &lt;code>git commit&lt;/code> and &lt;code>git push&lt;/code> again. (You can always access your last commands with the arrow-up and arrow-down key.) You should see a response that looks about like this:&lt;/p>
&lt;p>&lt;img alt="git push" src="https://m365princess.com/images/git-push.png">&lt;/p>
&lt;p>Please note that you see two abbreviated hashs, with which you could also compare what happened in the last commit compared to the previous one: Type &lt;code>git diff &amp;lt;FIRST HASH HERE&amp;gt; &amp;lt;SECOND HASH HERE&amp;gt;&lt;/code>&lt;/p>
&lt;h3 id="contribute-to-open-source-projects">Contribute to open-source projects&lt;/h3>
&lt;p>But what if you want to contribute to a repository of which you aren&amp;rsquo;t the owner? It&amp;rsquo;s possible to work locally in those projects as well, but we need to do a couple of more steps:&lt;/p>
&lt;h4 id="fork-the-repository">Fork the repository&lt;/h4>
&lt;p>as the repository owner would not want you to directly commit to their repository, you will first create a remote copy - this is then called your &lt;strong>fork&lt;/strong>. To do that:&lt;/p>
&lt;ul>
&lt;li>open the repository that you want to contribute to&lt;/li>
&lt;li>make sure you understand the contributing guide, in most cases, you will be asked to fork the repository:&lt;/li>
&lt;li>Select &lt;strong>Fork&lt;/strong>, the URL of your fork is now &lt;code>https://github.com/&amp;lt;YOUR GITHUB ACCOUNT&amp;gt;/&amp;lt;REPOSITORY NAME&amp;gt;/&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Now &lt;a href="https://m365princess.com/blogs/started-github-git/#how-to-get-started-with-git-and-github#Clone-this-repository">clone this repository locally&lt;/a>.&lt;/p>
&lt;h4 id="add-upstream">Add Upstream&lt;/h4>
&lt;p>You will now want to make sure, that all your contributions point to the original repository, which is why you want to add an upstream to it:&lt;/p>
&lt;ul>
&lt;li>navigate to the folder where your cloned repository is located&lt;/li>
&lt;li>type &lt;code>git remote add upstream &amp;lt;ORIGINAL REPOSITORY URL HERE&amp;gt;&lt;/code>&lt;/li>
&lt;li>to check if everything works correctly, type &lt;code>git remote -v&lt;/code>, you should see this output:&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>origin https://github.com/&amp;lt;YOUR ACCOUNT HERE&amp;gt;/&amp;lt;REPOSITORY NAME&amp;gt;.git (fetch)
origin https://github.com/&amp;lt;YOUR ACCOUNT HERE&amp;gt;/&amp;lt;REPOSITORY NAME&amp;gt;.git (push)
upstream https://github.com/&amp;lt;ORIGINAL ACCOUNT HERE&amp;gt;/&amp;lt;REPOSITORY NAME&amp;gt; (fetch)
upstream https://github.com/&amp;lt;ORIGINAL ACCOUNT HERE&amp;gt;/&amp;lt;REPOSITORY NAME&amp;gt; (push)
&lt;/code>&lt;/pre>&lt;p>Some repository owners will ask you in the contribution guide to create a new branch for the feature you want to add - you can create and switch to that new branch with &lt;code>git checkout -b &amp;lt;YOUR BRANCH NAME HERE&amp;gt;&lt;/code>.
Like in your own repository, you can work on the local files and commit and push your changes to your remote fork.&lt;/p>
&lt;h4 id="pull-request">Pull request&lt;/h4>
&lt;p>You will now want to (kindly) ask the repository owner/ maintainer to pull in your changes. You do that by doing a pull request:&lt;/p>
&lt;ul>
&lt;li>Open the original repository&lt;/li>
&lt;li>Select &lt;strong>Pull requests&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New pull request&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="create pull request" src="https://m365princess.com/images/create-pr.png">&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>compare across forks&lt;/strong>&lt;/li>
&lt;li>Select your fork from the &lt;strong>head repository&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="compare changes" src="https://m365princess.com/images/compare-changes.png">&lt;/p>
&lt;ul>
&lt;li>fill out the form&lt;/li>
&lt;/ul>
&lt;h3 id="working-together">working together&lt;/h3>
&lt;p>A maintainer will review your pull request and&lt;/p>
&lt;ul>
&lt;li>reach out to you with a comment (you will get a notification about that)&lt;/li>
&lt;li>request changes or&lt;/li>
&lt;li>&lt;em>merge&lt;/em> your changes. This means that they approved your work and that your changes will be added to the original repository.&lt;/li>
&lt;/ul>
&lt;h2 id="additional-resources">Additional resources&lt;/h2>
&lt;p>If you ned more help for your first contributions, have questions or just want to connect with like-minded people, and for everyone who is more into interactive sessions: Within Microsoft 365 PnP, I am part of the &lt;em>Sharing is Caring&lt;/em> initiative, who aims to lower barriers for new contributors. We run a couple of sessions, please check out &lt;a href="https://aka.ms/sharing-is-caring">Sharing is Caring&lt;/a>&lt;/p>
&lt;p>You can also have a look into the documentation of &lt;a href="https://docs.github.com/">GitHub&lt;/a> and &lt;a href="https://git-scm.com/doc">Git&lt;/a>- this article should only get you started, not replace reading the docs.&lt;/p>
&lt;p>Open-source is fun and very rewarding and I hope that this article made your start a little bit easier! Please reach out if you have questions, comments, or want to connect.&lt;/p></description></item><item><title>Create a portfolio site with GitHub pages and reveal.js</title><link>https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/</link><pubDate>Mon, 06 Sep 2021 16:09:09 +0000</pubDate><guid>https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/</guid><description>&lt;p>In this post I want to show how you can create a simple portfolio site with GitHub pages and reveal.js&lt;/p>
&lt;p>&lt;a href="https://pages.github.com/">GitHub pages&lt;/a> lets you host your website directly from the GitHub repository&lt;/p>
&lt;p>But instead of having a lengthy README.md file with a lot of images in it, we want to use reveal.js to create a cool look&amp;amp;feel:&lt;/p>
&lt;p>&lt;a href="https://revealjs.com/">reveal.js&lt;/a> is an open-source HTML presentation framework that lets you create rich, interactive slides using open web technologies.&lt;/p>
&lt;p>The result should look a little something like this:&lt;/p>
&lt;p>&lt;img alt="reveal animation" src="https://m365princess.com/images/reveal.gif">&lt;/p>
&lt;p>To go from 0 to hero, we will need to accomplish the following steps:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/#create-a-repository">create a repository&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/#revealjs">reveal.js&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/#preparations">preparations&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/#installation">installation&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/#create-slides">create slides&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/blogs/create-portfolio-site-github-pages-reveal-js/#create-github-pages">create GitHub pages&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>I will show how todo this on Windows and using Visual Studio code, if you run on a different OS or code editor, it might look a bit different, but the steps stay the same.&lt;/p>
&lt;h2 id="create-a-repository">create a repository&lt;/h2>
&lt;ul>
&lt;li>open &lt;a href="https://github.com">github.com&lt;/a>&lt;/li>
&lt;li>log in. If you don&amp;rsquo;t have an account, sign up, it&amp;rsquo;s free.&lt;/li>
&lt;li>select &lt;strong>New&lt;/strong>&lt;/li>
&lt;/ul>
&lt;!-- ![create new repository](/content/english/blogs/media/github-create-repo.png) -->
&lt;ul>
&lt;li>
&lt;p>Give it a name&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Check &lt;strong>Add a README file&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Check &lt;strong>Choose a license&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Select a License that suits your needs&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Select &lt;strong>Create repository&lt;/strong>&lt;/p>
&lt;p>&lt;img alt="create new repository" src="https://m365princess.com/images/github-create-repo-form.png">&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Now let&amp;rsquo;s clone this repository so that we have it locally available:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Select &lt;strong>Code&lt;/strong>, then copy the URL to your clipboard&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;img alt="clone repository" src="https://m365princess.com/images/github-clone.png">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Open Visual Studio Code terminal or (I run PowerShell, your commands might look different if you run another shell)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Navigate to a folder where you want to clone the repository&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Type &lt;code>git clone &amp;lt;COPIED URL GOES HERE&amp;gt;&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Navigate to your new folder with &lt;code>cd &amp;lt;YOUR REPO NAME GOES IN HERE&amp;gt;&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Open your project in a new VSCode instance with &lt;code>code .&lt;/code> (yes, there is a &lt;code> &lt;/code> between &lt;code>code&lt;/code> and the &lt;code>.&lt;/code>)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="revealjs">reveal.js&lt;/h2>
&lt;h3 id="preparations">preparations&lt;/h3>
&lt;p>To install reveal.js, we will need to prepare our repository a bit:&lt;/p>
&lt;ol>
&lt;li>Create a new &lt;code>docs&lt;/code> folder at the root of your (nearly) empty repository with &lt;code>mkdir docs&lt;/code>&lt;/li>
&lt;li>Navigate to this &lt;code>docs&lt;/code> folder with &lt;code>cd docs&lt;/code>&lt;/li>
&lt;li>Create a &lt;code>media&lt;/code> folder (this is where you will store all your portfolio images in) with &lt;code>mkdir media&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="installation">installation&lt;/h3>
&lt;ol>
&lt;li>Download the reveal.js zip file from here: &lt;a href="https://revealjs.com/installation/#basic-setup">reveal.js basic setup&lt;/a>&lt;/li>
&lt;li>unzip the file into the docs folder of your repository&lt;/li>
&lt;/ol>
&lt;h2 id="create-slides">create slides&lt;/h2>
&lt;ol>
&lt;li>open the &lt;code>index.html&lt;/code> file&lt;/li>
&lt;li>replace the existing two sections with your content, link to images that you will store in the media folder.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;!DOCTYPE html&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">charset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;utf-8&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;viewport&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Demo Portfolio&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;dist/reset.css&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;dist/reveal.css&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;dist/theme/black.css&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cp">&amp;lt;!-* Theme used for syntax highlighted code --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;plugin/highlight/monokai.css&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;reveal&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;slides&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">section&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Unicorn slide&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;media/unicorn.png&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">section&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">section&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Cupcake slide &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">scr &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">media/cupcake.png&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">section&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;dist/reveal.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;plugin/notes/notes.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;plugin/markdown/markdown.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;plugin/highlight/highlight.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// More info about initialization &amp;amp; config:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// * https://revealjs.com/initialization/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// * https://revealjs.com/config/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">Reveal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">initialize&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">hash&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Learn about plugins: https://revealjs.com/plugins/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">RevealMarkdown&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">RevealHighlight&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">RevealNotes&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="create-github-pages">create GitHub pages&lt;/h2>
&lt;p>We will now create the GitHub page&lt;/p>
&lt;ul>
&lt;li>open your GitHub repository again&lt;/li>
&lt;li>Select &lt;strong>Settings&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Pages&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>main&lt;/strong> as Branch&lt;/li>
&lt;li>Select &lt;strong>/docs&lt;/strong> as folder&lt;/li>
&lt;li>Select &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Now let&amp;rsquo;s publish :-)&lt;/p>
&lt;p>In terminal, type&lt;/p>
&lt;ul>
&lt;li>&lt;code>git add .&lt;/code> to add all changes to your staging area&lt;/li>
&lt;li>&lt;code>git commit -m &amp;quot;&amp;lt;YOUR COMMIT MESSAGE HERE&amp;gt;&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;code>git push&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>If you work on Windows, it might be that you see warnings for all files:&lt;/p>
&lt;p>&lt;code>The file will have its original line endings in your working directory&lt;/code>
&lt;code>warning: LF will be replaced by CRLF in reveal.js-master/test/test.html.&lt;/code>&lt;/p>
&lt;p>The end of a line in a unix system represented with a line feed (LF), while on windows a line ends with a carriage return (CR) and a line feed (LF) =&amp;gt; (CRLF). We get this warning on Windows when the file was originally uploaded from a unix system. What to do about it?&lt;/p>
&lt;p>You can either decide to not care (everything should be working fine, just those warning can be a little annoying), you can &lt;a href="http://vcloud-lab.com/entries/devops/resolved-git-warning-lf-will-be-replaced-by-crlf-in-file">read more about line endings&lt;/a> and in case you are really the only developer working on these files, you can change settings with &lt;code>git config core.autocrlf true&lt;/code>.&lt;/p>
&lt;p>Now check again the GitHub Pages site - it should confirm:&lt;/p>
&lt;p>&lt;img alt="GitHub-pages confirmation" src="https://m365princess.com/images/github-pages-published.png">&lt;/p>
&lt;p>If you now select the link, you should see your GitHub pages site!&lt;/p>
&lt;p>How to continue:&lt;/p>
&lt;ul>
&lt;li>make &lt;a href="https://revealjs.com/vertical-slides/">vertical slides&lt;/a>&lt;/li>
&lt;li>change to a different &lt;a href="https://revealjs.com/themes/">theme&lt;/a>&lt;/li>
&lt;li>tell others :-)&lt;/li>
&lt;/ul>
&lt;p>I hope you liked this little tutorial!&lt;/p></description></item><item><title>Putting some more FUN into Azure Functions, Managed Identity &amp; Microsoft Graph</title><link>https://m365princess.com/blogs/putting-fun-azure-functions-managed-identity-microsoft-graph/</link><pubDate>Sat, 07 Aug 2021 18:20:02 +0000</pubDate><guid>https://m365princess.com/blogs/putting-fun-azure-functions-managed-identity-microsoft-graph/</guid><description>&lt;p>I want to show, how you can use a &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview">Managed Identity&lt;/a> in &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview">Azure Functions&lt;/a> to get an access token for &lt;a href="https://docs.microsoft.com/en-us/graph/overview">Microsoft Graph API&lt;/a>. I will later expand on that scenario and make the solution available to be consumed in Power Platform. &lt;figure class="wp-block-image size-large">&lt;/p>
&lt;p>&lt;img loading="lazy" width="1024" height="281" src="https://m365princess.com/wp-content/uploads/2021/08/image-8-1024x281.png" alt="" class="wp-image-833" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-8-1024x281.png 1024w, https://m365princess.com/wp-content/uploads/2021/08/image-8-300x82.png 300w, https://m365princess.com/wp-content/uploads/2021/08/image-8-768x211.png 768w, https://m365princess.com/wp-content/uploads/2021/08/image-8.png 1328w" sizes="(max-width: 1024px) 100vw, 1024px" /> &lt;figcaption>Solution Overview&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;h2 id="prerequisites-to-benefit-from-this-article">Prerequisites to benefit from this article&lt;/h2>
&lt;p>To get the most out of this post, you should understand the following concepts - I included some links that should help you getting started.&lt;/p>
&lt;h3 id="managed-identity---what-is-that-and-why-would-i-want-one">Managed Identity - What is that and why would I want one?&lt;/h3>
&lt;p>Managed Identities are the state-of-the-art way to get Azure Active Directory access tokens, because you won&amp;rsquo;t need to handle credentials, which is of course a security benefit&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>where there are credentials, there are leaks ¯\_(ツ)\_/¯&lt;/code>&lt;/p>
&lt;/blockquote>
&lt;p>If you are unfamiliar with tokens, &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/introduction-to-tokens/ba-p/2267853">this article&lt;/a> by &lt;a href="https://twitter.com/lee_ford">Lee Ford&lt;/a> will help. This is not a full tutorial on Managed Identities, for more information please start to read here. Also, &lt;a href="https://twitter.com/YannickReekmans">Yannick Reekmans&lt;/a> posted an entire &lt;a href="https://blog.yannickreekmans.be/secretless-applications-managed-identities/">series on Managed Identities&lt;/a>, highly recommended to read this - helped me a lot to understand.&lt;/p>
&lt;h3 id="azure-functions">Azure Functions&lt;/h3>
&lt;p>If you&amp;rsquo;d like to deploy and execute code without needing to THINK about server infrastructure or VMs, you may like Azure Functions. Azure Functions can be written in a lot of languages and you can deploy and execute Azure functions on any platform that can run .NET Core. Azure Functions are scalable, as they use compute-on-demand: More resources are allocated automagically, when the demand of execution increases. If the demand decreases again, you don&amp;rsquo;t need to worry about the extra resources anymore as they drop off again. This way, you only pay what you need. For more info please start to read here: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview">Azure Functions Overview | Microsoft Docs&lt;/a>&lt;/p>
&lt;p>I will use PowerShell for my Azure Functions, so that IT-Pros can benefit from this post as well. But please do choose &lt;s>your poison&lt;/s> your favorite language, the concept stays the same. Use whatever you are familiar with or what makes you happy.&lt;/p>
&lt;h3 id="microsoft-graph">Microsoft Graph&lt;/h3>
&lt;p>Microsoft Graph is THE API to access Microsoft 365 resources, in our case we will want to read all Microsoft 365 groups - for more information see also the &lt;a href="https://docs.microsoft.com/en-us/graph/permissions-reference#group-permissions">Microsoft Graph permissions reference - Microsoft Graph | Microsoft Docs.&lt;/a>&lt;/p>
&lt;p>Our challenge will be to access the Graph API with a Managed Identity. I will show how to do this entirely in Azure CLI &amp;amp; VS Code. Creating and assigning Managed Identities has been shown often before in the Azure portal, but a script would allow for automation, it is usually faster than working in a visual environment and you can use the tool of your choice. I highly recommend VS Code as editor (and I like the built-in terminal as well).&lt;/p>
&lt;h2 id="create-your-azure-functions-locally">Create your Azure Functions locally&lt;/h2>
&lt;p>You still may create your first Azure Function in the Azure Portal (I did as well), which demos really nicely, but as we want to go for real world, I will show how to develop your Azure Functions in &lt;a href="https://code.visualstudio.com/">Visual Studio code&lt;/a>. This has some great advantages:&lt;/p>
&lt;ul>
&lt;li>you develop locally using the tools that you are already familiar with&lt;/li>
&lt;li>you don&amp;rsquo;t need to adjust to ever changing UI in Azure portal&lt;/li>
&lt;li>you can benefit from source control&lt;/li>
&lt;li>you can collaborate with others&lt;/li>
&lt;/ul>
&lt;p>I covered how to do this also in a &lt;a href="https://m365princess.com/putting-the-fun-into-azure-functions-the-friendly-sms-reminder/">previous post&lt;/a>, but for the sake of a start-to-end scenario, here we go again:&lt;/p>
&lt;ol>
&lt;li>Install the Core Tools package with &lt;code>npm install -g azure-functions-core-tools@3 --unsafe-perm true&lt;/code>&lt;/li>
&lt;li>Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions">Azure Functions extension for VS Code&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>New Project&lt;/strong>&lt;/li>
&lt;li>Select a folder for your project&lt;/li>
&lt;li>Select a language - I will use &lt;strong>PowerShell&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>HTTP trigger&lt;/strong> as a template&lt;/li>
&lt;li>Type in a better name like &lt;code>GetGraphToken&lt;/code>&lt;/li>
&lt;li>Select Authorization level &lt;strong>Function&lt;/strong>&lt;/li>
&lt;li>Select how you want to open your project – I prefer &lt;strong>Add to workspace&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>You will now see your &lt;strong>Local Project&lt;/strong> in the pane.&lt;br>
(yes, my pane is on the right hand side so that my code doesn&amp;rsquo;t &amp;ldquo;jump&amp;rdquo; all the time when I am expanding or collapsing the pane)&lt;figure class="wp-block-image size-large">&lt;/p>
&lt;p>&lt;img loading="lazy" width="1024" height="377" src="https://m365princess.com/wp-content/uploads/2021/08/image-9-1024x377.png" alt="" class="wp-image-842" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-9-1024x377.png 1024w, https://m365princess.com/wp-content/uploads/2021/08/image-9-300x110.png 300w, https://m365princess.com/wp-content/uploads/2021/08/image-9-768x282.png 768w, https://m365princess.com/wp-content/uploads/2021/08/image-9-1536x565.png 1536w, https://m365princess.com/wp-content/uploads/2021/08/image-9-2048x753.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /> &lt;figcaption>Visual Studio Code showing an Azure Functions project&lt;/figcaption>&lt;/figure>&lt;/p>
&lt;p>We will now write the code. Replace the default code of &lt;code>run.ps1&lt;/code> in VSCode by this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">using&lt;/span> &lt;span class="n">namespace&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Net&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Input bindings are passed in via param block&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">param&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$Request&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$TriggerMetadata&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Write to the Azure Functions log stream.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;PowerShell HTTP trigger function processed a request.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Interact with query parameters or the body of the request&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nv">$Scope&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$Request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Query&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Scope&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">-not&lt;/span> &lt;span class="nv">$Scope&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$Scope&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$Request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Scope&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#If parameter &amp;#34;Scope&amp;#34; has not been provided, we assume that graph.microsoft.com is the target resource&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">If&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="nv">$Scope&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$Scope&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://graph.microsoft.com/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$tokenAuthUri&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$env:IDENTITY_ENDPOINT&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="s2">&amp;#34;?resource=&lt;/span>&lt;span class="nv">$Scope&lt;/span>&lt;span class="s2">&amp;amp;api-version=2019-08-01&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$response&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Invoke-RestMethod&lt;/span> &lt;span class="n">-Method&lt;/span> &lt;span class="n">Get&lt;/span> &lt;span class="n">-Headers&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;X-IDENTITY-HEADER&amp;#34;&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:IDENTITY_HEADER&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="n">-Uri&lt;/span> &lt;span class="nv">$tokenAuthUri&lt;/span> &lt;span class="n">-UseBasicParsing&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$accessToken&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">access_token&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#Invoke REST call to Graph API&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$uri&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;https://graph.microsoft.com/v1.0/groups&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$authHeader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;Authorization&amp;#39;&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="s1">&amp;#39;Bearer &amp;#39;&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="nv">$accessToken&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Invoke-RestMethod&lt;/span> &lt;span class="n">-Uri&lt;/span> &lt;span class="nv">$uri&lt;/span> &lt;span class="n">-Headers&lt;/span> &lt;span class="nv">$authHeader&lt;/span> &lt;span class="n">-Method&lt;/span> &lt;span class="n">Get&lt;/span> &lt;span class="n">-ResponseHeadersVariable&lt;/span> &lt;span class="n">RES&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="py">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">If&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$result&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$body&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$StatusCode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;200&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">Else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$body&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$RES&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$StatusCode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;400&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Associate values to output bindings by calling &amp;#39;Push-OutputBinding&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Push-OutputBinding&lt;/span> &lt;span class="n">-Name&lt;/span> &lt;span class="n">Response&lt;/span> &lt;span class="n">-Value&lt;/span> &lt;span class="p">([&lt;/span>&lt;span class="no">HttpResponseContext&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="vm">@&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">StatusCode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$StatusCode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Body&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$body&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Take a moment to understand what the code does:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>log that a request was received&lt;/p>
&lt;/li>
&lt;li>
&lt;p>define the scope (if not stated, it&amp;rsquo;s Microsoft Graph)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>obtain the token from the environment variables &lt;code>IDENTITY_ENDPOINT&lt;/code> and &lt;code>IDENTITY_HEADER&lt;/code> which come from the Managed Identity (thanks &lt;a href="https://twitter.com/YannickReekmans">Yannick Reekmans&lt;/a> for this hint) - learn more here: &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=powershell&amp;WT.mc_id=M365-MVP-5003400#obtain-tokens-for-azure-resources">Managed identities - Azure App Service | Microsoft Docs&lt;/a> - you can also see your environment variables here, the mentioned ones will show after you assigned the system assigned Managed Identity: &lt;code>https://&amp;lt;your-functionappname-here&amp;gt;.scm.azurewebsites.net/ENV.cshtml#envVariables)&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>pass that token in the header of the REST call towards the group endpoint of Microsoft Graph&lt;/p>
&lt;/li>
&lt;li>
&lt;p>get the status code (hopefully 200)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Watch out - the Graph API will this way return up to 100 groups - Please adjust with &lt;a href="https://docs.microsoft.com/en-us/graph/query-parameters">query parameters&lt;/a> as needed like &lt;code>https://graph.microsoft.com/v1.0/groups?$top=42&lt;/code> or use pagination, which is described here: &lt;a href="https://docs.microsoft.com/en-us/graph/paging">Paging Microsoft Graph data in your app - Microsoft Graph | Microsoft Docs&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h2 id="resources-in-azure">Resources in Azure&lt;/h2>
&lt;p>We will now create the Functions App in Azure - You can either install the CLI or use Cloud shell (available on &lt;a href="https://shell.azure.com">shell.azure.com&lt;/a> - no installation is needed then and it works in any browser!)&lt;/p>
&lt;h3 id="variables--names">Variables &amp;amp; names&lt;/h3>
&lt;p>For testing purposes, I pseudo-randomized a number to not always need to come up with new names:&lt;/p>
&lt;pre tabindex="0">&lt;code>#Get a random number between 100 and 300 to more easily be able to distinguish between several trials
$rand = Get-Random -Minimum 100 -Maximum 300
&lt;/code>&lt;/pre>&lt;p>We will now set some variables,this reduces risk of typos and makes our code better readable, also we can reuse it better. This is a courtesy to future-self&lt;/p>
&lt;pre tabindex="0">&lt;code>#Set values
$resourceGroup = &amp;#34;DemoPlay$rand&amp;#34;
$location = &amp;#34;westeurope&amp;#34;
$storage = &amp;#34;luisedemostorage$rand&amp;#34;
$functionapp = &amp;#34;LuiseDemo-functionapp$rand&amp;#34;`
&lt;/code>&lt;/pre>&lt;h3 id="resource-group">Resource group&lt;/h3>
&lt;p>Let&amp;rsquo;s create a resource-group that will later hold our Azure Functions App&lt;/p>
&lt;pre tabindex="0">&lt;code>#create group
az group create -n $resourceGroup -l $location`
&lt;/code>&lt;/pre>&lt;h3 id="storage-account">Storage Account&lt;/h3>
&lt;p>As our Functions App will need a storage account, we will create this as well:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-azurecli" data-lang="azurecli">
#create storage account
az storage account create ` -n $storage`
-l $location ` -g $resourceGroup`
--sku Standard_LRS
&lt;/code>&lt;/pre>&lt;h3 id="function-app">Function App&lt;/h3>
&lt;p>Now create the Azure Functions App which later holds our function (remember we created that earlier locally, but will later deploy it to Azure)&lt;/p>
&lt;pre tabindex="0">&lt;code>#create function
az functionapp create ` -n $functionapp`
--storage-account $storage ` --consumption-plan-location $location`
--runtime powershell ` -g $resourceGroup`
--functions-version 3`
&lt;/code>&lt;/pre>&lt;p>It will take a few moments for everything to be set, once this step is completed, you will be prompted with a message, that you also can benefit from Application Insights.&lt;/p>
&lt;h3 id="managed-identity">Managed Identity&lt;/h3>
&lt;p>We want things to be super secure - this is why we want to enable a system assigned Managed Identity for our new function:&lt;/p>
&lt;p>&lt;code>az functionapp identity assign -n $functionapp -g $resourceGroup&lt;/code>&lt;/p>
&lt;p>Our Managed Identity shall have the right permission scope to access Graph API for Group.Read.All, and to eventually be able to make the required REST call, we will need the Graph API service Provider permission scope, expressed as App role. Let&amp;rsquo;s do this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-#" data-lang="#">Get Graph Api service provider (that&amp;#39;s later needed for --api)
az ad sp list --query &amp;#34;&amp;amp;#91;?appDisplayName==&amp;#39;Microsoft Graph&amp;#39;].{Name:appDisplayName, Id:appId}&amp;#34; --output table --all
#Save that service provider
$graphId = az ad sp list --query &amp;#34;&amp;amp;#91;?appDisplayName==&amp;#39;Microsoft Graph&amp;#39;].appId | &amp;amp;#91;0]&amp;#34; --all
# Get permission scope for &amp;#34;Group.Read.All&amp;#34;
$appRoleId = az ad sp show --id $graphId --query &amp;#34;appRoles&amp;amp;#91;?value==&amp;#39;Group.Read.All&amp;#39;].id | &amp;amp;#91;0]&amp;#34; `
&lt;/code>&lt;/pre>&lt;p>Time to make the REST call to assign the permissions as shown above to the Managed Identity:&lt;/p>
&lt;pre tabindex="0">&lt;code>#Set values
$webAppName=&amp;#34;LuiseDemo-functionapp$rand&amp;#34;
$principalId=$(az resource list -n $webAppName --query &amp;amp;#91;*].identity.principalId --out tsv)
$graphResourceId=$(az ad sp list --display-name &amp;#34;Microsoft Graph&amp;#34; --query &amp;amp;#91;0].objectId --out tsv)
$appRoleId=$(az ad sp list --display-name &amp;#34;Microsoft Graph&amp;#34; --query &amp;#34;&amp;amp;#91;0].appRoles&amp;amp;#91;?value==&amp;#39;Group.Read.All&amp;#39; &amp;amp;&amp;amp; contains(allowedMemberTypes, &amp;#39;Application&amp;#39;)].id&amp;#34; --out tsv)
$body=&amp;#34;{&amp;#39;principalId&amp;#39;:&amp;#39;$principalId&amp;#39;,&amp;#39;resourceId&amp;#39;:&amp;#39;$graphResourceId&amp;#39;,&amp;#39;appRoleId&amp;#39;:&amp;#39;$appRoleId&amp;#39;}&amp;#34;
#the actual REST call
az rest --method post --uri https://graph.microsoft.com/v1.0/servicePrincipals/$principalId/appRoleAssignments --body $body --headers Content-Type=application/json`
&lt;/code>&lt;/pre>&lt;p>If you like to, you may now have a look at our Managed Identity permissions in the Azure portal - for everyone who loves to be assured in a UI that things have worked:&lt;/p>
&lt;ul>
&lt;li>Open &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Azure Active Directory&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Enterprise applications&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Managed Identity&lt;/strong> from the &lt;strong>Applications&lt;/strong> dropdown&lt;/li>
&lt;li>Note the app level permission &lt;strong>Read all groups&lt;/strong> for Microsoft Graph&lt;/li>
&lt;/ul>
&lt;h2 id="deploy-to-azure">Deploy to Azure&lt;/h2>
&lt;p>Let&amp;rsquo;s now deploy our function to our Functions App. Go back to Visual Studio Code:&lt;/p>
&lt;ul>
&lt;li>Select the Azure extension
*Select &lt;strong>deploy to Functions App&lt;/strong> - its that little cloud icon&lt;/li>
&lt;/ul>
&lt;figure class="wp-block-image size-full">
&lt;img loading="lazy" width="805" height="314" src="https://m365princess.com/wp-content/uploads/2021/08/image-13.png" alt="" class="wp-image-852" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-13.png 805w, https://m365princess.com/wp-content/uploads/2021/08/image-13-300x117.png 300w, https://m365princess.com/wp-content/uploads/2021/08/image-13-768x300.png 768w" sizes="(max-width: 805px) 100vw, 805px" />&lt;/figure>
&lt;ul>
&lt;li>Select the Functions App you already created in Azure&lt;/li>
&lt;li>Confirm the Pop up window by selecting &lt;strong>Deploy&lt;/strong>&lt;/li>
&lt;/ul>
&lt;figure class="wp-block-image size-full">
&lt;p>&lt;img loading="lazy" width="830" height="196" src="https://m365princess.com/wp-content/uploads/2021/08/image-14.png" alt="" class="wp-image-853" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-14.png 830w, https://m365princess.com/wp-content/uploads/2021/08/image-14-300x71.png 300w, https://m365princess.com/wp-content/uploads/2021/08/image-14-768x181.png 768w" sizes="(max-width: 830px) 100vw, 830px" />&lt;/figure>&lt;/p>
&lt;figure class="wp-block-image size-full">
&lt;p>&lt;img loading="lazy" width="1001" height="113" src="https://m365princess.com/wp-content/uploads/2021/08/image-15.png" alt="" class="wp-image-854" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-15.png 1001w, https://m365princess.com/wp-content/uploads/2021/08/image-15-300x34.png 300w, https://m365princess.com/wp-content/uploads/2021/08/image-15-768x87.png 768w" sizes="(max-width: 1001px) 100vw, 1001px" />&lt;/figure> &lt;figure class="wp-block-image size-large">&lt;img loading="lazy" width="1024" height="227" src="https://m365princess.com/wp-content/uploads/2021/08/image-16-1024x227.png" alt="" class="wp-image-855" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-16-1024x227.png 1024w, https://m365princess.com/wp-content/uploads/2021/08/image-16-300x66.png 300w, https://m365princess.com/wp-content/uploads/2021/08/image-16-768x170.png 768w, https://m365princess.com/wp-content/uploads/2021/08/image-16-1536x340.png 1536w, https://m365princess.com/wp-content/uploads/2021/08/image-16-2048x453.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" />&lt;/figure>&lt;/p>
&lt;h2 id="time-to-test">Time to test!&lt;/h2>
&lt;p>Please note, that due to our Managed Identity, we can&amp;rsquo;t test locally. You can trigger your functions with Postman or similar or run a test in the Azure portal:&lt;/p>
&lt;ul>
&lt;li>Open &lt;a href="http://portal.azure.com">portal.azure.com&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>Resource groups&lt;/strong>&lt;/li>
&lt;li>Select the resource group you worked in&lt;/li>
&lt;li>Select the Azure Function App you worked in&lt;/li>
&lt;li>Select &lt;strong>Functions&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Code + Test&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Test/Run&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Run&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>You should see a status code 200 - and a list of all your Microsoft 365 groups. YAY! &lt;figure class="wp-block-image size-full">&lt;/p>
&lt;p>&lt;img loading="lazy" width="378" height="199" src="https://m365princess.com/wp-content/uploads/2021/08/image-17.png" alt="" class="wp-image-858" srcset="https://m365princess.com/wp-content/uploads/2021/08/image-17.png 378w, https://m365princess.com/wp-content/uploads/2021/08/image-17-300x158.png 300w" sizes="(max-width: 378px) 100vw, 378px" /> &lt;/figure>&lt;/p>
&lt;figure class="wp-block-image size-large">
&lt;p>&lt;img loading="lazy" width="929" height="1024" src="https://m365princess.com/wp-content/uploads/2021/08/groups-929x1024.png" alt="" class="wp-image-859" srcset="https://m365princess.com/wp-content/uploads/2021/08/groups-929x1024.png 929w, https://m365princess.com/wp-content/uploads/2021/08/groups-272x300.png 272w, https://m365princess.com/wp-content/uploads/2021/08/groups-768x846.png 768w, https://m365princess.com/wp-content/uploads/2021/08/groups.png 1019w" sizes="(max-width: 929px) 100vw, 929px" /> &lt;/figure>&lt;/p>
&lt;h2 id="conclusion-and-fusiondev-scenario">Conclusion and FusionDev scenario&lt;/h2>
&lt;p>As developers, we could build a robust, scalable and secure solution, developed with familiar tools like VS Code, PowerShell or Azure CLI. But what if we want to make this available to makers/citizen developers? What if we want to make sure, that this could be used in Power Apps and Power Automate? One way to achieve this, is creating a Power Platform custom connector. This way, makers can use the connector which calls our Azure Functions in a canvas app and display for example the groups in a gallery or table.&lt;/p>
&lt;p>Please note, that there are of course more fancy use cases, I will focus in this blog post on the code - If you have a good story, please reach out 🙂&lt;/p>
&lt;h2 id="more-resources-to-learn">More Resources to learn&lt;/h2>
&lt;p>Get started with Microsoft 365 development by signing up for a &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/dev-program">free Developer tenant&lt;/a>&lt;/p>
&lt;p>Join the &lt;a href="https://aka.ms/m365PnP">Microsoft 365 PnP Community&lt;/a> - join our calls and benefit from guidance, tools, samples&lt;/p></description></item><item><title>The friendly SMS reminder with Logic Apps, Azure Functions &amp; Key Vault</title><link>https://m365princess.com/blogs/2021-07-26-putting-the-fun-into-azure-functions-the-friendly-sms-reminder/</link><pubDate>Mon, 26 Jul 2021 20:37:57 +0000</pubDate><guid>https://m365princess.com/blogs/2021-07-26-putting-the-fun-into-azure-functions-the-friendly-sms-reminder/</guid><description>&lt;p>Yay, this is a new blog post series on how to put the FUN in Azure Functions 🙂&lt;/p>
&lt;p>Recently, I was learning about Azure Functions and came up with a sweet use case what I could do to combine Azure Functions and Logic Apps: I want to automatically call or text myself (using Twilio) to remind me of recurring tasks, like putting away the laundry , buying more Lego , or similar.&lt;/p>
&lt;p>There will be of course more sophisticated use cases, for the sake of an easy demonstration we will stick to this and use a simple SMS.&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>Azure subscription -If you don&amp;rsquo;t have one, you can get one for &lt;a href="https://azure.microsoft.com/free/" target="_blank" rel="noopener">free here&lt;/a>&lt;/li>
&lt;li>Twilio account&lt;/li>
&lt;li>Twilio trial number - trial comes for free - sign up here &lt;a href="https://www.twilio.com/referral/crd63h">Twilio Luise&lt;/a> (Transparency: This is a referral link, which will get you and me 10$ to spend on Twilio for cool future projects that aren&amp;rsquo;t covered by the trial. However, if you don&amp;rsquo;t like us to get the 10 bucks, you can also  sign up without the referral link with &lt;a href="https://www.twilio.com" target="_blank" rel="noopener">&lt;a href="https://www.twilio.com">https://www.twilio.com&lt;/a>)&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Here is our solution overview:&lt;/p>
&lt;img loading="lazy" class=" wp-image-782 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/AZTwilio.png" alt="" width="641" height="325" srcset="https://m365princess.com/wp-content/uploads/2021/07/AZTwilio.png 436w, https://m365princess.com/wp-content/uploads/2021/07/AZTwilio-300x152.png 300w" sizes="(max-width: 641px) 100vw, 641px" />
&lt;p>As you can see, we will touch some interesting things like&lt;/p>
&lt;ul>
&lt;li>Azure Logic Apps&lt;/li>
&lt;li>Azure Function App&lt;/li>
&lt;li>Azure Key Vault&lt;/li>
&lt;li>Managed Identity&lt;/li>
&lt;li>Twilio API&lt;/li>
&lt;/ul>
&lt;p>I will sometimes provide several approaches to reach the same goal, you can pick whatever feels right to you.&lt;/p>
&lt;h2 id="azure-resource-group--function-app">Azure Resource group &amp;amp; Function App&lt;/h2>
&lt;p>Ok, so we will create an Azure Function App that will do the heavy lifting for us. Our Azure Function needs a Resource group to live in, and we will first create the Resource Group and then add some resources to it.&lt;/p>
&lt;ul>
&lt;li>Go to &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a>&lt;/li>
&lt;li>Select  &lt;strong>Create a resource&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-737 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource.png" alt="" width="642" height="79" srcset="https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource.png 3832w, https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource-300x37.png 300w, https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource-1024x126.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource-768x95.png 768w, https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource-1536x189.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/AzureCreateResource-2048x252.png 2048w" sizes="(max-width: 642px) 100vw, 642px" />
&lt;ul>
&lt;li>Search for &lt;strong>Function App&lt;/strong> in the search bar&lt;/li>
&lt;li>Select &lt;strong>Function App&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-738 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppNew.png" alt="" width="352" height="328" srcset="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppNew.png 1008w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppNew-300x279.png 300w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppNew-768x715.png 768w" sizes="(max-width: 352px) 100vw, 352px" />
&lt;p>Select &lt;strong>Create&lt;/strong>&lt;/p>
&lt;img loading="lazy" class="wp-image-736 alignnone" src="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate.png" alt="" width="640" height="337" srcset="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate.png 2597w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate-300x158.png 300w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate-1024x539.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate-768x404.png 768w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate-1536x808.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreate-2048x1077.png 2048w" sizes="(max-width: 640px) 100vw, 640px" />
&lt;ul>
&lt;li>Fill out the form:
&lt;ul>
&lt;li>(1) Select your Azure subscription&lt;/li>
&lt;li>(2) Select &lt;strong>Create New&lt;/strong> to create a new Resource Group where our Function app lives in&lt;/li>
&lt;li>(3) Type in the name of your new Resource group&lt;/li>
&lt;li>(4) Select &lt;strong>OK&lt;/strong>&lt;/li>
&lt;li>(5) Give your app a name&lt;/li>
&lt;li>(6) Select &lt;strong>Node.js&lt;/strong> as Runtime stack&lt;/li>
&lt;li>(7) Select your Region&lt;/li>
&lt;li>(8) Select  &lt;strong>Review + Create&lt;/strong>&lt;figure class="wp-block-image size-large">&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" width="664" height="1024" class="wp-image-756" src="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreateSteps-1-664x1024.png" alt="" srcset="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreateSteps-1-664x1024.png 664w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreateSteps-1-195x300.png 195w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreateSteps-1-768x1184.png 768w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreateSteps-1-996x1536.png 996w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppCreateSteps-1.png 1242w" sizes="(max-width: 664px) 100vw, 664px" /> &lt;/figure>&lt;/p>
&lt;p>you can now review everything - as a last step&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>It will take a few seconds for Azure to deploy your Resource group and the Function App. You will be notified when everything is ready for you.&lt;/p>
&lt;p>As an alternative, you may want to create your Azure Function from within Visual Studio Code:\&lt;/p>
&lt;h2 id="creating-the-azure-function-in-visual-studio-code">Creating the Azure Function in Visual Studio Code&lt;/h2>
&lt;p>Please follow these steps:&lt;/p>
&lt;ol>
&lt;li>Install the Core Tools package with &lt;code>npm install -g azure-functions-core-tools@3 --unsafe-perm true&lt;/code>&lt;/li>
&lt;li>Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions" target="_blank" rel="noopener">Azure Functions extension for VS Code&lt;/a>&lt;/li>
&lt;li>Select &lt;strong>New Project&lt;/strong>&lt;/li>
&lt;li>Select a folder for your project&lt;/li>
&lt;li>Select a language&lt;/li>
&lt;li>Select &lt;strong>HTTP trigger&lt;/strong> as a template&lt;/li>
&lt;li>Type in a better name, like SampleTexts&lt;/li>
&lt;li>Select Authorization level &lt;strong>Function&lt;/strong>&lt;/li>
&lt;li>Select how you want to open your project - I prefer &lt;strong>Add to workspace&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>You will now see your &lt;strong>Local Project&lt;/strong> in the pane.&lt;/p>
&lt;img loading="lazy" class="wp-image-785 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode.png" alt="" width="641" height="195" srcset="https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode.png 3845w, https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode-300x91.png 300w, https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode-1024x312.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode-768x234.png 768w, https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode-1536x468.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/azfunctionVSCode-2048x624.png 2048w" sizes="(max-width: 641px) 100vw, 641px" />
&lt;p>You can now develop, debug and test your function locally and have source control of your code . I like this way more than jumping back and forth in the different blades of the Azure portal - also, I rely on IntelliSense pretty much ¯_(ツ)_/¯&lt;/p>
&lt;p>To deploy your function app, select the **deploy **icon. If you are not already logged into your Azure account, a browser window will pop up and ask you to sign in with your credentials.&lt;/p>
&lt;img loading="lazy" class="size-full wp-image-786 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/swploy.png" alt="" width="640" height="52" srcset="https://m365princess.com/wp-content/uploads/2021/07/swploy.png 640w, https://m365princess.com/wp-content/uploads/2021/07/swploy-300x24.png 300w" sizes="(max-width: 640px) 100vw, 640px" />
&lt;p>Once you selected the &lt;strong>deploy&lt;/strong> icon, you need to specify the function in Azure and need to confirm that you know what you are doing 🙂&lt;/p>
&lt;img loading="lazy" class=" wp-image-787 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/confirmdeploy.png" alt="" width="637" height="153" srcset="https://m365princess.com/wp-content/uploads/2021/07/confirmdeploy.png 829w, https://m365princess.com/wp-content/uploads/2021/07/confirmdeploy-300x72.png 300w, https://m365princess.com/wp-content/uploads/2021/07/confirmdeploy-768x184.png 768w" sizes="(max-width: 637px) 100vw, 637px" />
&lt;p>Azure needs again a few seconds to deploy and Visual Studio Code will let you know once this is done ✅&lt;/p>
&lt;img loading="lazy" class=" wp-image-789 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/confirmdeploysuccess.png" alt="" width="641" height="123" srcset="https://m365princess.com/wp-content/uploads/2021/07/confirmdeploysuccess.png 813w, https://m365princess.com/wp-content/uploads/2021/07/confirmdeploysuccess-300x58.png 300w, https://m365princess.com/wp-content/uploads/2021/07/confirmdeploysuccess-768x147.png 768w" sizes="(max-width: 641px) 100vw, 641px" />
&lt;h2 id="twilio">Twilio&lt;/h2>
&lt;p>Before we add a function and some code to it, let&amp;rsquo;s understand first what we will be doing: We want to automagically send a text message to our own phone number. We do this by using our brand new Twilio account and log in with our account SID and an Auth token. We will also need our own phone number and the trial number we got from Twilio. As we do not want to hard-code these credentials, we will store them in the Function app configuration and refer to the configuration in our code. In a second scenario below, I will show how to store the credentials even more secure in an Azure Key Vault. But first, head over to Twilio, create an account and get a trial number. Your Twilio Dashboard should look a little like this:&lt;/p>
&lt;img loading="lazy" class="wp-image-744 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard.png" alt="" width="640" height="329" srcset="https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard.png 2076w, https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard-300x154.png 300w, https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard-1024x527.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard-768x395.png 768w, https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard-1536x790.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/twiliodashboard-2048x1054.png 2048w" sizes="(max-width: 640px) 100vw, 640px" />
&lt;ul>
&lt;li>Copy &lt;strong>Account SID&lt;/strong>&lt;/li>
&lt;li>Copy &lt;strong>Auth token&lt;/strong>&lt;/li>
&lt;li>Copy your Trial number&lt;/li>
&lt;li>Have your own phone number present&lt;/li>
&lt;/ul>
&lt;p>We will need these values in a moment!&lt;/p>
&lt;h2 id="azure-function-app-configuration">&lt;strong>Azure Function App configuration&lt;/strong>&lt;/h2>
&lt;p>Head over to your Azure Function App&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Configuration&lt;/strong>&lt;/li>
&lt;li>(2) Select &lt;strong>New application setting&lt;/strong>&lt;/li>
&lt;li>(3) Type in &lt;strong>TWILIO_SID&lt;/strong> as name&lt;/li>
&lt;li>(4) Paste in your copied &lt;strong>Account SID&lt;/strong> as the value&lt;/li>
&lt;li>(5) Select &lt;strong>OK&lt;/strong>&lt;figure class="wp-block-image size-large">&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" class="wp-image-750" src="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfiguration-2-1024x527.png" alt="" width="639" height="329" srcset="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfiguration-2-1024x527.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfiguration-2-300x154.png 300w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfiguration-2-768x395.png 768w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfiguration-2-1536x791.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfiguration-2-2048x1054.png 2048w" sizes="(max-width: 639px) 100vw, 639px" /> &lt;/figure>&lt;/p>
&lt;p>Repeat step 1-5 from above for&lt;/p>
&lt;ul>
&lt;li>TWILIO_TOKEN&lt;/li>
&lt;li>SENDER_NUMBER (your Twilio trial number)&lt;/li>
&lt;li>RECIPIENT_NUMBER (your phone number)&lt;/li>
&lt;/ul>
&lt;p>Hit save Your App setting should now look like this:&lt;figure class="wp-block-image size-large is-resized">&lt;/p>
&lt;p>&lt;img loading="lazy" class="wp-image-752" src="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfigurationnewsettings-1-1024x946.png" alt="" width="641" height="592" srcset="https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfigurationnewsettings-1-1024x946.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfigurationnewsettings-1-300x277.png 300w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfigurationnewsettings-1-768x709.png 768w, https://m365princess.com/wp-content/uploads/2021/07/FunctionAppConfigurationnewsettings-1.png 1140w" sizes="(max-width: 641px) 100vw, 641px" /> &lt;/figure>&lt;/p>
&lt;h2 id="azure-function-app--add-function">&lt;strong>Azure Function App -add function&lt;/strong>&lt;/h2>
&lt;p>Now its time to add some functionality to our app:&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Functions&lt;/strong>&lt;/li>
&lt;li>(2) Select &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>(3) Select HTTP trigger&lt;/li>
&lt;li>(4) Type in a name&lt;/li>
&lt;li>(5) Select &lt;strong>Add&lt;/strong>&lt;figure class="wp-block-image size-large">&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" class="wp-image-757" src="https://m365princess.com/wp-content/uploads/2021/07/AddFunctions-1-1024x513.png" alt="" width="639" height="320" srcset="https://m365princess.com/wp-content/uploads/2021/07/AddFunctions-1-1024x513.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/AddFunctions-1-300x150.png 300w, https://m365princess.com/wp-content/uploads/2021/07/AddFunctions-1-768x385.png 768w, https://m365princess.com/wp-content/uploads/2021/07/AddFunctions-1-1536x769.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/AddFunctions-1-2048x1026.png 2048w" sizes="(max-width: 639px) 100vw, 639px" /> &lt;/figure>&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Code + Test&lt;/strong>&lt;/li>
&lt;li>(2) Delete the sample code in the index.js file&lt;/li>
&lt;li>(3) Paste in the following snippet:&lt;/li>
&lt;li>(4) Don&amp;rsquo;t forget to hit the save button - I always do ¯_(ツ)_/¯&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">accountSid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">TWILIO_SID&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">authToken&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">TWILIO_TOKEN&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;twilio&amp;#34;&lt;/span>&lt;span class="p">)(&lt;/span>&lt;span class="nx">accountSid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">authToken&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">create&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">from&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SENDER_NUMBER&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Woohoo- it worked!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">to&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RECIPIENT_NUMBER&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Message sent&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;YAY it worked - girl check your phone &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">done&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Twilio Error: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">message&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34; -- &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">code&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">status&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="sb">`Twilio Error Message: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">\nTwilio Error code: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">done&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Please note&lt;/p>
&lt;ul>
&lt;li>how we refer to the app configuration settings with environment variables - If this is your first Azure Function app, I can highly recommend that you have a look over here: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=v2">JavaScript developer reference for Azure Functions | Microsoft Docs&lt;/a>&lt;/li>
&lt;li>that we of course want a success (for status 200/default) or error message (for status 500)&lt;/li>
&lt;li>More information on how to work with Twilio - including code - &lt;a href="https://www.twilio.com/blog/how-to-send-sms-node-js">How to Send an SMS with Node.js (twilio.com)&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Now Select &lt;strong>Overview&lt;/strong> and then &lt;strong>Get Function URL -&lt;/strong> copy the URL- we will need this in the next step.&lt;/p>
&lt;h2 id="azure-logic-apps-workflow">Azure Logic Apps workflow&lt;/h2>
&lt;p>Let&amp;rsquo;s now have some more fun in Azure and use a Logic Apps workflow to trigger our Azure Function. We will first add a Logic App to our resource group and then define a workflow within that app. We will cover two options how to call our Azure Function, option A with the built-in ‘Call an Azure Function&amp;rsquo; action and as option B with an HTTP action. In both cases, navigate to your resource group&amp;gt;&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Create a resource&lt;/strong>&lt;/li>
&lt;li>Search in the search box for &lt;strong>Logic App&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Logic App&lt;/strong> from the results&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Now fill out the form as follows:&lt;/p>
&lt;ul>
&lt;li>(1) Select your Azure subscription&lt;/li>
&lt;li>(2) Select your Resource Group&lt;/li>
&lt;li>(3) Type in a name&lt;/li>
&lt;li>(4) Select your region&lt;/li>
&lt;li>(5) Select &lt;strong>Review + Create&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>You can now review your input - when everything looks OK, then select &lt;strong>Create&lt;/strong>&lt;/p>
&lt;p>Azure will need a few seconds to deploy your brand new resource and notify you once this is completed. Once this is done,&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Workflows&lt;/strong>&lt;/li>
&lt;li>(2) Select &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>(3) Type in a name for your workflow&lt;/li>
&lt;li>(4) Select &lt;strong>Stateful&lt;/strong>&lt;/li>
&lt;li>(5) Select &lt;strong>Create&lt;/strong>&lt;figure class="wp-block-image size-large">&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" class="wp-image-758" src="https://m365princess.com/wp-content/uploads/2021/07/Createworkflow-1024x521.png" alt="" width="641" height="326" srcset="https://m365princess.com/wp-content/uploads/2021/07/Createworkflow-1024x521.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/Createworkflow-300x153.png 300w, https://m365princess.com/wp-content/uploads/2021/07/Createworkflow-768x391.png 768w, https://m365princess.com/wp-content/uploads/2021/07/Createworkflow-1536x782.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/Createworkflow-2048x1042.png 2048w" sizes="(max-width: 641px) 100vw, 641px" /> &lt;/figure>&lt;/p>
&lt;p>Now select your new workflow and follow these steps:&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Designer&lt;/strong>&lt;/li>
&lt;li>(2) From the left hands pane, select &lt;strong>Recurrence&lt;/strong> as a trigger&lt;/li>
&lt;li>(3) Fill out the form as follows for a weekly reminder on Sundays (as an example) by adding parameters &lt;strong>Start time&lt;/strong> and &lt;strong>On these days&lt;/strong> - For testing purposes I&amp;rsquo;d choose a higher frequency 🙂&lt;figure class="wp-block-image size-large is-resized">&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" class="wp-image-760" src="https://m365princess.com/wp-content/uploads/2021/07/CreateworkflowTrigger-1024x592.png" alt="" width="640" height="370" srcset="https://m365princess.com/wp-content/uploads/2021/07/CreateworkflowTrigger-1024x592.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/CreateworkflowTrigger-300x174.png 300w, https://m365princess.com/wp-content/uploads/2021/07/CreateworkflowTrigger-768x444.png 768w, https://m365princess.com/wp-content/uploads/2021/07/CreateworkflowTrigger.png 1058w" sizes="(max-width: 640px) 100vw, 640px" /> &lt;/figure>&lt;/p>
&lt;p>as stated above, there are (at least) two ways to call our Azure function, this is:&lt;/p>
&lt;h3 id="option-a-with-the-built-in-8216call-an-azure-function">Option A with the built-in ‘Call an Azure Function'&lt;/h3>
&lt;ul>
&lt;li>Select the &lt;strong>+&lt;/strong> icon in the designer&lt;/li>
&lt;li>Select &lt;strong>Add an action&lt;/strong>&lt;/li>
&lt;li>In the left hands pane, search for &lt;strong>Azure Fun&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Call an Azure Function&lt;/strong>&lt;/li>
&lt;li>Select your Azure Function App&lt;/li>
&lt;li>Give your connection a name&lt;/li>
&lt;li>Select the function within your Functions app&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;figure class="wp-block-image size-large">&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" class="wp-image-761" src="https://m365princess.com/wp-content/uploads/2021/07/image-1-1024x624.png" alt="" width="640" height="390" srcset="https://m365princess.com/wp-content/uploads/2021/07/image-1-1024x624.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/image-1-300x183.png 300w, https://m365princess.com/wp-content/uploads/2021/07/image-1-768x468.png 768w, https://m365princess.com/wp-content/uploads/2021/07/image-1.png 1075w" sizes="(max-width: 640px) 100vw, 640px" /> &lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Get&lt;/strong> as Method&lt;/li>
&lt;li>Save your app.&lt;/li>
&lt;li>Select &lt;strong>Overview&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Run Trigger&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Run&lt;/strong>&lt;figure class="wp-block-image size-large">&lt;/li>
&lt;/ul>
&lt;p>&lt;img loading="lazy" class="wp-image-762" src="https://m365princess.com/wp-content/uploads/2021/07/image-2-779x1024.png" alt="" width="641" height="843" srcset="https://m365princess.com/wp-content/uploads/2021/07/image-2-779x1024.png 779w, https://m365princess.com/wp-content/uploads/2021/07/image-2-228x300.png 228w, https://m365princess.com/wp-content/uploads/2021/07/image-2-768x1009.png 768w, https://m365princess.com/wp-content/uploads/2021/07/image-2.png 1054w" sizes="(max-width: 641px) 100vw, 641px" /> &lt;/figure>&lt;/p>
&lt;p>Note the custom output body that we defined in our Azure Function&lt;/p>
&lt;p>Now let&amp;rsquo;s discover the other option to call our Azure function:&lt;/p>
&lt;h3 id="option-b-with-the-generic-http-action">Option B with the generic HTTP action&lt;/h3>
&lt;p>Create your workflow as above, but instead of searching for &lt;strong>Azure Fun&lt;/strong>, search for &lt;strong>HTTP&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>HTTP&lt;/strong>&lt;/li>
&lt;li>Select Method &lt;strong>GET&lt;/strong>&lt;/li>
&lt;li>Enter the copied Azure functions URL as URI&lt;/li>
&lt;li>&lt;strong>Save&lt;/strong> your workflow&lt;/li>
&lt;/ul>
&lt;p>Once again, you can see the outputs of your workflow run ✅&lt;figure class="wp-block-image size-large">&lt;/p>
&lt;p>&lt;img loading="lazy" class="wp-image-763" src="https://m365princess.com/wp-content/uploads/2021/07/image-3-711x1024.png" alt="" width="646" height="930" srcset="https://m365princess.com/wp-content/uploads/2021/07/image-3-711x1024.png 711w, https://m365princess.com/wp-content/uploads/2021/07/image-3-208x300.png 208w, https://m365princess.com/wp-content/uploads/2021/07/image-3-768x1107.png 768w, https://m365princess.com/wp-content/uploads/2021/07/image-3.png 1039w" sizes="(max-width: 646px) 100vw, 646px" /> &lt;/figure>&lt;/p>
&lt;p>Next step, makes this even more secure by storing our Twilio token and the Twilio SID in an Azure Key vault so that we can refer to it in our app configuration.&lt;/p>
&lt;h2 id="azure-key-vault">Azure Key Vault&lt;/h2>
&lt;p>Head over to your Resource Group&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Create a resource&lt;/strong>&lt;/li>
&lt;li>Search in the search box for &lt;strong>Key vault&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Key Vault&lt;/strong> from the results&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Now fill out the form as follows:&lt;/p>
&lt;img loading="lazy" class="wp-image-770 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/CreateKeyVault.png" alt="" width="643" height="948" srcset="https://m365princess.com/wp-content/uploads/2021/07/CreateKeyVault.png 1302w, https://m365princess.com/wp-content/uploads/2021/07/CreateKeyVault-203x300.png 203w, https://m365princess.com/wp-content/uploads/2021/07/CreateKeyVault-694x1024.png 694w, https://m365princess.com/wp-content/uploads/2021/07/CreateKeyVault-768x1133.png 768w, https://m365princess.com/wp-content/uploads/2021/07/CreateKeyVault-1042x1536.png 1042w" sizes="(max-width: 643px) 100vw, 643px" />
&lt;p>Review and select &lt;strong>Create.&lt;/strong>&lt;/p>
&lt;p>It will take a couple of seconds again for Azure to deploy your resource and you will be notified once the Key vault is ready for you.&lt;/p>
&lt;p>In our shiny new Key Vault, we need to store the key/value pairs for both Twilio SID and Twilio Auth Token:&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Secrets&lt;/strong>&lt;/li>
&lt;li>(2) Select &lt;strong>Generate/Import&lt;/strong>&lt;/li>
&lt;li>Fill in the form as follows:
&lt;ul>
&lt;li>Name: **TWILIOTOKEN **(watch out, no underscores allowed)&lt;/li>
&lt;li>Value: the  copied Twilio token value from your Twilio dashboard&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Select &lt;strong>Create&lt;/strong>&lt;/li>
&lt;li>Repeat for &lt;strong>TWILIOSID&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-773 alignnone" src="https://m365princess.com/wp-content/uploads/2021/07/createsecrets.png" alt="" width="642" height="215" srcset="https://m365princess.com/wp-content/uploads/2021/07/createsecrets.png 2633w, https://m365princess.com/wp-content/uploads/2021/07/createsecrets-300x100.png 300w, https://m365princess.com/wp-content/uploads/2021/07/createsecrets-1024x343.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/createsecrets-768x257.png 768w, https://m365princess.com/wp-content/uploads/2021/07/createsecrets-1536x515.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/createsecrets-2048x686.png 2048w" sizes="(max-width: 642px) 100vw, 642px" />
&lt;p>We will use these two secrets later again in our Azure Function, but to make this work, we first need to enable system-assigned Managed Identity to it and then add an access policy for our function app. If you never heard about that, &lt;a href="https://blog.yannickreekmans.be/secretless-applications-managed-identities/">Yannick Reekmans blogged a series about secretless applications&lt;/a>.&lt;/p>
&lt;p>Head over to your Function app again and&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Identity&lt;/strong>&lt;/li>
&lt;li>(2) Switch the Status toggle to &lt;strong>On&lt;/strong>&lt;/li>
&lt;li>(3) Select &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-774 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/IdentityOn.png" alt="" width="642" height="438" srcset="https://m365princess.com/wp-content/uploads/2021/07/IdentityOn.png 1509w, https://m365princess.com/wp-content/uploads/2021/07/IdentityOn-300x205.png 300w, https://m365princess.com/wp-content/uploads/2021/07/IdentityOn-1024x699.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/IdentityOn-768x524.png 768w" sizes="(max-width: 642px) 100vw, 642px" />
&lt;p>A Pop-up displaying the following message appears, select &lt;strong>Yes&lt;/strong>&lt;/p>
&lt;img loading="lazy" class="wp-image-775 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/confirm.png" alt="" width="635" height="77" srcset="https://m365princess.com/wp-content/uploads/2021/07/confirm.png 1777w, https://m365princess.com/wp-content/uploads/2021/07/confirm-300x36.png 300w, https://m365princess.com/wp-content/uploads/2021/07/confirm-1024x124.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/confirm-768x93.png 768w, https://m365princess.com/wp-content/uploads/2021/07/confirm-1536x187.png 1536w" sizes="(max-width: 635px) 100vw, 635px" />
&lt;p>Now we will add the access policy to our Key vault so that our function may read the secrets. Head over to the Key vault and&lt;/p>
&lt;ul>
&lt;li>(1) Select &lt;strong>Access policy&lt;/strong>&lt;/li>
&lt;li>(2) Select &lt;strong>Add Access Policy&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-776 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/accesspolicy.png" alt="" width="641" height="295" srcset="https://m365princess.com/wp-content/uploads/2021/07/accesspolicy.png 2244w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicy-300x138.png 300w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicy-1024x471.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicy-768x353.png 768w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicy-1536x706.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicy-2048x942.png 2048w" sizes="(max-width: 641px) 100vw, 641px" />
&lt;p>Now fill out the form as follows:&lt;/p>
&lt;ul>
&lt;li>Key permissions: &lt;strong>Get&lt;/strong> and &lt;strong>List&lt;/strong>&lt;/li>
&lt;li>Secret permissions: &lt;strong>Get&lt;/strong> and &lt;strong>List&lt;/strong>&lt;/li>
&lt;li>Certificate permissions: &lt;strong>Get&lt;/strong> and &lt;strong>List&lt;/strong>&lt;/li>
&lt;li>Select principal:
&lt;ul>
&lt;li>Search for the name of your functions app&lt;/li>
&lt;li>Select it&lt;/li>
&lt;li>Select &lt;strong>Select&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Select &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Don&amp;rsquo;t forget to &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-777 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform.png" alt="" width="639" height="359" srcset="https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform.png 2252w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform-300x169.png 300w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform-1024x575.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform-768x431.png 768w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform-1536x863.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform-2048x1150.png 2048w, https://m365princess.com/wp-content/uploads/2021/07/accesspolicyform-800x450.png 800w" sizes="(max-width: 639px) 100vw, 639px" />
&lt;p>Now that our function app has permission to read the secrets from our Key vault, we will reference to the secret in the functions app configuration and get rid of the hard coded values in there. For this, we only need the name of our vault and the name of the secrets TWILIOTOKEN and TWILIOSID.&lt;/p>
&lt;p>Let&amp;rsquo;s now add these to the app configuration of our functions app. Head over to the functions app and&lt;/p>
&lt;ul>
&lt;li>Select &lt;strong>Configuration&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>New Application Settings&lt;/strong>&lt;/li>
&lt;/ul>
&lt;img loading="lazy" class="wp-image-779 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/appconfig.png" alt="" width="640" height="269" srcset="https://m365princess.com/wp-content/uploads/2021/07/appconfig.png 2255w, https://m365princess.com/wp-content/uploads/2021/07/appconfig-300x126.png 300w, https://m365princess.com/wp-content/uploads/2021/07/appconfig-1024x430.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/appconfig-768x323.png 768w, https://m365princess.com/wp-content/uploads/2021/07/appconfig-1536x645.png 1536w, https://m365princess.com/wp-content/uploads/2021/07/appconfig-2048x860.png 2048w" sizes="(max-width: 640px) 100vw, 640px" />
&lt;ul>
&lt;li>Fill out the form as follows:
&lt;ul>
&lt;li>Name: &lt;strong>TWILIOSID&lt;/strong>&lt;/li>
&lt;li>Value: &lt;strong>@Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret)&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>OK&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Repeat for &lt;strong>TWILIOTOKEN&lt;/strong>&lt;/li>
&lt;li>Don&amp;rsquo;t forget to *&lt;strong>*Save&lt;/strong>&lt;br>
**&lt;/li>
&lt;/ul>
&lt;p>Now let&amp;rsquo;s get rid of our hard coded values in the app configuration to clean up things:&lt;/p>
&lt;img loading="lazy" class="wp-image-780 alignleft" src="https://m365princess.com/wp-content/uploads/2021/07/cleanup.png" alt="" width="643" height="37" srcset="https://m365princess.com/wp-content/uploads/2021/07/cleanup.png 1708w, https://m365princess.com/wp-content/uploads/2021/07/cleanup-300x17.png 300w, https://m365princess.com/wp-content/uploads/2021/07/cleanup-1024x59.png 1024w, https://m365princess.com/wp-content/uploads/2021/07/cleanup-768x44.png 768w, https://m365princess.com/wp-content/uploads/2021/07/cleanup-1536x88.png 1536w" sizes="(max-width: 643px) 100vw, 643px" />
&lt;p>Select the &lt;strong>Delete&lt;/strong> icon - please check twice to delete the right settings!&lt;/p>
&lt;p>Now we need to make sure that our Azure function looks at the right app settings.&lt;br>
Select &lt;strong>Functions&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Select your function&lt;/li>
&lt;li>Select &lt;strong>Code + Test&lt;/strong>&lt;/li>
&lt;li>Replace the old environment variables by the new ones&lt;/li>
&lt;li>Don&amp;rsquo;t forget to &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>**That&amp;rsquo;s it - you made it work! And this is the result: **&lt;/p>
&lt;p>&lt;a href="https://m365princess.com/wp-content/uploads/2021/07/success.jpg">&lt;img loading="lazy" class="alignleft wp-image-784" src="https://m365princess.com/wp-content/uploads/2021/07/success.jpg" alt="" width="336" height="727" srcset="https://m365princess.com/wp-content/uploads/2021/07/success.jpg 591w, https://m365princess.com/wp-content/uploads/2021/07/success-139x300.jpg 139w, https://m365princess.com/wp-content/uploads/2021/07/success-473x1024.jpg 473w" sizes="(max-width: 336px) 100vw, 336px" />&lt;/a>&lt;/p>
&lt;p>&lt;strong>You successfully&lt;/strong>&lt;/p>
&lt;p>✅ created a resource group&lt;/p>
&lt;p>✅ build a function app with one function&lt;/p>
&lt;p>✅ called an API in that function&lt;/p>
&lt;p>✅ stored secrets in a Key Vault&lt;/p>
&lt;p>✅ assigned Managed Identity&lt;/p>
&lt;p>✅ referenced to your Key vault in your function app configuration&lt;/p>
&lt;p>✅ created a Logic app that kicks off the function weekly&lt;/p>
&lt;h2 id="conclusion--whats-next">Conclusion &amp;amp; what&amp;rsquo;s next? &lt;/h2>
&lt;p>Both approaches low code (Logic Apps) and Code (Azure Functions) tie in nicely together. Some thoughts though for everyone who wondered:&lt;/p>
&lt;ul>
&lt;li>yes, you could get the same result with about 42 other approaches - there is no THE right way to do it&lt;/li>
&lt;li>yes, you can replace the the Logic App with a Power Automate flow - which comes with some disadvantages:
&lt;ul>
&lt;li>the HTTP action requires a premium license&lt;/li>
&lt;li>you can&amp;rsquo;t easily monitor your entire solution in Azure as your flow would now not live in your resource group anymore&lt;/li>
&lt;li>your flow would run in a user&amp;rsquo;s context - or would need a service account ( MFA)&lt;/li>
&lt;li>you can&amp;rsquo;t easily templatize and deploy and redeploy your entire solution anymore as the flow is not part of your resource group.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>no, I did not want to use the Twilio connector in Power Automate or in Logic Apps which allows to send SMS &lt;em>without a single line of code&lt;/em> - I was looking for a well documented service outside of Azure to showcase that we can call any API in Azure Functions. If you want to level up:&lt;/li>
&lt;li>the amazing &lt;a href="https://twitter.com/ChloeCondon">Chloe Condon&lt;/a> delivered a session on IOT and Azure Functions, &lt;a href="https://www.youtube.com/watch?v=_K30eBabb3A">watch the video here&lt;/a>&lt;/li>
&lt;li>yes, you can do all of the above in Azure CLI as well, to get you started, please read here: &lt;a href="https://docs.microsoft.com/en-us/cli/azure/service-page/azure%20functions?view=azure-cli-latest">Azure Functions | Microsoft Docs&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>You have a more fancy use case? Let me know about it! Just imagine that we can integrate other services/data in Azure and/or Microsoft 365 and beyond - and decide if we want to do this in a low code or code manner.&lt;/p>
&lt;p>In my next post I will show how to templatize and deploy a solution.&lt;/p></description></item><item><title>Should you attend yet another meeting?</title><link>https://m365princess.com/blogs/2021-07-12-should-you-attend-yet-another-meeting/</link><pubDate>Mon, 12 Jul 2021 09:51:53 +0000</pubDate><guid>https://m365princess.com/blogs/2021-07-12-should-you-attend-yet-another-meeting/</guid><description>&lt;p>Do you suffer from meeting overload as well?&lt;/p>
&lt;p>We joke around, that “this meeting could have been an email” and still our calendars are filled with meetings, meetings and even more meetings. Sometimes, our time is double or even triple-booked. We “wait for everyone to join” because we know that people sit in hour-long or day-long back-to back sessions. This comes with a lot of disadvantages… Our ability to focus on the content is going down, we zone out, we do other stuff (like catching up on mails and messages while we are stuck in a meeting ).&lt;/p>
&lt;p>We need to develop a new skill: determining if our presence (not only having our account joined the meeting but proactively contributing to the meetings objectives) is required or not. To make better decisions when you are about to hit the “accept, use this handy flowchart:&lt;/p>
&lt;p>&lt;img alt="flow chart" src="https://m365princess.com/images/should-I-attend.png">&lt;/p></description></item><item><title>LearnTogether: Build Apps with Microsoft Graph</title><link>https://m365princess.com/blogs/2021-04-15-learntogether-build-apps-with-microsoft-graph/</link><pubDate>Thu, 15 Apr 2021 14:43:10 +0000</pubDate><guid>https://m365princess.com/blogs/2021-04-15-learntogether-build-apps-with-microsoft-graph/</guid><description>&lt;p>This week, I had the great pleasure to attend Microsoft’s Learn Together: Build apps for Microsoft Graph event. I also did sketchnotes of the event, you may find high resolution files (images, gifs and mp4) are &lt;a href="https://github.com/LuiseFreese/blog/tree/main/sketchnotes/learnTogether" target="_blank" rel="noopener noreferrer">available here on GitHub.&lt;/a>&lt;/p>
&lt;p>Call to action: Learn how to make apps with Microsoft Graph and complete this &lt;a href="https://docs.microsoft.com/en-us/learn/challenges?id=7ca6c7f7-cc0d-4c31-9b74-5ce5658787e7&amp;WT.mc_id=m365-24198-cxa" target="_blank" rel="noopener noreferrer">Microsoft Graph Learn Challenge&lt;/a>&lt;/p>
&lt;img loading="lazy" class="aligncenter size-full wp-image-711" src="https://m365princess.com/wp-content/uploads/2021/04/learntogetherGraph-segment1.gif" alt="" width="1578" height="1080" />
&lt;p> &lt;/p>
&lt;img loading="lazy" class="aligncenter wp-image-712" src="https://m365princess.com/wp-content/uploads/2021/04/learntogetherGraph-segment2.gif" alt="" width="1589" height="1088" />
&lt;p> &lt;/p>
&lt;img loading="lazy" class="aligncenter size-full wp-image-713" src="https://m365princess.com/wp-content/uploads/2021/04/learntogetherGraph-segment3to5.gif" alt="" width="1578" height="1080" /></description></item><item><title>Microsoft Graph Fundamentals learning path – Module 3</title><link>https://m365princess.com/blogs/2021-04-07-microsoft-graph-fundamentals-learning-path-module-3/</link><pubDate>Wed, 07 Apr 2021 13:59:31 +0000</pubDate><guid>https://m365princess.com/blogs/2021-04-07-microsoft-graph-fundamentals-learning-path-module-3/</guid><description>&lt;h1 id="doing-microsoft-graph-fundamentals-learning-path-on-ms-learn---part-3">Doing Microsoft Graph Fundamentals learning path on MS Learn - Part 3&lt;/h1>
&lt;p>This is already the third part of my little series on what it takes to do the &lt;a href="https://docs.microsoft.com/en-us/learn/paths/m365-msgraph-fundamentals/">Microsoft Graph Fundamentals Learning Path&lt;/a> on Microsoft Learn. If you missed &lt;a href="https://m365princess.com/microsoft-graph-fundamentals-learning-path-module-1/">part 1&lt;/a> or &lt;a href="https://m365princess.com/microsoft-graph-fundamentals-learning-path-module-2/">part 2&lt;/a>, it would be a good idea to catch up first, as the parts build upon each other.&lt;/p>
&lt;p>After we already saw how easily we could configure a JavaScript application to retrieve Microsoft 365 data using Microsoft Graph in the last module, we will focus this time on how to access user photo information by using Microsoft Graph.&lt;/p>
&lt;p>This module is supposed only to take 17 minutes to complete - timer set 😇.&lt;/p>
&lt;h2 id="intro">intro&lt;/h2>
&lt;p>Our goal is to insert a user profile in our app that we already used in the last module. We will need to authenticate our user using Microsoft identity and receive an access token and call Microsoft Graph API with this token. - If you now wonder, &amp;lsquo;wait, but why?&amp;rsquo; it&amp;rsquo;s a good idea to reread part 2 of my summary or do the last module.&lt;/p>
&lt;p>After making us aware of WHY we should display a user picture, we learn that there are several ways to get profile picture information with Microsoft Graph, for example
&lt;code>GET https://graph.microsoft.com/v1.0/me/photo/$value&lt;/code> gets us the image of the signed-in user itself. I used the Graph Explorer to check that:&lt;/p>
&lt;p>&lt;img alt="get user profile picture in Graph Explorer" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFun-image.png">&lt;/p>
&lt;p>&lt;a href="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFun-image.png">https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFun-image.png&lt;/a>&lt;/p>
&lt;p>Let&amp;rsquo;s do an exercise:&lt;/p>
&lt;h2 id="exercise---use-microsoft-graph-in-your-web-application-to-retrieve-a-users-profile-photo">Exercise - Use Microsoft Graph in your web application to retrieve a user&amp;rsquo;s profile photo&lt;/h2>
&lt;h3 id="build-upon-module-2">build upon module 2&lt;/h3>
&lt;p>As we are building upon the last module, we will not need to clone the repository again. The unit quickly walks us through the four primary files for our app &lt;code>index.html&lt;/code>, &lt;code>auth.js&lt;/code>, &lt;code>graph.js&lt;/code> and &lt;code>ui.js&lt;/code> to make us aware of what they will do and repeats the steps we did in module 2 to insert tenant ID and application ID.&lt;/p>
&lt;h3 id="now-the-new-part">Now the new part&lt;/h3>
&lt;ol>
&lt;li>copy - paste two code snippets into the &lt;code>index.html&lt;/code> file to create and style a button with an image element&lt;/li>
&lt;li>add a function to the &lt;code>graph.js&lt;/code> file to call Microsoft Graph API and retrieve the photo of the signed-in user&lt;/li>
&lt;li>add a function to the &lt;code>ui.js&lt;/code> file that displays the image that we got from Graph into the image element that we created and styled in step 1&lt;/li>
&lt;li>copy-paste a snippet to show a button that a signed-in user can click on to view their profile picture.&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="6 lines to get the profile picture from Graph" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/get-pic.png">&lt;/p>
&lt;h3 id="time-to-run-our-app">time to run our app&lt;/h3>
&lt;p>Like in Module 2, open your terminal (I use the built-in Powershell in Visual Studio Code) and type in &lt;code>npm start&lt;/code>, which will open your browser with &lt;code>localhost:8080&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>Click on &lt;strong>Sign in with Microsoft&lt;/strong>&lt;/li>
&lt;li>Click the &lt;code>show profile picture&lt;/code> button&lt;/li>
&lt;li>see that profile pic? YAY - time for a happy dance- You made it!&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="your MS Learn Trophy" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/tropy.png">&lt;/p>
&lt;h2 id="conclusion">conclusion&lt;/h2>
&lt;p>As I did module 2 just a couple of days before, I did not need to reinvent the wheel, and it took me way less than 17 minutes to complete this module 3 - but I realized that I could reuse this app over and over again to whenever I need to sign-in a user or want to display information that I can get from Microsoft Graph API.&lt;/p>
&lt;p>I wrote this summary of the whole learning path because I worked with Microsoft Graph before, and although I don&amp;rsquo;t write (a lot) of code daily, these modules felt doable - even if you are not a super experienced developer. I hope there will be even more cool content!&lt;/p>
&lt;p>If you now read this and wonder if you could build applications with Microsoft Graph as well: Yes, you can! To learn more - from developers, please join Microsoft&amp;rsquo;s Learn Together event: &lt;a href="https://learntogether-graph.splashthat.com/">Building apps with Microsoft Graph&lt;/a> on April 14!&lt;/p>
&lt;p>I would love to hear your feedback! Did you start this learning path? Or completed it? What were your aha moments? Where did you struggle? What content is now needed to take you to the next level? Please comment below!&lt;/p>
&lt;p>🦒&lt;/p></description></item><item><title>Microsoft Graph Fundamentals learning path – Module 2</title><link>https://m365princess.com/blogs/2021-04-02-microsoft-graph-fundamentals-learning-path-module-2/</link><pubDate>Fri, 02 Apr 2021 19:56:13 +0000</pubDate><guid>https://m365princess.com/blogs/2021-04-02-microsoft-graph-fundamentals-learning-path-module-2/</guid><description>&lt;h1 id="doing-microsoft-graph-fundamentals-learning-path-on-ms-learn---part-2">Doing Microsoft Graph Fundamentals learning path on MS Learn - Part 2&lt;/h1>
&lt;p>Welcome back to my series about the &lt;a href="https://docs.microsoft.com/en-us/learn/paths/m365-msgraph-fundamentals/">Microsoft Graph Fundamentals learning path&lt;/a> on Microsoft Learn. This is part 2; if you did not read &lt;a href="https://m365princess.com/microsoft-graph-fundamentals-learning-path-module-1/">part 1&lt;/a> yet, this is your chance to catch up! I will stay here and wait for you with a coffee ☕.&lt;/p>
&lt;p>This module is called &lt;strong>Configure a JavaScript application to retrieve Microsoft 365 data using Microsoft Graph&lt;/strong> and we start with an&lt;/p>
&lt;h2 id="intro">intro&lt;/h2>
&lt;p>We are still sticking to the business scenario from module 1: We want to create an app that can access email, chats, files, meetings. To authenticate users, Microsoft 365 uses Microsoft Identity, and we will need to use Microsoft Identity and Microsoft Graph to get the data we want to display in our app by using &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-overview">Microsoft Authentication Library(MSAL)&lt;/a>.&lt;/p>
&lt;p>Wait, what? Don&amp;rsquo;t worry if you did not completely understand this. We will do this step-by-step.&lt;/p>
&lt;h2 id="understand-the-role-of-azure-active-directory-with-microsoft-graph">Understand the role of Azure Active Directory with Microsoft Graph&lt;/h2>
&lt;p>OK, we already understood that Microsoft Graph is THE API to access data in Microsoft 365 - but of course, this data needs to be secured because we don&amp;rsquo;t want everyone to access them, right? This is what we need Microsoft Identity platform for. Microsoft identity ensures that only authorized users (delegated permissions) and apps (application permissions) access data stored in Microsoft 365. The challenge now is to link Microsoft Identity (of which we will use Azure Active Directory) to our Microsoft Graph powered app. The module explains in detail how you register your app in Azure AD and retrieve your application ID. Later on, you will add this ID into the MSAL (Microsoft Authentication Library)&amp;rsquo;s code of your app to link to your Azure Active directory.&lt;/p>
&lt;p>But before we do this in an exercise, we will learn some theoretical stuff that we need later on.&lt;/p>
&lt;h2 id="understand-microsoft-graph-permissions-and-consent">Understand Microsoft Graph permissions and consent&lt;/h2>
&lt;p>Crucial to understand that a user or admin needs to consent before the app requests permission to access Microsoft 365 data via Graph, which is why we need to know a little bit more about:&lt;/p>
&lt;h3 id="scopes">Scopes&lt;/h3>
&lt;p>All resources have specific scopes, like &lt;em>User.Read&lt;/em> (lets you read the profile of the signed-in user) or &lt;em>User.Read.All&lt;/em> lets you read the profiles of all users present in this directory. Of course, you will want only to allow scopes that are necessary for the application. You can look up scopes for each request in the &lt;a href="https://docs.microsoft.com/en-us/graph/api/overview?toc=.%2Fref%2Ftoc.json&amp;view=graph-rest-1.0">official documentation&lt;/a> and also learn about them while trying out requests in &lt;a href="https://aka.ms/ge">Graph Explorer&lt;/a>.&lt;/p>
&lt;h3 id="permission-types">Permission types&lt;/h3>
&lt;p>We can perform requests on behalf of a user (delegated permission), and we can run background processes like creating, reading, updating, or deleting events of all calendars without the requirement of a signed-in user. This means that an admin will need to pre-consent to these permissions.&lt;/p>
&lt;h3 id="access-tokens">Access tokens&lt;/h3>
&lt;p>The unit also describes how the magic with an access token works - and uses a great comparison for that! An access token is like a movie ticket - but your application gives it to Graph to show it has permission to access the requested data in Microsoft 365. LOVE this explanation so much!&lt;/p>
&lt;p>&lt;img alt="Graph Access Token" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphAccessTokenTicket.png">&lt;/p>
&lt;p>We use this movie ticket/access token in the Authorization header of our HTTP request.&lt;/p>
&lt;h2 id="register-an-application-with-azure-active-directory">Register an application with Azure Active Directory&lt;/h2>
&lt;p>In this unit, you learn which account type you can select when registering an app in AD and that web and single-page apps will require a redirect URI so that identity platform redirects and sends security tokens after authentication.&lt;/p>
&lt;p>In case you wondered: There is a big difference between authentication and authorization.&lt;/p>
&lt;p>&lt;img alt="Authentication and Authoruization" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFunAuth.png">&lt;/p>
&lt;h2 id="exercise---register-an-application-with-azure-active-directory">Exercise - Register an application with Azure Active Directory&lt;/h2>
&lt;p>This exercise walks us step by step through registering an app in Azure AD- I highly recommend following this unit if you never registered an application before:&lt;/p>
&lt;p>&lt;img alt="app registration" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/appreg.png">&lt;/p>
&lt;p>Let&amp;rsquo;s now&lt;/p>
&lt;h2 id="retrieve-an-access-token-using-msal">Retrieve an access token using MSAL&lt;/h2>
&lt;p>MSAL will make Token interaction more effortless for you because we can acquire tokens from the identity platform to authenticate users and access Microsoft Graph.&lt;/p>
&lt;p>&lt;img alt="authentication flow" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/auth.gif">&lt;/p>
&lt;p>Now that we understood the authentication flow, it&amp;rsquo;s time to get our hands dirty with&lt;/p>
&lt;h2 id="exercise---retrieve-an-access-token-using-msal">Exercise - Retrieve an access token using MSAL&lt;/h2>
&lt;p>To get this straight - you will clone a repository either using git or downloading a zip file. After opening this in Visual Studio Code (or any other editor), you will need to replace two placeholders with tenant ID and app ID from your Azure app registration.&lt;/p>
&lt;p>The unit walks you through some crucial parts of your app and lets you map this code to the authentication flow.&lt;/p>
&lt;p>&lt;img alt="your Graph App 🚀" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphApp.png">&lt;/p>
&lt;p>Congratz! - you made it!&lt;/p>
&lt;p>&lt;img alt="Graph Fundamentals - You did it" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFun-didit2.png">&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I loved this module - even if I already knew how to register applications and what Microsoft Graph does - it clarified the authentication flow once again and walked me nicely through some crucial parts of the code that I cloned from the MSLearn repository. Some basic understanding of JavaScript was beneficial to let the app run and know WHY and HOW it runs.&lt;/p></description></item><item><title>Microsoft Graph Fundamentals learning path - Module 1</title><link>https://m365princess.com/blogs/2021-04-01-microsoft-graph-fundamentals-learning-path-module-1/</link><pubDate>Thu, 01 Apr 2021 21:08:13 +0000</pubDate><guid>https://m365princess.com/blogs/2021-04-01-microsoft-graph-fundamentals-learning-path-module-1/</guid><description>&lt;h1 id="doing-microsoft-graph-fundamentals-learning-path-on-ms-learn---part-1">Doing Microsoft Graph Fundamentals learning path on MS Learn - Part 1&lt;/h1>
&lt;p>&lt;img alt="Microsoft Graph Fundamentals LearningPath" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFun.png">&lt;/p>
&lt;p>This blog post will summarize how I did the brand new &lt;a href="https://docs.microsoft.com/en-us/learn/paths/m365-msgraph-fundamentals/">Microsoft Graph Fundamentals Learning path&lt;/a>. Microsoft Graph Fundamentals consists of 3 modules:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>What is Microsoft Graph - lets you understand the Graph services and shows you how you can access user information from Graph using their learning playground called Graph Explorer. You will do a short exercise on that as well.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Configure a JavaScript application to retrieve Microsoft 365 data using Microsoft Graph - lets you understand how app registration works in Azure AD with permissions for Microsoft Graph powered apps and closes with two exercises using MSAL - making authentication easy.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Access user photo information with Microsoft Graph - in this module, you continue with the application you built-in module 2 and learn how to retrieve a user photo and do an exercise about it.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>The whole learning path is estimated to take us ~75 minutes. Let&amp;rsquo;s see how it goes :-)&lt;/p>
&lt;p>To be very honest: I worked with Graph before - see my blog posts here: &lt;a href="https://m365princess.com/category/microsoft-graph/">Microsoft Graph – M365 Princess&lt;/a> - but it&amp;rsquo;s the first time I do this guided learning on Microsoft Learn. I will cover module 1 in this post and continue with module 2 and 3 in the following posts.&lt;/p>
&lt;h2 id="what-is-microsoft-graph">What is Microsoft Graph&lt;/h2>
&lt;p>In super short: Microsoft Graph is a set of APIs that lets you access data in Microsoft 365 and use it for custom coded and low code applications. With this, Microsoft Graph is your key to data. Here are three tremendous advantages of it:&lt;/p>
&lt;ul>
&lt;li>across all Microsoft services, you can use one endpoint &lt;a href="https://graph.microsoft.com">https://graph.microsoft.com&lt;/a> - which makes development straightforward as you don&amp;rsquo;t need to learn all the different APIs for mail and calendar and files and so on&lt;/li>
&lt;li>documentation is fantastic, and there is a ton of learning material - like this learning path or the upcoming &lt;a href="https://learntogether-graph.splashthat.com/">Learn Together- Building apps with Microsoft Graph event &lt;/a>&lt;/li>
&lt;li>you can try out Graph in &lt;a href="https://aka.ms/ge">Graph Explorer&lt;/a> - if you like to read more about that, read my blog post on &lt;a href="https://m365princess.com/how-to-get-started-with-graph-explorer/">how to get started with Graph Explorer&lt;/a>&lt;/li>
&lt;li>Microsoft Graph Toolkit (you will learn more about it later) makes authentication (my personal kryptonite) easy. It also provides you with ready-to-use components and reduces the time you need to develop.&lt;/li>
&lt;/ul>
&lt;h3 id="intro">Intro&lt;/h3>
&lt;p>For this module, you will need to be a global admin in a Microsoft 365 tenant. The easiest way to have this is to &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/dev-program">join the Microsoft 365 developer program&lt;/a> and get a free E5 subscription. If you are not familiar with this, read &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/what-is-a-dev-tenant-and-why-would-you-want-one/ba-p/2036610">Julie Turner&amp;rsquo;s article about it&lt;/a>, at least some basic JavaScript understanding, and you should know what Azure Active Directory does. You will also need to have &lt;a href="https://nodejs.org/en/">Node.js&lt;/a> installed.&lt;/p>
&lt;p>The learning module introduces you to a business scenario so that it is easier for you to imagine which kind of applications we are talking about. In this scenario, we want to bring together messages from chat, emails, attended meetings, notes, key contacts, and relevant files.&lt;/p>
&lt;p>Our application could also grow later on and bring in data from more services like Windows 10 services or Enterprise Mobility and Security Services. We will not build this in total in this learning path, but we get a perspective, what we can develop based on our organization&amp;rsquo;s needs.&lt;/p>
&lt;h3 id="understand-microsoft-graph-services">Understand Microsoft Graph Services&lt;/h3>
&lt;p>At the very heart of Graph, we will find users and groups. In our application, we will need to access data from a single user&amp;rsquo;s personal scope (mail, messages, events) and a group scope (teamwork).&lt;/p>
&lt;p>The module introduces you to some Microsoft Graph API calls and shows you how the response will look. All API responses will be in JSON format - in case you want to learn more about it, read this article by Bob German on &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/introduction-to-json/ba-p/2049369">Introduction to JSON&lt;/a>&lt;/p>
&lt;p>The even more exciting part is that apart from making direct API calls, Microsoft provides us with the Graph SDK (Software Developer Kit). We can use the client Graph SDK client libraries to even more easily call the Graph API.&lt;/p>
&lt;h3 id="access-user-information-from-microsoft-graph-using-graph-explorer">Access user information from Microsoft Graph using Graph Explorer&lt;/h3>
&lt;p>This chapter introduces you to Graph Explorer -easily access it at &lt;a href="https://aka.ms/ge">aka.ms/ge&lt;/a>, try out some sample queries! My blog post on &lt;a href="https://m365princess.com/how-to-get-started-with-graph-explorer/">how to get started with Graph Explorer&lt;/a> explains that in detail.&lt;/p>
&lt;h3 id="exercise---access-user-information-from-microsoft-graph-using-graph-explorer">Exercise - Access user information from Microsoft Graph using Graph Explorer&lt;/h3>
&lt;p>Time to access your own data! I strongly recommend not playing in your production tenant- especially if you do not only want to read data with &lt;strong>GET&lt;/strong> requests but also want to write, update, or delete data with &lt;strong>POST, PATCH, UPDATE, or DELETE&lt;/strong> requests. Get yourself a Microsoft 365 developer tenant and use this.&lt;/p>
&lt;p>This chapter teaches you how to modify permissions in Graph explorer and how tips help you.&lt;/p>
&lt;p>You will learn how to send a message to Teams via Graph - this is not a test; it will really appear in Teams. 🚀&lt;/p>
&lt;p>Let us 1&amp;rsquo;up this already fantastic experience. Besides using this beautiful UI, Graph Explorer provides you with:&lt;/p>
&lt;ul>
&lt;li>Access tokens used for authentication (recognizing who a user is) and authorization (looking up the correct privileges)&lt;/li>
&lt;li>Code snippets for three different languages so that you can copy-paste them into your applications&lt;/li>
&lt;li>Microsoft Graph Toolkit components - most fabulous thing ever! If you are not familiar with MGT, &lt;a href="https://www.youtube.com/watch?v=TbAZHvB5NEk">go check it out &lt;/a>&lt;/li>
&lt;li>Adaptive cards snippets so you can quickly build UI components for your apps&lt;/li>
&lt;/ul>
&lt;p>You see, this is the &amp;lsquo;absolutely carefree package&amp;rsquo; provided by the Microsoft Graph team.&lt;/p>
&lt;h2 id="conclusion-on-module-1">Conclusion on Module 1:&lt;/h2>
&lt;p>Microsoft Graph is not only THE door opener to access all kinds of information and data across Microsoft 365 for developers and makers. They also provide us with this fantastic learning playground, aka Graph Explorer, in which we can try out, learn, explore and get snippets for all kinds of development scenarios.&lt;/p>
&lt;p>After introducing you to some basic concepts on Microsoft Graph, you have learned on a real-world example how to use Microsoft Graph before continuing to access data via Graph in a JavaScript application. This learning module is fantastic! If you never heard about Graph, you&amp;rsquo;ll get everything you need to start right away with it!&lt;/p>
&lt;p>&lt;img alt="Microsoft Graph Fundamentals - You did it" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphFun/GraphFun-youdidit1.png">&lt;/p></description></item><item><title>How to get started with Graph Explorer</title><link>https://m365princess.com/blogs/2021-03-26-how-to-get-started-with-graph-explorer/</link><pubDate>Fri, 26 Mar 2021 14:59:48 +0000</pubDate><guid>https://m365princess.com/blogs/2021-03-26-how-to-get-started-with-graph-explorer/</guid><description>&lt;h1 id="how-to-get-started-with-graph-explorer">How to get started with Graph Explorer&lt;/h1>
&lt;p>Don&amp;rsquo;t know what the Microsoft Graph Explorer is? Or have you already heard about it, but were not sure, how to get started and how this would help you? This post will show you what Graph Explorer is, the benefits, and how to use it.&lt;/p>
&lt;h2 id="what-is-microsoft-graph-explorer">What is Microsoft Graph Explorer&lt;/h2>
&lt;p>Microsoft Graph offers us a single endpoint &lt;a href="https://graph.microsoft.com">https://graph.microsoft.com&lt;/a> to access data in Microsoft 365, Windows 10, and Enterprise Mobility and Security and can be used by makers and developers. To get started to use the Graph API, the Graph Team offers us a very fantastic tool called &lt;strong>Graph Explorer&lt;/strong>.&lt;/p>
&lt;p>What does it do? Well, it lets us explore Graph! It&amp;rsquo;s a learning playground where we can try out requests, get responses, learn about permission scopes, and more. To access Graph Explorer, visit &lt;a href="https://aka.ms/ge">aka.ms/ge&lt;/a> and make yourself familiar with it:&lt;/p>
&lt;p>&lt;img alt="Overview of Graph Explorer" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/overview.png">&lt;/p>
&lt;h3 id="authentication">Authentication&lt;/h3>
&lt;p>You can decide if you want to sign in or if you want to try out with sample data provided by Microsoft. I recommend &amp;lsquo;playing&amp;rsquo; in your developer tenant; if you don&amp;rsquo;t have one, &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/what-is-a-dev-tenant-and-why-would-you-want-one/ba-p/2036610">learn here how to join the Microsoft 365 developer program and get a developer tenant&lt;/a>. The benefit of signing in with your (developer) account is, that you can execute all requests including POST and DELETE requests, which is not possible in the sample account.&lt;/p>
&lt;p>When you click the gear icon, you will find a shortcut to the Microsoft 365 developer program website (to get your sandbox with sample data), and you can change the theme as it suits your needs best. I like dark mode most 🖤.&lt;/p>
&lt;p>&lt;img alt="Microsoft Graph Gear" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/gear.png">&lt;/p>
&lt;h3 id="sample-queries">Sample queries&lt;/h3>
&lt;p>You will find sample queries below authentication - some may be disabled if you are not logged in. If you click on a sample, like I did in the screenshot below, Graph Explorer will send this HTTP request to Microsoft Graph - and get my joined teams. We can see this in the request area (upper part) and the response area (lower part):&lt;/p>
&lt;p>&lt;img alt="Get my joined Teams" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/teams-channel.png">&lt;/p>
&lt;p>We now want to create a channel called &amp;lsquo;Microsoft Graph&amp;rsquo; in the Team &amp;lsquo;Insidious Word Domination Plans&amp;rsquo;. We first copy the ID of the Team from the response of that request and then use this ID in the next sample we try out, which is:&lt;/p>
&lt;p>&lt;img alt="post a channel" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/post-teams-channel.png">&lt;/p>
&lt;p>We then replace the &lt;code>{teams-id}&lt;/code> placeholder with the copied ID value from the previous request and change the body to our needs:&lt;/p>
&lt;p>&lt;img alt="post a channel to my team" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/post-teams-channel-replace.png">&lt;/p>
&lt;p>In the &lt;strong>Modify permissions&lt;/strong> tab, we can learn about - and consent to permissions needed to execute this request:&lt;/p>
&lt;p>&lt;img alt="modify permissions" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/modify-permissions.png">&lt;/p>
&lt;p>But the awesomeness of this tool doesn&amp;rsquo;t stop here - we get ready-to-use code snippets in different languages to insert them into our applications:&lt;/p>
&lt;p>&lt;img alt="code snippet" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/code-snippet-js.png">&lt;/p>
&lt;p>And for some GET requests, we even get Adaptive Cards:&lt;/p>
&lt;p>&lt;img alt="Adaptive Card JSON" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/adaptivecards-json.png">&lt;/p>
&lt;p>We can also try out Microsoft Graph Toolkit components right here, although I would personally recommend doing this in the dedicated &lt;a href="https://mgt.dev">Microsoft Graph Toolkit Playground&lt;/a>. If you are unfamiliar with Microsoft Graph Toolkit, you can read how I started to use it - in my &lt;a href="https://m365princess.com/exploring-microsoft-graph-toolkit-lap-as-non-developer/">blog series about MGT&lt;/a> - I also recommend having a look at the beautiful &lt;a href="https://developer.microsoft.com/en-us/graph/components">Microsoft Graph component&lt;/a> browser.&lt;/p>
&lt;p>Last but not least: Documentation to every sample is nicely tied in - click on the pop-out icon next to the sample queries:
&lt;img alt="pop-out that links to docs" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/GraphExplorer/pop-out-docs.png">&lt;/p>
&lt;h2 id="how-does-graph-explorer-help-building-apps">How does Graph Explorer help building apps?&lt;/h2>
&lt;p>I use Microsoft Graph in Power Apps with custom connectors or in Power Automate and Azure Logic Apps with the HTTP action to execute actions that are not present (yet). If you never did that but want to learn about it, here are two blog posts that will get you started:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://m365princess.com/how-to-use-a-custom-connector-in-power-automate/">How to use a custom connector&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://m365princess.com/how-to-get-started-with-http-requests-in-power-automate/">How to get started with HTTP requests in Power Automate&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>To get from the rough idea to a working up, I follow this process:&lt;/p>
&lt;ol>
&lt;li>Read the docs. I mean, seriously. learn, which endpoint you will need to call, which permissions you will need.&lt;/li>
&lt;li>Try out in Graph Explorer; when it works, proceed to step 3, in case it doesn&amp;rsquo;t, go back to step 1 :&amp;rsquo;)&lt;/li>
&lt;li>Register your application in Azure AD with the permission scope that was needed for the request in Graph Explorer&lt;/li>
&lt;li>Try out the flow/the action of your custom connector in a basic sample flow/app.&lt;/li>
&lt;li>Now replace all hard-coded values like a Teams ID with Dynamic Content from within your flow&lt;/li>
&lt;/ol>
&lt;p>You see, Graph Explorer is an amazing tool to learn and try out - it gets you a step closer to a working solution, but you do not need to worry upfront about app registration, permissions etc. It&amp;rsquo;s a cool way to do a proof of concept - trying out if you can get, post, patch, update or delete the resources you like to before you start building your app.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>I am curious - which other tools help you developing with Microsoft Graph? Recently, &lt;a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-msgraph-autocomplete">Elio Struyf published a VSCode extension&lt;/a>, that auto-completes Graph URLs for you, read more about it &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/new-vscode-extension-for-autocompleting-your-microsoft-graph/ba-p/2231013">here&lt;/a>. Also, please share below what you build with Microsoft Graph? And how you use Graph Explorer? If you like to contribute to Graph Explorer, you can &lt;a href="https://github.com/microsoftgraph/microsoft-graph-explorer-v4">check it out on GitHub&lt;/a>. I am looking forward to your feedback!&lt;/p>
&lt;p>❤ Sharing is Caring&lt;/p></description></item><item><title>X things you should do before making an app</title><link>https://m365princess.com/blogs/2021-03-11-x-things-you-should-do-before-making-an-app/</link><pubDate>Thu, 11 Mar 2021 21:38:37 +0000</pubDate><guid>https://m365princess.com/blogs/2021-03-11-x-things-you-should-do-before-making-an-app/</guid><description>&lt;h1 id="10-things-we-should-think-about-before-we-build-an-app">10 things we should think about before we build an app&lt;/h1>
&lt;p>With Power Apps, we can rapidly build custom business applications that connect to our business data in a low code manner. This means that not only professional developers can develop applications but that a lot more people will be able to make apps that fit their specific use cases.&lt;/p>
&lt;p>But as development is not only writing code, there are certainly some things we should do before we hit &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a>. Many developers will agree that development is 20% about writing code and 80% about communications (meetings, gathering requirements, adjusting things).&lt;/p>
&lt;blockquote>
&lt;p>If we now introduce &amp;rsquo;low code development&amp;rsquo;, we work on these 20%, not on those 80%.&lt;/p>
&lt;/blockquote>
&lt;p>The issue with that is that the narrative of &amp;rsquo;everyone should make apps&amp;rsquo; doesn&amp;rsquo;t reflect that there is much content out there to teach business users how to use controls, components, connectors, and ask the community when in doubt which function they should use. But there is about near to zero guidance around making better decisions about how we should approach building apps.
Regardless of by whom it is developed, every app needs to be documented, maintained, and supported. Pro-developers are very aware of this, as DevOps is what they live and breathe every single day. But apart from IT departments, who will need to care about governance, application lifecycle management, etc., there are some things on the business user side that we should consider.&lt;/p>
&lt;p>This post will list ten things that we should think about before we start building our apps&lt;/p>
&lt;h2 id="1--which-problem-does-the-app-solve">1- which problem does the app solve?&lt;/h2>
&lt;p>This sounds obvious that one would first do a proper analysis of value proposition and if the app they have in mind is a must-have solution to a specific business case or if it&amp;rsquo;s more or less &amp;lsquo;yet another thing to use&amp;rsquo;, although there are working alternatives in place.&lt;/p>
&lt;h3 id="understand-your-market">understand your market&lt;/h3>
&lt;p>Let us take some time to segment our market and understand who (even if we build for our colleagues) needs our app. There will be roles that will rely on our app, and it will become mission-critical to them. Others will have already found applications that do similar things, or the app doesn&amp;rsquo;t solve a relevant problem. We can specifically look at underserved groups, such as blue-collar workers, and see if we can address their needs. Becoming obsessed with solving specific needs without assumptions is critical here. This means that we will need to validate the market that we have in mind. Even if we consider ourselves a business user/power user /citizen developer, doesn&amp;rsquo;t necessarily mean that we are our user and precisely know what they need.&lt;/p>
&lt;h3 id="psychological-strain-vs-energy-of-change">psychological strain vs. energy of change&lt;/h3>
&lt;p>To make users love an app, we will need to solve their pains or adress their needs and make it not too hard for them to change their behavior. If the energy they will need to change their behavior is higher than the psychological strain they face right now, they will not adopt your app. It&amp;rsquo;s unfortunately as simple as that.&lt;/p>
&lt;h2 id="2--calculate-value">2- calculate value&lt;/h2>
&lt;p>Now you have a fair idea of who will use your app for which use cases and also know what users are doing now:&lt;/p>
&lt;ul>
&lt;li>abuse other tools&lt;/li>
&lt;li>work on paper&lt;/li>
&lt;li>purchase 3rd party tools&lt;/li>
&lt;li>use unapproved shadow IT tools&lt;/li>
&lt;/ul>
&lt;p>and to which results this leads&lt;/p>
&lt;ul>
&lt;li>be busy with tasks that don&amp;rsquo;t add value&lt;/li>
&lt;li>lose information&lt;/li>
&lt;li>cause additional costs&lt;/li>
&lt;li>severe risks in terms of data security, governance, compliance etc.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>tl;dr: it costs time &amp;amp; money&lt;/p>
&lt;/blockquote>
&lt;p>But we need to make an effort to calculate the higher costs in terms of money and time and make an estimation for the next 12 or 24 months in order. This will also help with any approval process/ get funding.&lt;/p>
&lt;p>If the app we have in mind doesn&amp;rsquo;t create (enough) value, we can take this as a learning opportunity better to meet the needs of our (internal) customers.&lt;/p>
&lt;blockquote>
&lt;p>The goal is to provide more value, not to deliver a poorly designed app that costs a little less. Of course, we can build apps &amp;lsquo;for fun&amp;rsquo; or because we want to learn, or &amp;lsquo;just because we can&amp;rsquo;, but we should carefully distinguish those apps from apps that we want to pitch and &amp;lsquo;sell internally&amp;rsquo;.&lt;/p>
&lt;/blockquote>
&lt;h2 id="3--scope">3- scope&lt;/h2>
&lt;p>The mother of all questions: Does it scale? Will our app be something for our personal productivity? Ease a workload for a small team? Or do we talk about a mission-critical process? And even if we start with a small group of users to try out, is there a way where a broader audience could want to use it? We need to carefully identify our app&amp;rsquo;s scope, as this will impact many decisions.&lt;/p>
&lt;h2 id="4--data-model">4- data model&lt;/h2>
&lt;p>Let&amp;rsquo;s talk about our data model. Which data source will you need to get data, in which services will you write data? Which dependencies do you have, which other apps, flows, bots will be part of the solution? Of course, the consultant answer on when to use what will always be an &amp;lsquo;it depends&amp;rsquo;, but there are probably more reasons to look into different data sources as you are aware of:&lt;/p>
&lt;h3 id="licensing">licensing&lt;/h3>
&lt;p>As this is a pretty emotionally driven subject, we should handle this some more cool-headed. The idea of first understanding which needs your app solves, who would benefit from it, and how much money and time all users would save together by using it is the licensing discussion&amp;rsquo;s counterpart. Of course, organizations tend not to want to pay for additional licenses, but the idea that one could deliver excellent business value without any costs is somehow romanticized. Incorporate licensing fees for premium connectors (as you need them) in your calculation, and if the app still delivers more value than it costs, we will probably get approval/green lights for it.&lt;/p>
&lt;blockquote>
&lt;p>If the app isn&amp;rsquo;t worth more than ~10$ per month and user, we should probably not be building it.&lt;/p>
&lt;/blockquote>
&lt;h3 id="performance">performance&lt;/h3>
&lt;p>The data sources we choose have not only impacted the licensing model but also our apps&amp;rsquo; performance. For instance, if data needs to travel through an on-premises data gateway to a SQL server, most probably, our app will not be as fast as if the data sits in Dataverse. For an elaborated comparison on this and other data sources such as Excel, SharePoint, and more, please read this article about &lt;a href="https://powerapps.microsoft.com/en-us/blog/considerations-for-optimized-performance-in-power-apps/">Considerations for optimized performance in Power Apps&lt;/a>&lt;/p>
&lt;h3 id="developer-and-user-experience">developer and user experience&lt;/h3>
&lt;p>We can also impact the experience you as a developer will have while building the app, depending on the data source. If you ever &amp;rsquo;loved&amp;rsquo; to deal with, for example, lookup columns from a SharePoint list, you will agree that you didn&amp;rsquo;t choose the easiest way to build an app. If you need to find workarounds as a developer, this will impact your user, which means that their experience won&amp;rsquo;t be as good as possible.&lt;/p>
&lt;h2 id="5--delivery">5- delivery&lt;/h2>
&lt;p>Let&amp;rsquo;s say we are about to hit &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a>, and we have a fair idea of what to do there to make an app. We will publish our app, share it, and mentally move on to something different. Now, who cares for that app? Who will maintain our app? Things can easily change when we create apps on top of cloud services. This can quickly become a not to be underestimated workload, and probably we as a business user won&amp;rsquo;t have the capacity to cover that. And even if we are not talking about adjusting to things that changed: Who will support our app? Who will answer questions? Who will implement new features?&lt;/p>
&lt;p>Delivery of software should contain code (and yes, this applies to Power Apps) and proper documentation. Sharing knowledge about our app (what we use, inputs, outputs, dependencies, licensing, data model, accessibility, features, etc.) very early by writing it down to enable those who need to maintain and support our solution is essential. Making it a habit to have a changelog, where we document which features we add, remove or change, is crucial. If we think that this is too time-consuming, we should be aware that someone will need to spend more time on fixing the lack of documentation for us.&lt;/p>
&lt;blockquote>
&lt;p>A lack of documentation will create technical debt&lt;/p>
&lt;/blockquote>
&lt;h2 id="6--what-is-your-minimal-lvable-product">6- what is your minimal l❤vable product?&lt;/h2>
&lt;p>Yes, I mean it! Define your minimal lovable product. If you only heard about a minimal viable product so far, please read this article &lt;a href="https://medium.com/the-happy-startup-school/beyond-mvp-10-steps-to-make-your-product-minimum-loveable-51800164ae0c">How to Build a Minimum Loveable Produc&lt;/a>. In essence? Which features will we need to make users fall in love with our app? We will build this. We will not fall into the rabbit hole of delivering the whole software in one piece but focus on the crucial parts. Once we published our first version, we gather feedback on how we can improve it. Becoming comfortable with a mindset that software is never finished can be a good idea.&lt;/p>
&lt;h2 id="7--mock-up-your-app">7- mock-up your app&lt;/h2>
&lt;p>Finally- let&amp;rsquo;s talk frontend - how shall our app look like? It&amp;rsquo;s super tempting to start building screens and buttons and decide on colors etc., but we will get a better impression on the big picture of our app if we first do a mock-up. We may choose to use a professional app for that or if we will draw something (I personally do this in OneNote). Defining which screens and buttons and forms and galleries you will need will make the next steps easier.&lt;/p>
&lt;h2 id="8--componentize-your-app">8- componentize your app&lt;/h2>
&lt;p>Reinventing the wheel is always painful, and to avoid this, we can think about components in our apps so that we reuse what we (or even others in this environment in the component library) build before. While controls are an excellent way to start learning and understanding how everything works, components are the way to accelerate our process of making apps and make sure that we easily adjust them and don&amp;rsquo;t need to start over again for the next app we are making. It is also the low-code equivalent to the principle of &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">&amp;lsquo;Don&amp;rsquo;t repeat yourself&lt;/a>&amp;rsquo;. Thinking upfront about which controls we would repeatedly use and planning to componentize these will ensure that we don&amp;rsquo;t need to start all over again.&lt;/p>
&lt;p>You can watch &lt;a href="https://www.youtube.com/watch?v=RO6RMYauAlY">April Dunnam&amp;rsquo;s video about componetizing Power Apps&lt;/a> to get up to speed.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/RO6RMYauAlY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div>
&lt;h2 id="9--accessiblity">9- accessiblity&lt;/h2>
&lt;p>Accessibility is nothing that comes on top of a ready-to-publish app but should be one of your core concepts straight from the beginning. The accessibility checker in Power Apps is a good start, but it&amp;rsquo;s always worth exploring even more ideas. We can find lots of guidance on ensuring that more people can benefit from our apps, &lt;a href="https://www.microsoft.com/design/inclusive/">Microsoft Inclusive Design&lt;/a> is an excellent go-to resource.&lt;/p>
&lt;h2 id="10--build-in-microsoft-teams">10- build in Microsoft Teams&lt;/h2>
&lt;p>As we are more and more working in Microsoft Teams, we should not only build applications for teams, but for (Microsoft) Teams! Considering the context of apps gives you even more ways to satisfy user needs. Coming back to the scope of our app, we need to carefully think if this app is just for our team, or if we want to make it available for the whole organization. Staying in Teams also has an impact on licensing, as you can for instance use &lt;a href="https://docs.microsoft.com/en-us/power-platform/admin/about-teams-environment">Dataverse for Teams&lt;/a>, which is then seeded in you Microsoft 365 license.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>As we could see, there are quite some things to do and think about before we hit &lt;a href="https://make.powerapps.com">make.powerapps.com&lt;/a> and I put these together as an approach for good practices. What do you do before you actually start developing? What would you like to add to my list? Please comment below!&lt;/p></description></item><item><title>5 commands to try in CLI for Microsoft 365 to fall in love with it</title><link>https://m365princess.com/blogs/2021-03-11-5-commands-to-try-in-cli-for-microsoft-365-to-fall-in-love-with-it/</link><pubDate>Thu, 11 Mar 2021 19:48:08 +0000</pubDate><guid>https://m365princess.com/blogs/2021-03-11-5-commands-to-try-in-cli-for-microsoft-365-to-fall-in-love-with-it/</guid><description>&lt;h1 id="5-commands-to-try-in-cli-for-microsoft-365-to-fall-in-love-with-it">5 commands to try in CLI for Microsoft 365 to fall in love with it&lt;/h1>
&lt;p>&lt;img alt="Legogirl-and-car" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/5-commands-in-CLIMicrosoft365-to-fall-in-love-with/matt-hudson-bV7eTkTcVTY-unsplash.jpg">&lt;/p>
&lt;p>After I blogged about &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/how-to-send-adaptive-cards-with-cli-microsoft-365/ba-p/2143466">How to send Adaptive Cards with CLI for Microsoft 365&lt;/a> and also used &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/should-we-use-sharepoint-rest-or-microsoft-graph-api-in-power/ba-p/2182284">CLI to compare different ways to create SharePoint lists&lt;/a>, I found some more commands that made me fall in love with it. CLI for Microsoft 365 has three main benefits from my point of view:&lt;/p>
&lt;ul>
&lt;li>💻 it&amp;rsquo;s platform-agnostic and even works on &lt;a href="https://azure.microsoft.com/en-us/features/cloud-shell/?&amp;ef_id=Cj0KCQiAnKeCBhDPARIsAFDTLTIDlnMADqglDP6WLiQ_Yq23PQL7px3W9ElP7bBanGB6762ENh6DzScaAsTxEALw_wcB:G:s&amp;OCID=AID2100049_SEM_Cj0KCQiAnKeCBhDPARIsAFDTLTIDlnMADqglDP6WLiQ_Yq23PQL7px3W9ElP7bBanGB6762ENh6DzScaAsTxEALw_wcB:G:s">Azure Cloud Shell&lt;/a> so that every browser can be my admin machine&lt;/li>
&lt;li>💡 the syntax is easy and almost intuitive to use for me, although I only start to not use the UI for everything I want to manage in my Microsoft 365 tenant&lt;/li>
&lt;li>🚀 documentation is clear and concise with excellent code samples.&lt;/li>
&lt;/ul>
&lt;p>Bonus: 💖 Caring maintainers and awesome contributors&lt;/p>
&lt;p>In case you never used CLI for Microsoft 365 before, please first read &lt;a href="https://m365princess.com/how-to-get-started-with-cli-microsoft-365-and-adaptive-cards/#how-to-use-cli-microsoft-365">how to get started with CLI Microsoft 365&lt;/a> where I explain how to install the CLI.&lt;/p>
&lt;p>My screenshots will show that I work in PowerShell in Visual Studio Code, but you can use any other shell you like to use.&lt;/p>
&lt;h2 id="get-a-list-of-power-apps">Get a list of Power Apps&lt;/h2>
&lt;p>Wouldn&amp;rsquo;t it be nice to get a list of apps? This is what I thought as well. We will look into the &lt;a href="https://pnp.github.io/cli-microsoft365/cmd/pa/app/app-list/">CLI for Microsoft 365 documentation&lt;/a> find the command to list all Power Apps in this tenant, which gives us an idea, what makers are doing to be able to offer help and support as well.&lt;/p>
&lt;p>After having installed CLI and logged in:&lt;/p>
&lt;p>Run &lt;code>m365 pa app list&lt;/code>&lt;/p>
&lt;p>which will get you exactly that list - with internal names and display names:&lt;/p>
&lt;p>&lt;img alt="m365 pa app list" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/5-commands-in-CLIMicrosoft365-to-fall-in-love-with/list-pa.png">&lt;/p>
&lt;h2 id="get-an-overview-of-custom-connectors-in-an-environment">Get an overview of custom connectors in an environment&lt;/h2>
&lt;p>If we allowed makers to build their custom connectors to fulfill their unique needs, we might want to look at that as well. If you never create a custom connector, you can read my blog post about &lt;a href="https://m365princess.com/how-to-use-a-custom-connector-in-power-automate/">how to build a custom connector&lt;/a>.&lt;/p>
&lt;p>Run &lt;code>m365 pa environment get --name Default-&amp;lt;name of your default environment&amp;gt;&lt;/code>&lt;/p>
&lt;p>Now, where do we get this &lt;code>&amp;lt;name of your default environment&amp;gt;&lt;/code> from? This is your tenant ID, which you can obtain from the URL of any Power App running in this environment, or you can copy it from &lt;a href="https://portal.azure.com">portal.azure.com&lt;/a>, where you will find it in Azure Active Directory as &lt;strong>Tenant ID&lt;/strong>.&lt;/p>
&lt;p>&lt;img alt="obtain environment name in PA URL" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/5-commands-in-CLIMicrosoft365-to-fall-in-love-with/url-powerapps.png">&lt;/p>
&lt;ul>
&lt;li>Copy this Tenant ID&lt;/li>
&lt;li>Replace &lt;code>&amp;lt;name of your default environment&amp;gt;&lt;/code> with this Tenant ID&lt;/li>
&lt;li>Run the command&lt;/li>
&lt;/ul>
&lt;h2 id="get-a-list-of-users">Get a list of users&lt;/h2>
&lt;p>Obtaining a list of users on a specific SharePoint website will be helpful to get their IDs.&lt;/p>
&lt;p>Run &lt;code>m365 spo user list --webUrl &amp;quot;https://m365princess.sharepoint.com/sites/m365princess&amp;quot;&lt;/code>by replacing my webUrl with the tenant you are logged in and a site URL you want to query.&lt;/p>
&lt;p>The response will be something like this:&lt;/p>
&lt;p>&lt;img alt="list spo users" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/5-commands-in-CLIMicrosoft365-to-fall-in-love-with/list-spo-users.png">&lt;/p>
&lt;h2 id="get-a-list-of-external-users">Get a list of external users&lt;/h2>
&lt;p>Another cool starting point is to get a list of external users. As internal users tend to invite many guests, it could help have an overview of external users and see when these external users were created. You can also obtain the ID of users.&lt;/p>
&lt;p>&lt;code>m365 spo externaluser list&lt;/code>,&lt;/p>
&lt;p>&lt;img alt="get external users" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/5-commands-in-CLIMicrosoft365-to-fall-in-love-with/list-external.png">&lt;/p>
&lt;h2 id="remove-users">remove users&lt;/h2>
&lt;p>With&lt;/p>
&lt;p>&lt;code>m365 spo user remove --webUrl &amp;quot;https://contoso.sharepoint.com/sites/HR&amp;quot; --id 10 --confirm&lt;/code>&lt;/p>
&lt;p>we can remove users.&lt;/p>
&lt;h2 id="feedback--whats-next">Feedback &amp;amp; What&amp;rsquo;s next?&lt;/h2>
&lt;p>I hope that you now got an idea of how you can get started with CLI for Microsoft 365 :-) Shall I blog more about it and show even more commands? Still, I am curious, what would you use it for? Which are the commands that you use every single day? Please comment below!&lt;/p></description></item><item><title>How to create a (faux) table in Adaptive Cards with Power Automate</title><link>https://m365princess.com/blogs/2021-03-06-how-to-create-a-faux-table-in-adaptive-cards-with-power-automate/</link><pubDate>Sat, 06 Mar 2021 12:30:41 +0000</pubDate><guid>https://m365princess.com/blogs/2021-03-06-how-to-create-a-faux-table-in-adaptive-cards-with-power-automate/</guid><description>&lt;h1 id="how-to-create-a-table-in-adaptive-cards-with-power-automate">How to create a table in Adaptive Cards with Power Automate&lt;/h1>
&lt;p>In this blog post we learn how we can display a table in an Adaptive Card, pull data from a SharePoint list and use Power Automate to do that in one flow.&lt;/p>
&lt;p>When I read in the &lt;a href="https://docs.microsoft.com/en-us/adaptive-cards/authoring-cards/text-features">documentation&lt;/a>, that tables and headers are not supported, it was somehow a BUMMER 🙄, but then I asked the worlds laziest developer &lt;a href="https://twitter.com/bernierh">Hugo Bernier&lt;/a>, if there was really now way to do it.&lt;/p>
&lt;p>Our first idea was, to templatize an Adaptive Card, and then pass data into that template; but very unfortunately, this isn&amp;rsquo;t supported in Power Automate. Our second idea resolved the whole problem: We would build the JSON for our Adaptive Card like different LEGO bricks and then put them together.&lt;/p>
&lt;p>We would need&lt;/p>
&lt;ul>
&lt;li>1 brick (we will use variables in Power Automate) for the upper part of the Card, where we create a columnset,&lt;/li>
&lt;li>3 bricks for the headers of our faux table&lt;/li>
&lt;li>3 bricks for the rows over which we will loop&lt;/li>
&lt;li>1 brick that contains our &amp;lsquo;Open Link&amp;rsquo; button&lt;/li>
&lt;li>1 brick at the end of the card to close all open &lt;code>{&lt;/code> and &lt;code>[&lt;/code> with &lt;code>}&lt;/code> and &lt;code>]&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>To make things a bit more approachable, here is our little&lt;/p>
&lt;h2 id="use-case">use case&lt;/h2>
&lt;p>We want to display items of a SharePoint list in an Adaptive Card as a table. The result should look like this:&lt;/p>
&lt;p>&lt;img alt="Adaptive Card result" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/V2AdaptiveCard-Result.png">&lt;/p>
&lt;p>Purpose is to notify the Team each Monday about all Unicorns with a unicornibility index of less than 85 so that the team can take care of them. We do not only want to display 1 single item of our list with a factsheet but display as many items as match our query (unicornibility lt 85). We don’t want to hard-code any value in here to keep things flexible.&lt;/p>
&lt;h2 id="lets-start-building-our-power-automate-flow">Let’s start building our Power Automate flow&lt;/h2>
&lt;h3 id="recurrence-trigger">recurrence Trigger&lt;/h3>
&lt;p>We start with a &lt;strong>Recurrence&lt;/strong> trigger, set the &lt;strong>interval&lt;/strong> to &lt;code>1&lt;/code> and the &lt;strong>Frequency&lt;/strong> to &lt;code>Week&lt;/code>.&lt;/p>
&lt;p>&lt;img alt="recurrence trigger" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/recurrence.png">&lt;/p>
&lt;h3 id="get-items-sharepoint">get Items (SharePoint)&lt;/h3>
&lt;p>Next action is getting the items from our SharePoint list. Set &lt;strong>Filter Query&lt;/strong> to &lt;code>unicornibility lt 85&lt;/code> to only get those items, that match our query – this will ensure a better performance than first pulling all list items and then working with conditions later. You can also limit how many items you want to retrieve.&lt;/p>
&lt;p>&lt;img alt="get items" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/get-items.png">&lt;/p>
&lt;h3 id="preparations-to-bind-data-and-json-schema-of-the-adaptive-card">Preparations to bind data and JSON schema of the Adaptive Card&lt;/h3>
&lt;p>We want to get a table into an Adaptive Card, which is not supported natively, so we need to do a little trick. We will use variables to build the different pieces of the Adaptive Card JSON, that we will later need.
The Code in total would look like this:&lt;/p>
&lt;pre tabindex="0">&lt;code>{
&amp;#34;$schema&amp;#34;: &amp;#34;http://adaptivecards.io/schemas/adaptive-card.json&amp;#34;,
&amp;#34;type&amp;#34;: &amp;#34;AdaptiveCard&amp;#34;,
&amp;#34;version&amp;#34;: &amp;#34;1.2&amp;#34;,
&amp;#34;body&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;text&amp;#34;: &amp;#34;Hey Team- watch out! 🦄 need some extra 💖&amp;#34;,
&amp;#34;wrap&amp;#34;: true,
&amp;#34;weight&amp;#34;: &amp;#34;Bolder&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;ColumnSet&amp;#34;,
&amp;#34;columns&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;Column&amp;#34;,
&amp;#34;items&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;weight&amp;#34;: &amp;#34;Bolder&amp;#34;,
&amp;#34;text&amp;#34;: &amp;#34;Name&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;separator&amp;#34;: true,
&amp;#34;text&amp;#34;: &amp;#34;UNICORN1&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;separator&amp;#34;: true,
&amp;#34;text&amp;#34;: &amp;#34;UNICORN2&amp;#34;
}
],
&amp;#34;width&amp;#34;: &amp;#34;stretch&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;Column&amp;#34;,
&amp;#34;items&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;weight&amp;#34;: &amp;#34;Bolder&amp;#34;,
&amp;#34;text&amp;#34;: &amp;#34;Unicornibility&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;separator&amp;#34;: true,
&amp;#34;text&amp;#34;: &amp;#34;VALUE1&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;separator&amp;#34;: true,
&amp;#34;text&amp;#34;: &amp;#34;VALUE2&amp;#34;
}
],
&amp;#34;width&amp;#34;: &amp;#34;auto&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;Column&amp;#34;,
&amp;#34;items&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;weight&amp;#34;: &amp;#34;Bolder&amp;#34;,
&amp;#34;text&amp;#34;: &amp;#34;Party Readiness&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;separator&amp;#34;: true,
&amp;#34;text&amp;#34;: &amp;#34;PValue1&amp;#34;
},
{
&amp;#34;type&amp;#34;: &amp;#34;TextBlock&amp;#34;,
&amp;#34;separator&amp;#34;: true,
&amp;#34;text&amp;#34;: &amp;#34;PValue2&amp;#34;
}
],
&amp;#34;width&amp;#34;: &amp;#34;stretch&amp;#34;
}
]
},
{
&amp;#34;type&amp;#34;: &amp;#34;ActionSet&amp;#34;,
&amp;#34;actions&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;Action.OpenUrl&amp;#34;,
&amp;#34;title&amp;#34;: &amp;#34;open here and care 💖&amp;#34;
}
]
}
]
}
&lt;/code>&lt;/pre>&lt;p>Let’s break this into pieces:&lt;/p>
&lt;ol>
&lt;li>First variable will be the upper part of the Adaptive Card in which we define the schema, a title and create a column set.
&lt;img alt="initialize variable for card- upper part" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/varCard-initialize.png">&lt;/li>
&lt;li>We initialize variables for the 3 Headers “Name”, “Unicornibility” and “Party Readiness Index”&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;img alt="initialize var Column 1" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/varColumn1-initialize.png">&lt;/li>
&lt;li>&lt;img alt="initialize var Column 2" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/varColumn2-initialize.png">&lt;/li>
&lt;li>&lt;img alt="initialize var Column 3" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/varColumn3-initialize.png">&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>We create an &lt;strong>Apply to Each&lt;/strong> and loop over the values of our SharePoint list for each column by appending our variables.&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;img alt="initialize var Column 3" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/apply-to-each.png">&lt;/li>
&lt;/ul>
&lt;ol start="4">
&lt;li>We append the upper part of our card by the 3 columns (consisting of the headers and rows) and the actionset plus end of the card&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;img alt="append columns to card" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/append%20to%20Card.png">&lt;/li>
&lt;/ul>
&lt;p>In case you wonder why we needed to somehow unclean cut the JSON – this is a bug in Power Automate. Although we defined our variables as string, Power Automate asked us to provide valid JSON. We could not provide valid JSON though, because we needed to cut the JSON into pieces. We needed therefore to find a way to make Power Automate believe, that we are not storing JSON in a string variable, and apparently a &lt;code>{&lt;/code> at the beginning was a trigger for Power Automate to check if JSON was valid (which was not, but on purpose!).&lt;/p>
&lt;p>Our Code would look color coded like this:&lt;/p>
&lt;ul>
&lt;li>&lt;img alt="color-coded" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/V2color-coded.png">&lt;/li>
&lt;/ul>
&lt;p>And if we now lay the color-code blocks over the Adaptive Card:&lt;/p>
&lt;ul>
&lt;li>&lt;img alt="Adaptive Card color-coded" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/V2AdaptiveCard-result-color.png">&lt;/li>
&lt;/ul>
&lt;h3 id="send-adaptive-card">Send Adaptive Card&lt;/h3>
&lt;p>You may choose if you want to send the Post as the Flow bot or as a user or if you want to send this into a 1:1 chat or into a Channel. The Adaptive Card is our card variable.&lt;/p>
&lt;p>&lt;img alt="Adaptive Card" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-create-table-in-adaptive-cards/card.png">&lt;/p>
&lt;h2 id="conclusion-and-whats-next">Conclusion and what&amp;rsquo;s next&lt;/h2>
&lt;p>Although not natively supported, we can actually display a (faux) table in Adaptive Cards and bind this to a datasource. Potentially issues could occure here, as our columns are independent from each other. The Adaptive Cards renders columns, bnit not rows, which means that if we have different heights, it could be problematic to make them look good and even. What&amp;rsquo;s next? Find the limit how many rows we can display and what else we could do with Cards :&amp;rsquo;) What would you like to figure out? I am curious, please reply below!&lt;/p></description></item><item><title>Should we use SharePoint REST or Microsoft Graph API in Power Automate?</title><link>https://m365princess.com/blogs/2021-03-04-should-we-use-sharepoint-rest-or-microsoft-graph-api-in-power-automate/</link><pubDate>Thu, 04 Mar 2021 08:30:58 +0000</pubDate><guid>https://m365princess.com/blogs/2021-03-04-should-we-use-sharepoint-rest-or-microsoft-graph-api-in-power-automate/</guid><description>&lt;h1 id="should-we-use-sharepoint-rest-or-microsoft-graph-api-in-power-automate">Should we use SharePoint REST or Microsoft Graph API in Power Automate?&lt;/h1>
&lt;p>When working with Microsoft 365, we see many overlapping tools and features, and we will need (to provide) much guidance around &amp;lsquo;when to use what&amp;rsquo; for users. While most comparisons address users, I want to cover some more IT-related scenarios in this blog post. Specifically, I want to compare two different RESTful APIs, which we can use in Power Automate and Azure Logic Apps to send HTTP requests. If you are not familiar with that, don&amp;rsquo;t fret; continue to read my blog post about &lt;a href="https://m365princess.com/how-to-get-started-with-http-requests-in-power-automate/">how to get started with http requests in Power Automate&lt;/a>, I will grab a coffee ☕ in the meanwhile.&lt;/p>
&lt;p>Back again? Cool. Let me introduce you to our&lt;/p>
&lt;h2 id="use-case">use case&lt;/h2>
&lt;p>We want to create a new SharePoint list and add some columns based on the user&amp;rsquo;s input using Power Automate or Azure Logic Apps. When we look at the different available SharePoint actions in Power Automate, we will see that there is no &amp;lsquo;create a list&amp;rsquo; and no &amp;lsquo;add column to SharePoint list&amp;rsquo; action, but that we could try out something with &lt;a href="https://docs.microsoft.com/en-us/sharepoint/dev/business-apps/power-automate/guidance/working-with-send-sp-http-request">&amp;lsquo;send an HTTP request to SharePoint&amp;rsquo;&lt;/a>&lt;/p>
&lt;h3 id="option-no-1-sharepoint-rest">Option No. 1: SharePoint REST&lt;/h3>
&lt;p>The &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action uses SharePoint REST API. To create a list, we can look up &lt;a href="https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/working-with-lists-and-list-items-with-rest#working-with-lists-by-using-rest">working with lists and lists items&lt;/a> and see that we need to send a POST request to the &lt;code>https://{site_url}/_api/web/lists&lt;/code> endpoint and specify in the body of our list how it should look like. We can define the title and description of the list and also &lt;a href="https://techcommunity.microsoft.com/t5/sharepoint/near-complete-list-of-sharepoint-list-types-and-templates-a-k-a/m-p/220550">set the Basetemplate&lt;/a> (in case you want to do the same with a library etc.):&lt;/p>
&lt;pre>&lt;code>POST https://{site_url}/_api/web/lists
Authorization: &amp;quot;Bearer &amp;quot; + accessToken
Accept: &amp;quot;application/json;odata=verbose&amp;quot;
Content-Type: &amp;quot;application/json&amp;quot;
Content-Length: {length of request body as integer}
X-RequestDigest: &amp;quot;{form_digest_value}&amp;quot;
{
&amp;quot;__metadata&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;SP.List&amp;quot;
},
&amp;quot;AllowContentTypes&amp;quot;: true,
&amp;quot;BaseTemplate&amp;quot;: 100,
&amp;quot;ContentTypesEnabled&amp;quot;: true,
&amp;quot;Description&amp;quot;: &amp;quot;My list description&amp;quot;,
&amp;quot;Title&amp;quot;: &amp;quot;Test&amp;quot;
}
&lt;/code>&lt;/pre>
&lt;p>Now how do we do this in Power Automate without writing much code?&lt;/p>
&lt;h4 id="mobile-flow-button">mobile flow button&lt;/h4>
&lt;p>To make things easier, I will use the mobile flow trigger with three text inputs:&lt;/p>
&lt;p>&lt;img alt="mobile flow trigger with two text inputfields" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/mobileflowtrigger.png">&lt;/p>
&lt;p>You can also trigger the flow from a list, a form, an app, a bot, or whatever suits your use case.&lt;/p>
&lt;h4 id="send-an-http-request-to-sharepoint---create-a-list">Send an HTTP request to SharePoint - create a list&lt;/h4>
&lt;p>Now we need to add the &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action:&lt;/p>
&lt;ul>
&lt;li>Select the site of your choice from the dropdown menu&lt;/li>
&lt;li>Select method &lt;strong>Post&lt;/strong>&lt;/li>
&lt;li>enter &lt;code>_api/web/lists/&lt;/code> as URI&lt;/li>
&lt;li>enter Headers as follows:
&lt;ul>
&lt;li>&lt;code>Content-type&lt;/code> : &lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;li>&lt;code>accept&lt;/code>:&lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>enter&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code> &amp;#34;__metadata&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;SP.List&amp;#34;
},
&amp;#34;AllowContentTypes&amp;#34;: true,
&amp;#34;BaseTemplate&amp;#34;: 100,
&amp;#34;ContentTypesEnabled&amp;#34;: true,
&amp;#34;Description&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_1&amp;#39;]}&amp;#34;,
&amp;#34;Title&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text&amp;#39;]}&amp;#34;
}
&lt;/code>&lt;/pre>&lt;p>as body- make sure you replace the placeholder with Dynamic Content:&lt;/p>
&lt;p>&lt;img alt="send an http request to SharePoint" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/sendhttprequest.png">&lt;/p>
&lt;h4 id="parse-json">Parse JSON&lt;/h4>
&lt;p>Now we want to add a column. Let&amp;rsquo;s have a look &lt;a href="https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/working-with-lists-and-list-items-with-rest#working-with-lists-by-using-rest">into the documentation&lt;/a>, how we can do this.&lt;/p>
&lt;pre tabindex="0">&lt;code>POST https://{site_url}/_api/web/lists(guid&amp;#39;{list_guid}&amp;#39;)/Fields
Authorization: &amp;#34;Bearer &amp;#34; + accessToken
Accept: &amp;#34;application/json;odata=verbose&amp;#34;
Content-Type: &amp;#34;application/json&amp;#34;
Content-Length: {length of request body as integer}
X-RequestDigest: &amp;#34;{form_digest_value}&amp;#34;
{
&amp;#34;__metadata&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;SP.Field&amp;#34;
},
&amp;#34;Title&amp;#34;: &amp;#34;field title&amp;#34;,
&amp;#34;FieldTypeKind&amp;#34;: FieldType value,
&amp;#34;Required&amp;#34;: &amp;#34;true/false&amp;#34;,
&amp;#34;EnforceUniqueValues&amp;#34;: &amp;#34;true/false&amp;#34;,
&amp;#34;StaticName&amp;#34;: &amp;#34;field name&amp;#34;
}
&lt;/code>&lt;/pre>&lt;p>We need another &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action, and we need the &lt;strong>list Guid&lt;/strong>. To get the list Guid, we need to add a &lt;strong>Parse JSON&lt;/strong> action. If you are not familiar with that - I blogged about it: &lt;a href="https://m365princess.com/how-to-use-parse-json-action-in-power-automate/">How to use Parse JSON action in Power Automate&lt;/a>&lt;/p>
&lt;h4 id="parse-json-1">Parse JSON&lt;/h4>
&lt;ul>
&lt;li>Let your flow run - just the mobile trigger and the &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action&lt;/li>
&lt;li>Go to your flow run history&lt;/li>
&lt;li>Copy the outputs from the &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action&lt;/li>
&lt;li>add a &amp;lsquo;Parse JSON&amp;rsquo; action to your flow&lt;/li>
&lt;li>select &lt;code>body&lt;/code> from the &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo;action as &lt;strong>Content&lt;/strong>&lt;/li>
&lt;li>click &lt;strong>Generate from sample&lt;/strong>&lt;/li>
&lt;li>paste the copied JSON code in here&lt;/li>
&lt;li>click &lt;strong>Done&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>When we now have a look into our Dynamic Content, we will see many more options, also the list Guid, which is named &lt;strong>Id&lt;/strong> here.&lt;/p>
&lt;h4 id="send-an-http-request-to-sharepoint-2---add-a-column">Send an HTTP request to SharePoint 2 - add a column&lt;/h4>
&lt;p>Now we add another &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action, which will create us a column:&lt;/p>
&lt;ul>
&lt;li>Select the site of your choice from the dropdown menu&lt;/li>
&lt;li>Select method &lt;strong>Post&lt;/strong>&lt;/li>
&lt;li>enter &lt;code>_api/web/lists(guid'@{body('Parse_JSON')?['d']?['Id']}')/Fields&lt;/code>as URI (replace the placeholder with Dynamic content)&lt;/li>
&lt;li>enter Headers as follows:
&lt;ul>
&lt;li>&lt;code>Content-type&lt;/code> : &lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;li>&lt;code>accept&lt;/code>:&lt;code>application/json;odata=verbose&lt;/code>
enter in the Body:&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>{
&amp;#34;__metadata&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;SP.Field&amp;#34;
},
&amp;#34;Title&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_2&amp;#39;]}&amp;#34;,
&amp;#34;FieldTypeKind&amp;#34;: 2,
&amp;#34;Required&amp;#34;: &amp;#34;false&amp;#34;,
&amp;#34;EnforceUniqueValues&amp;#34;: &amp;#34;false&amp;#34;,
&amp;#34;StaticName&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_2&amp;#39;]}&amp;#34;
}
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Please replace again all placeholder by Dynamic content&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Send an HTTP request to SharePoint 2" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/sendhttprequest2.png">&lt;/p>
&lt;p>Should you stumble upon the FieldTypeKind, please find reference &lt;a href="https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-csom/ee540543(v=office.15)">here&lt;/a> - 2 means &amp;lsquo;single line of text&amp;rsquo;.&lt;/p>
&lt;p>If you want to run your flow, please think about changing the list name because you already created a list!&lt;/p>
&lt;p>If we now control our newly created SharePoint list, we will see that our new column doesn&amp;rsquo;t show up in the default view but that we need to enable the column- bummer!&lt;/p>
&lt;h4 id="send-an-http-request-to-sharepoint-3---add-column-to-view">Send an HTTP request to SharePoint 3 - add column to view&lt;/h4>
&lt;p>To have the column in the default view (or another view), we need to add another &amp;lsquo;send an HTTP request to SharePoint&amp;rsquo; action:&lt;/p>
&lt;ul>
&lt;li>Select the site of your choice from the dropdown menu&lt;/li>
&lt;li>Select method &lt;strong>Post&lt;/strong>&lt;/li>
&lt;li>enter &lt;code>_api/web/Lists/getByTitle('@{triggerBody()['text']}')/views/getByTitle('All Items')/ViewFields/addViewField('@{triggerBody()['text_2']}')&lt;/code>as URI&lt;/li>
&lt;li>(replace the placeholder with Dynamic content)&lt;/li>
&lt;li>enter Headers as follows:
&lt;ul>
&lt;li>&lt;code>Content-type&lt;/code> : &lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;li>&lt;code>accept&lt;/code>:&lt;code>application/json;odata=verbose&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Body is empty&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="send an HTTP request to SharePoint 3" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/senhttp3.png">&lt;/p>
&lt;h4 id="advantages-of-this-solution">Advantages of this solution&lt;/h4>
&lt;ul>
&lt;li>no need to register an application in Azure AD&lt;/li>
&lt;li>send an HTTP request to SharePoint is not a premium connector, which means that you won&amp;rsquo;t need a Power Automate Standalone license&lt;/li>
&lt;/ul>
&lt;h4 id="disadvantages-of-this-solution">Disadvantages of this solution:&lt;/h4>
&lt;ul>
&lt;li>with an &amp;lsquo;http request to SharePoint&amp;rsquo; action you have - compared to the power of Microsoft Graph API - limited options, as you can only send requests to SharePoint, but not to other services in Microsoft 365-&lt;/li>
&lt;li>to add the new column to our default view, we need 3 HTTP requests - which makes the flow unnecessarily more complex&lt;/li>
&lt;/ul>
&lt;h3 id="option-no-2-microsoft-graph-api">Option No. 2: Microsoft Graph API&lt;/h3>
&lt;p>Let&amp;rsquo;s see how we can create a SharePoint list or library and columns in it using Microsoft Graph. Microsoft Graph is a super powerful set of APIs that gives you a consistent experience for authentication, documentation, and samples. You can try it out on &lt;a href="https://developer.microsoft.com/en-us/graph/graph-explorer">Microsoft Graph Explorer&lt;/a>. For full documentation please continue &lt;a href="https://docs.microsoft.com/en-us/graph/overview">here&lt;/a>. If you are not familiar with using Microsoft Graph in Power Automate, &lt;a href="https://m365princess.com/how-to-get-started-with-http-requests-in-power-automate/">please continue to read here&lt;/a>&amp;hellip; time for another coffee for me then :-)&lt;/p>
&lt;h4 id="mobile-flow-trigger">mobile flow trigger&lt;/h4>
&lt;p>Again, to make things easy, we will use the same trigger as in Option No. 1.:&lt;/p>
&lt;p>&lt;img alt="mobile flow trigger" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/mobileflowtrigger.png">&lt;/p>
&lt;h4 id="http-action">HTTP action&lt;/h4>
&lt;p>Now that we registered our app in Azure AD, we can continue with the HTTP action in Power Automate.&lt;/p>
&lt;p>To create a list, we will look up &lt;a href="https://docs.microsoft.com/en-us/graph/api/list-create?view=graph-rest-1.0&amp;tabs=http">documentation here&lt;/a> and see that we will need to send a POST request to&lt;/p>
&lt;p>&lt;code>https://graph.microsoft.com/v1.0/sites/{site-id}/lists&lt;/code>&lt;/p>
&lt;p>And that we will need to add permissions to be able to call this API. Our HTTP request requires authentication, which can be done via Azure Active Directory OAuth, but we will first need to represent our app (yes, this flow that calls Microsoft Graph is an application) in Azure AD.&lt;/p>
&lt;p>We will follow these steps to register an app in Azure AD:&lt;/p>
&lt;ul>
&lt;li>Go to portal.azure.com and log in&lt;/li>
&lt;li>Click app registrations&lt;/li>
&lt;li>Click New App registration&lt;/li>
&lt;li>Give your app a nice name&lt;/li>
&lt;li>Save tenant ID and Client(app) ID somewhere (notepad or similar)&lt;/li>
&lt;li>Click &lt;strong>API PERMISSIONS&lt;/strong> and select Microsoft Graph&lt;/li>
&lt;/ul>
&lt;p>Now look up the permissions needed for this action: &lt;a href="https://docs.microsoft.com/en-us/graph/api/list-create?view=graph-rest-1.0&amp;tabs=http">Create a new list&lt;/a>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Permission type&lt;/th>
&lt;th style="text-align: center">Permissions (from least to most privileged)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">Application&lt;/td>
&lt;td style="text-align: center">Sites.ReadWrite.All&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>Select all these permissions&lt;/li>
&lt;li>Grant Admin consent&lt;/li>
&lt;li>Click &lt;strong>Certificates &amp;amp; secrets&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>New client secret&lt;/strong>&lt;/li>
&lt;li>Type in a description&lt;/li>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Copy the value and save it in your notepad (you will need that later)&lt;/li>
&lt;/ul>
&lt;h4 id="initialize-variables-for-tenant-id-app-id-and-app-secret">Initialize variables for Tenant ID, App ID and App Secret&lt;/h4>
&lt;p>Create three different string variables with the copied values of Tenant ID, App ID, and App Secret&lt;/p>
&lt;h4 id="http-action-to-create-a-list">HTTP action to create a list&lt;/h4>
&lt;p>Add an HTTP (not &amp;lsquo;send an HTTP request to SharePoint action) action to your flow and fill it out as follows:&lt;/p>
&lt;ul>
&lt;li>Method: Post&lt;/li>
&lt;li>URI: &lt;code>https://graph.microsoft.com/v1.0/sites/{site-id}/lists&lt;/code> - You can obtain the site-id from a request in Graph Explorer:
&lt;code>https://graph.microsoft.com/v1.0/sites?search=keyword&lt;/code>&lt;/li>
&lt;li>add &lt;strong>Content-Type&lt;/strong>: &lt;strong>application/json&lt;/strong> to the Headers&lt;/li>
&lt;li>enter as body:&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>{
&amp;#34;displayName&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text&amp;#39;]}&amp;#34;,
&amp;#34;columns&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_2&amp;#39;]}&amp;#34;,
&amp;#34;text&amp;#34;: {}
}
],
&amp;#34;list&amp;#34;: {
&amp;#34;template&amp;#34;: &amp;#34;genericList&amp;#34;
}
}
&lt;/code>&lt;/pre>&lt;p>Replace the placeholders by Dynamic Content
If you stumble off the &lt;code>genericList,&lt;/code> please &lt;a href="https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-server/ee541191(v=office.15)">read here for reference&lt;/a> about other list templates like libraries.&lt;/p>
&lt;p>If you need to add more columns, you can do that by&lt;/p>
&lt;pre tabindex="0">&lt;code>{
&amp;#34;displayName&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text&amp;#39;]}&amp;#34;,
&amp;#34;columns&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_2&amp;#39;]}&amp;#34;,
&amp;#34;text&amp;#34;: {}
},
{
&amp;#34;name&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_3&amp;#39;]}&amp;#34;,
&amp;#34;text&amp;#34;: {}
},
{
&amp;#34;name&amp;#34;: &amp;#34;@{triggerBody()[&amp;#39;text_4&amp;#39;]}&amp;#34;,
&amp;#34;text&amp;#34;: {}
}
],
&amp;#34;list&amp;#34;: {
&amp;#34;template&amp;#34;: &amp;#34;genericList&amp;#34;
}
}
&lt;/code>&lt;/pre>&lt;p>and so on. Let&amp;rsquo;s go ahead and&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Advanced Options&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Active Directory OAuth&lt;/strong>&lt;/li>
&lt;li>Enter &lt;code>https://login.microsoftonline.com&lt;/code> as Authority&lt;/li>
&lt;li>Enter the Tenant ID variable as Tenant&lt;/li>
&lt;li>Enter &lt;code>https://graph.microsoft.com&lt;/code> as Audience&lt;/li>
&lt;li>Enter the App ID variable as Client ID&lt;/li>
&lt;li>Select &lt;strong>Secret&lt;/strong> as Credential Type&lt;/li>
&lt;li>Enter the App Secret variable as Secret&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="variables and HTTP action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/varsandhttp.png">&lt;/p>
&lt;p>When we now run our flow, we will see that the columns that we created are already visible in the default view.&lt;/p>
&lt;h3 id="advantages-of-this-solution-1">Advantages of this solution:&lt;/h3>
&lt;ul>
&lt;li>We only need one HTTP request to Create a list, columns and have the columns in the default view&lt;/li>
&lt;li>If our flow gets more complex over time and we provision more things in not only SharePoint, we can do this with Microsoft Graph as well and extend our permission scope in Azure AD app registration&lt;/li>
&lt;/ul>
&lt;h3 id="disadvantages-of-this-solution-1">Disadvantages of this solution:&lt;/h3>
&lt;ul>
&lt;li>Because HTTP is a premium connector, we will need a Power Apps Standalone license&lt;/li>
&lt;li>We also need to register an app in Azure AD&lt;/li>
&lt;/ul>
&lt;h2 id="bonus-chapter-what-about-cli-microsoft-365">Bonus Chapter: What about CLI Microsoft 365?&lt;/h2>
&lt;p>If you read one of my previous blog posts about &lt;a href="https://m365princess.com/how-to-get-started-with-cli-microsoft-365-and-adaptive-cards/">How to get started with CLI Microsoft 365 and Adaptive Cards&lt;/a>, you could read between the lines that I found CLI Microsoft 365 pretty cool. Although I wanted to compare SharePoint REST and Microsoft Graph API using Power Automate in this Post, I felt it could be a cool idea to check how things would go in CLI Microsoft 365.&lt;/p>
&lt;p>If you are not familiar with it, please read my blog post first or head over to the &lt;a href="https://pnp.github.io/cli-microsoft365">full documentation&lt;/a>. After you installed CLI Microsoft 365, open a shell that makes you happy (I use PowerShell inside Visual Studio Code Terminal).&lt;/p>
&lt;h3 id="login">Login&lt;/h3>
&lt;ul>
&lt;li>Run &lt;code>m365 login&lt;/code>&lt;/li>
&lt;li>Copy the Login Code, click on the link&lt;/li>
&lt;li>Paste the Login Code&lt;/li>
&lt;li>Select the user you want to log in with from the list&lt;/li>
&lt;li>Return to your shell window&lt;/li>
&lt;/ul>
&lt;h3 id="create-a-list">Create a list&lt;/h3>
&lt;ul>
&lt;li>Run &lt;code>m365 spo list add --title Awesome%20List --baseTemplate DocumentLibrary --webUrl https://xxx.sharepoint.com/sites/yyy&lt;/code> by replacing &lt;code>xxx&lt;/code>by your tenantname and &lt;code>yyy&lt;/code> by your sitename&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="add a list in CLI Microsoft365" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/cli2.png">&lt;/p>
&lt;h3 id="add-fields-and-more">Add fields and more&lt;/h3>
&lt;p>You can now run even more commands to add fields, make them required, add them to default view, and so on, &lt;a href="https://pnp.github.io/cli-microsoft365/cmd/spo/field/field-add/">feel free to try it out&lt;/a>!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>As always, the answer to the question &amp;ldquo;When shall I use what&amp;rdquo; will be a typical consultant &amp;lsquo;It Depends.&amp;rsquo; Depending on your experience and skillset, the scope of your app, and how you approach it, you will prefer one tool over another - the purpose of this blog was to share some options to achieve the same thing - with creating a SharePoint list as an example. Please tell me - which solution would you prefer? Which are your use cases? Please reply below; I am curious!&lt;/p>
&lt;p>&lt;img alt="it depends" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/sharepointrest-or-graph/itdepends.png">&lt;/p></description></item><item><title>How to build a FAQ chatbot for Microsoft Teams with Power Virtual Agents</title><link>https://m365princess.com/blogs/2021-02-25-how-to-build-a-faq-chatbot-for-microsoft-teams-with-power-virtual-agents/</link><pubDate>Thu, 25 Feb 2021 19:39:25 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-25-how-to-build-a-faq-chatbot-for-microsoft-teams-with-power-virtual-agents/</guid><description>&lt;h1 id="how-to-build-an-faq-bot-for-microsoft-teams-with-power-virtual-agents-in-minutes">How to build an FAQ bot for Microsoft Teams with Power Virtual Agents in minutes&lt;/h1>
&lt;p>In this blog I want to show you, how you can build, test and publish an FAQ bot for Microsoft Teams within minutes. We will use the Power Virtual Agents for Teams, which means, that you will not need any additional license to your Microsoft 365 license, for reference see also &lt;a href="https://docs.microsoft.com/en-us/power-virtual-agents/requirements-licensing-subscriptions#power-virtual-agents-for-microsoft-teams-plan">Power Virtual Agents for Microsoft Teams plan&lt;/a>.&lt;/p>
&lt;h2 id="what-is-power-virtual-agents">What is Power Virtual Agents?&lt;/h2>
&lt;p>Power Virtual Agents belongs like Power Apps, &lt;a href="https://flow.microsoft.com">Power Automate&lt;/a> and Power Bi to the Power Platform (wow, that was a powerFULL sentence 😇). You can create chatbots, which can interact with users in apps and websites, trigger workflows and more, without the need of writing code. You can choose if you want to use it in the &lt;a href="https://powerva.microsoft.com">Power Virtual Agents standalone web app&lt;/a> or as &lt;a href="https://aka.ms/PVAForTeams">app within Microsoft Teams&lt;/a>.&lt;/p>
&lt;h2 id="lets-build-a-bot">Let&amp;rsquo;s build a bot&lt;/h2>
&lt;p>I will guide you how to create an FAQ bot. To feed our bot we will need some FAQ so that our can bot can learn them. I will use &lt;a href="https://docs.microsoft.com/en-us/power-platform/admin/powerapps-flow-licensing-faq">FAQ regarding licensing&lt;/a> 🤓, but you can choose any FAQ from a website or PDF or even Word file that you like.&lt;/p>
&lt;ul>
&lt;li>Open Teams&lt;/li>
&lt;li>Click on the &lt;strong>Apps&lt;/strong> icon&lt;/li>
&lt;li>Search for &lt;strong>Power Virtual Agents&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Power Virtual Agents" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/pva-teams.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Add Power Virtual Agents to Teams" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/add-pva.png">&lt;/p>
&lt;ul>
&lt;li>Select the Team you want your bot to join&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="select a team" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/pva-create.png">&lt;/p>
&lt;ul>
&lt;li>Give your bot a name ans select a language that your bot shall understand (should be the same language as your FAQ)&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="name your bot" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/pva-create2.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Chatbots&lt;/strong> - here you get an overview of ALL your chatbots&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="my Chatbots" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/my-chatsbots.png">&lt;/p>
&lt;h3 id="add-topics-from-any-website">Add topics from any website&lt;/h3>
&lt;ul>
&lt;li>Click &lt;strong>Topics&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Topics" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/topics.png">&lt;/p>
&lt;p>You see, that some basic topics are already created for you. You can take a look later.&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Suggested&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="suggested topics" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/suggested.png">&lt;/p>
&lt;p>Now we want to work on feeding our bot with the FAQ from the website that we selected.&lt;/p>
&lt;ul>
&lt;li>Copy the URL of the FAQ website&lt;/li>
&lt;li>Paste the URL into the &lt;strong>Link to online content&lt;/strong> field&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Get Faq" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/getfaq.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Start&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>This may take now a couple of minutes. Grab a coffee in the meanwhile: ☕. Soon you will see the message that your new suggested topics are now in:&lt;/p>
&lt;p>&lt;img alt="Success" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/success.png">&lt;/p>
&lt;h3 id="review--edit-topics">Review &amp;amp; edit topics&lt;/h3>
&lt;p>You can now review and edit each topic:&lt;/p>
&lt;p>&lt;img alt="Review and edit" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/edit-topics.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Save Topic&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>After you are done with reviewing and editing your topics, you will need to turn on the topics&lt;/p>
&lt;ul>
&lt;li>Switch the toggle to &lt;strong>on&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Turn on topics" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/turn-on.png">&lt;/p>
&lt;blockquote>
&lt;p>Train your bot by entering more trigger phrases. This way, it is more likely that the Chatbot understands users asking questions even if they don&amp;rsquo;t exactly match the trigger phrases.&lt;/p>
&lt;/blockquote>
&lt;p>Time to test the bot!&lt;/p>
&lt;p>&lt;img alt="Test bot" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/end-connversation.png">&lt;/p>
&lt;p>You can now review and edit your topics until you are happy with the results.&lt;/p>
&lt;h3 id="publish-your-bot-to-microsoft-teams">Publish your Bot to Microsoft Teams&lt;/h3>
&lt;ul>
&lt;li>Click the &lt;strong>Publish&lt;/strong> icon&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Publish" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/publish.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Add Bot" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/add.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Add to Teams&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Success" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/publish-bot.png">&lt;/p>
&lt;ul>
&lt;li>Use your bot&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Chat" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-build-a-faq-bot/chat.png">&lt;/p>
&lt;h2 id="conclusion--whats-next">Conclusion &amp;amp; what&amp;rsquo;s next&lt;/h2>
&lt;p>It took us only a few minutes to create, test and publish a chatbot, that now works inside of Microsoft Teams. Want to do some more? We could extend the capabilities of our Power Virtual Agents bot: Let&amp;rsquo;s say our bot can&amp;rsquo;t answer a question and needs to transfer the chat to a human agent, who will answer that question. What if we trained the bot with that answer so that our bot gets smarter over time? I will cover that in one of my next blog posts. What do you use chatbots for? Did you already try to make a 5 minute bot? Please share below :)&lt;/p></description></item><item><title>How to use a custom connector in Power Automate</title><link>https://m365princess.com/blogs/2021-02-23-how-to-use-a-custom-connector-in-power-automate/</link><pubDate>Tue, 23 Feb 2021 21:08:19 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-23-how-to-use-a-custom-connector-in-power-automate/</guid><description>&lt;h1 id="how-to-create-a-custom-connector-in-power-automate">How to create a custom connector in Power Automate&lt;/h1>
&lt;p>Power Automate is a super cool tool, which gives us a lot of options. But sometimes, the built-in connectors, are not enough. In one of previous posts, I showed you &lt;a href="https://m365princess.com/how-to-get-started-with-http-requests-in-power-automate/">how to send HTTP requests to Microsoft Graph API&lt;/a>. This time, I will show you how to connect to APIs outside of Microsoft 365 in Power Automate and even use an IOT button to trigger your flow.&lt;/p>
&lt;h2 id="use-case">Use case&lt;/h2>
&lt;p>To make things more approachable, here is a little use case for you:&lt;/p>
&lt;blockquote>
&lt;p>I want to click an IOT button and this shall trigger a flow which tweets about the music I currently listen to on Spotify.&lt;/p>
&lt;/blockquote>
&lt;p>The result will look like this:&lt;/p>
&lt;p>&lt;img alt="tweet about spotify" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/tweet.png">&lt;/p>
&lt;h2 id="what-we-need">What we need&lt;/h2>
&lt;p>To achieve this, we will need a couple of things:&lt;/p>
&lt;ol>
&lt;li>an IOT button - I use a Flic Smart button for that- that triggers my flow&lt;/li>
&lt;li>a flow that connects to our Spotify and to twitter&lt;/li>
&lt;/ol>
&lt;p>So lets have a look at&lt;/p>
&lt;h3 id="iot-button">IOT button&lt;/h3>
&lt;p>I use a &lt;a href="https://flic.io/">Flic IOT button&lt;/a> to trigger my flow. This button works with bluetooth, which means that we will need a bluetooth enabled device to work with this button- either a smartphone or an IOT Hub.&lt;/p>
&lt;h4 id="set-up-your-iot-button">Set up your IOT button&lt;/h4>
&lt;ul>
&lt;li>download the app from your app store&lt;/li>
&lt;li>install the app&lt;/li>
&lt;li>register a new account&lt;/li>
&lt;li>connect your folic button by pressing it for ~10 seconds&lt;/li>
&lt;/ul>
&lt;p>If you like to, rename this button - please keep in mind, that one button can be used to trigger several flows, as we have three different event types: Click, Double-Click and Hold.&lt;/p>
&lt;h3 id="spotify">Spotify&lt;/h3>
&lt;p>In this flow we want to trigger by one or any event of the flic button and then tweet the song we are currently listening to on Spotify. Turns out, that there is no connector for Spotify, so why not building our own custom connector?&lt;/p>
&lt;blockquote>
&lt;p>To be able to build custom actions, you will need an API for this service. Lucky us, that Spotify provides us with that API so that we can use this to build our custom connector.&lt;/p>
&lt;/blockquote>
&lt;p>Of course we need to have at least a free Spotify account so that we can listen to music that then shall be tweeted about.&lt;/p>
&lt;p>Before we can build the connector, we will need to register for &lt;a href="https://developer.spotify.com/">Spotify&amp;rsquo;s Developer program&lt;/a> - Once this is done, we can retrieve Spotify content such as album data, playlists and more though Spotify Web API. To get user-related data (like the song our user is playing right now) we need to authorize our application so that we are allowed to retrieve this information.&lt;/p>
&lt;h4 id="register-our-application-on-spotify">Register our application on Spotify&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>Log into your brand new Spotify for Developers account&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Go to your &lt;a href="https://developer.spotify.com/dashboard/applications">Dashbaord&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Click &lt;strong>Create an App&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Give your app a name and accept T&amp;amp;C&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Create a new app" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/create-an-app.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Create&lt;/strong>&lt;/li>
&lt;li>Copy the &lt;code>Client ID&lt;/code> and the &lt;code>Client Secret&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="build-the-custom-connector">Build the custom Connector&lt;/h4>
&lt;ul>
&lt;li>Go to &lt;a href="https://flow.microsoft.com">flow.microsoft.com&lt;/a>&lt;/li>
&lt;li>Click &lt;strong>Data&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Custom connectors&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>New Custom connector&lt;/strong>, &lt;strong>Create from blank&lt;/strong>&lt;/li>
&lt;li>Add a name for your connector&lt;/li>
&lt;li>Click &lt;strong>Continue&lt;/strong>&lt;/li>
&lt;li>If you like to, you can upload a connector icon, this step is optional&lt;/li>
&lt;li>enter &lt;code>api.spotify.com&lt;/code> as &lt;strong>Host&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Custom Connector - general" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/cc-general.png">&lt;/p>
&lt;p>You can find the values you need to fill in here in the &lt;a href="https://developer.spotify.com/documentation/web-api/reference/#reference-index">Spotify for developers documentation&lt;/a>, but to make things easier for you, I will provide them for you.&lt;/p>
&lt;ul>
&lt;li>Click on &lt;strong>Security&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>OAuth2.0&lt;/strong>&lt;/li>
&lt;li>Select &lt;strong>Generic Oath 2&lt;/strong> as Identity Provider&lt;/li>
&lt;li>Paste in your Client ID and Client secret&lt;/li>
&lt;li>enter &lt;code>https://accounts.spotify.com/authorize&lt;/code> as Authorization URL&lt;/li>
&lt;li>enter &lt;code>https://accounts.spotify.com/api/token&lt;/code> as Token URL and Refresh URL&lt;/li>
&lt;li>enter &lt;code>user-read-currently-playing&lt;/code> as scope&lt;/li>
&lt;li>Click **Create connector&lt;/li>
&lt;li>Copy the Redirect URL&lt;/li>
&lt;li>go to your &lt;a href="https://developer.spotify.com/dashboard/applications">Spotify app&lt;/a>&lt;/li>
&lt;li>Click &lt;strong>Edit settings&lt;/strong>&lt;/li>
&lt;li>past the Redirect URI into the field for Redirect URIs&lt;/li>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="edit settings" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/edit%20settings.png">&lt;/p>
&lt;p>Now go back to your Custom connector&lt;/p>
&lt;ul>
&lt;li>Click on &lt;strong>Definition&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>New action&lt;/strong>&lt;/li>
&lt;li>enter something like &lt;code>GetSong&lt;/code> in Summary&lt;/li>
&lt;li>enter a description&lt;/li>
&lt;li>enter an operation ID like &lt;code>getssong&lt;/code> - please note, that this ID shouldn&amp;rsquo;t start with an upper case letter&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Custom Connector definition" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/cc-definition-general.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>import from sample&lt;/strong>&lt;/li>
&lt;li>Select verb &lt;strong>Get&lt;/strong>&lt;/li>
&lt;li>paste in &lt;code>https://api.spotify.com/v1/me/player/currently-playing&lt;/code> as URL&lt;/li>
&lt;/ul>
&lt;p>(For reference: &lt;a href="https://developer.spotify.com/console/get-users-currently-playing-track/">https://developer.spotify.com/console/get-users-currently-playing-track/&lt;/a>)&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Import&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Update connector&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Test&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>To test our new connector, we need to select from an existing connection or create a new connection.&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>New connection&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>A new pop up window will appear and promt us to &lt;strong>Agree&lt;/strong> - you as a user authorize your Spotify app to retrieve data related to your user account - such as the song currently playing.&lt;/p>
&lt;p>&lt;img alt="custom connector authorization" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/cc-authorization.png">&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>Agree&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Close&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="use-the-custom-connector-in-our-flow">Use the custom connector in our flow&lt;/h3>
&lt;p>Now it&amp;rsquo;s time to build our flow&lt;/p>
&lt;h4 id="trigger-flic">Trigger flic&lt;/h4>
&lt;p>As already said, we want the flic button to be our trigger&lt;/p>
&lt;p>&lt;img alt="Power Automate flow with flic as a trigger" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/flic.png">&lt;/p>
&lt;p>You can choose, if you want this flow to be triggered by any event type or if you want to save the two other event types for other flows.&lt;/p>
&lt;p>Now we want to get the current song from our shiny new Custom connector:&lt;/p>
&lt;h3 id="get-current-song">Get current song&lt;/h3>
&lt;ul>
&lt;li>Click on &lt;strong>Insert a new Step&lt;/strong>&lt;/li>
&lt;li>Click on &lt;strong>Custom&lt;/strong>&lt;/li>
&lt;li>Select the new custom connector for Spotify&lt;/li>
&lt;/ul>
&lt;p>Our intention now is to tweet something like &amp;ldquo;I am currently listenintg to {songname} by {artistname}, check it out {spotify URL}.&amp;rdquo; But from our custom connector, we don&amp;rsquo;t get the name of song and artist per se, we will need to first parse the JSON output. If you never heard of that before, don&amp;rsquo;t worry, go read this article about &lt;a href="https://m365princess.com/how-to-get-started-with-http-requests-in-power-automate/">how to parse JSON in Power Automate&lt;/a>, I will just wait here for you and drink a coffee.&lt;/p>
&lt;p>Back again? Cool! ☕&lt;/p>
&lt;ul>
&lt;li>Let your flow run&lt;/li>
&lt;li>Go to your run history&lt;/li>
&lt;li>Copy the output of the &lt;strong>Get current song&lt;/strong> action&lt;/li>
&lt;li>Insert a &lt;strong>Parse JSON&lt;/strong> action&lt;/li>
&lt;li>Click &lt;strong>Generate from sample&lt;/strong>&lt;/li>
&lt;li>Paste into the new field&lt;/li>
&lt;li>Click &lt;strong>done&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Magic 🦄 - Now we can see all the output from our custom action as Dynamic content. Next thing up is to send the tweet. We can use the twitter connector for it, but Buffer works fine as well.&lt;/p>
&lt;ul>
&lt;li>Add the &lt;strong>post a tweet&lt;/strong> action&lt;/li>
&lt;/ul>
&lt;p>provide your tweet text with Dynamic content as you wish from your Parse JSON action. Don&amp;rsquo;t be afraid when the flow adds &lt;strong>Apply to each&lt;/strong> loops! Unfortunatley, both artist-name and -song-name are named &lt;code>name&lt;/code>, so you will need to figure out which is which.&lt;/p>
&lt;ul>
&lt;li>save your flow&lt;/li>
&lt;/ul>
&lt;h2 id="run-your-flow">Run your flow&lt;/h2>
&lt;p>Open your flic app and select the new button, set the action that is triggered by the &lt;code>click&lt;/code> event to &lt;strong>Microsoft Flow&lt;/strong> (watch out, this is the old name of Power Automate, which is not refelcted in the Flic app). When you now click the button, this will trigger our flow, that listens to the &lt;strong>Click&lt;/strong> event of that button, get the current song and tweet about it!&lt;/p>
&lt;p>&lt;img alt="post tweet in Power Automate" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-use-custom-connectors-in-powerautomate/post-tweet.png">&lt;/p>
&lt;h2 id="conclusion-and-whats-next">Conclusion and what&amp;rsquo;s next&lt;/h2>
&lt;p>In this post I explained, how you can create a custom connector and call an API outside of Microsoft 365. You learned how to define actions and how to authorize your application so that you can retrieve the requested data. Which use cases do you have in mind? What would you like to build a custom connector for? Please share!&lt;/p></description></item><item><title>How to get started with CLI Microsoft 365 and Adaptive Cards</title><link>https://m365princess.com/blogs/2021-02-17-how-to-get-started-with-cli-microsoft-365-and-adaptive-cards/</link><pubDate>Wed, 17 Feb 2021 16:13:39 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-17-how-to-get-started-with-cli-microsoft-365-and-adaptive-cards/</guid><description>&lt;h1 id="how-to-send-adaptive-cards-with-cli-microsoft-365">How to send Adaptive Cards with CLI Microsoft 365&lt;/h1>
&lt;p>In this blog post I want to explain how you can send an Adaptive Card with CLI Microsoft 365. I will guide you from zero to hero 🚀, so even if you don&amp;rsquo;t know anything about the CLI Microsoft 365 or about Adaptive cards, don&amp;rsquo;t stop reading, you will be able to do that in a few minutes- I promise!&lt;/p>
&lt;h2 id="what-is-cli-microsoft-365">What is CLI Microsoft 365?&lt;/h2>
&lt;p>Lets first get us all on the same page and clarify, what CLI Microsoft 365 is and why we should use it: It is a CLI (Command Line Interface) which let&amp;rsquo;s us manage Microsoft 365 from any kind of OS: It doesn&amp;rsquo;t matter if we work on Windows, MacOS or Linux. Previously, some configurations were only possible by PowerShell for Windows, which limited a lot of admins. But even if you work on Windows and are pretty fine with using PowerShell, CLI might be a nice alternative to try out.&lt;/p>
&lt;h3 id="how-to-use-cli-microsoft-365">How to use CLI Microsoft 365&lt;/h3>
&lt;p>I will now describe your first steps:&lt;/p>
&lt;h4 id="install-nodejs">Install Node.js&lt;/h4>
&lt;p>To use the CLI MIcrosoft 365, you will need to install Node.js - Please follow these steps:&lt;/p>
&lt;ul>
&lt;li>go to &lt;a href="https://nodejs.org/en/">https://nodejs.org/en/&lt;/a>&lt;/li>
&lt;li>download the LTS version of Node.js&lt;/li>
&lt;li>install Node.js&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>If in doubt, if you have the correct version installed or if you need to use different versions of Node.js, pleas read &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/use-node-version-manager-to-develop-your-spfx-apps/ba-p/2128393">Use Node Version Manager to develop your SPFx apps&lt;/a> by &lt;a href="https://twitter.com/atwork">Toni Pohl&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h4 id="install-cli-microsoft-365">Install CLI Microsoft 365&lt;/h4>
&lt;p>Now that you have Node.js installed, we can continue with installing the CLI Microsoft 365. You can choose any shell you like: If you want to, you can use PowerShell or PowerShell in the Terminal in Visual Studio Code or whatever makes you happy. I like to use the PowerShell terminal in Visual Studio Code, because links render to be clickable and this saves me copy/pasting links into a new browser tab. But if you like to any other shell, that is perfectly fine as well. To install CLI Microsoft 365, run the command:&lt;/p>
&lt;p>&lt;code>npm i -g @pnp/cli-microsoft365&lt;/code>&lt;/p>
&lt;p>In case you wonder:&lt;/p>
&lt;ul>
&lt;li>the &lt;code>i&lt;/code> is an alias for &lt;code>install&lt;/code>&lt;/li>
&lt;li>the &lt;code>-g&lt;/code> means that we want to install this package globally&lt;/li>
&lt;li>it will take about 2 minutes&lt;/li>
&lt;/ul>
&lt;h4 id="login">Login&lt;/h4>
&lt;p>Now that we installed CLI Microsoft 365, it&amp;rsquo;s time to actually do something here. But before we can get or post anything, we will need to log into our tenant. Pro Tip: You can use your your Developer tenant for this. If you don&amp;rsquo;t know, what this is, please go read &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/what-is-a-dev-tenant-and-why-would-you-want-one/ba-p/2036610">What is a “Dev Tenant” and why would you want one?&lt;/a> by &lt;a href="https://twitter.com/jfj1997">Julie Turner&lt;/a>. Run the following command:&lt;/p>
&lt;p>&lt;code>m365 login&lt;/code>&lt;/p>
&lt;p>In response you will be asked to open a webbrowser and login with a code. If you are using Visual Studio Code, you can click on the link, please copy the code upfront.&lt;/p>
&lt;p>&lt;img alt="Login" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-send-adaptivecards-with-CLIMicrosoft365/login.png">&lt;/p>
&lt;p>After you pasted the code,&lt;/p>
&lt;ul>
&lt;li>click &lt;strong>Next&lt;/strong>&lt;/li>
&lt;li>pick an account out of the list of accounts&lt;/li>
&lt;/ul>
&lt;p>You will be seeing this message and can close this browser tab- we won&amp;rsquo;t need it anymore.
&lt;img alt="you are logged in" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-send-adaptivecards-with-CLIMicrosoft365/PnPmanagementShellOK.png">&lt;/p>
&lt;p>Yay! You successfully logged in! You don&amp;rsquo;t believe that? Let&amp;rsquo;s check with CLI Microsoft 365 and run this command to get your status:&lt;/p>
&lt;p>&lt;code>m365 status&lt;/code>&lt;/p>
&lt;p>and it will get your status for you:&lt;/p>
&lt;p>&lt;img alt="status logged in" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-send-adaptivecards-with-CLIMicrosoft365/status.png">&lt;/p>
&lt;p>If you want some inspiration, what you could do now, run this command:&lt;/p>
&lt;p>&lt;code>m365 help&lt;/code>&lt;/p>
&lt;p>&lt;img alt="help" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-send-adaptivecards-with-CLIMicrosoft365/help.png">
which gives you a list of things you can try, these are called command groups, as each of them can contain several commands. When you now choose one of the command groups and run for example this one:&lt;/p>
&lt;p>&lt;code>m365 outlook&lt;/code>&lt;/p>
&lt;p>you will get the commands, that are available to you in this command group. Sometimes, you can go several levels deep-Just try them out!&lt;/p>
&lt;h2 id="what-are-adaptive-cards">What are Adaptive Cards?&lt;/h2>
&lt;p>Next thing we need to know is, what are Adaptive Cards and why would we want to use them? When we look at &lt;a href="https://adaptivecards.io">adaptivecards.io&lt;/a> we can read the definition &amp;lsquo;Adaptive Cards are platform-agnostic snippets of UI, authored in JSON, that apps and services can openly exchange. When delivered to a specific app, the JSON is transformed into native UI that automatically adapts to its surroundings. It helps design and integrate light-weight UI for all major platforms and frameworks.&amp;rsquo; This means, that we get a nice method to share and display information without needing to know how to use HTML/CSS to render them correctly in different host applications. If you like to know more, please read &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/get-started-with-adaptive-cards/ba-p/2048786">Get started with Adaptive Cards&lt;/a> by &lt;a href="https://twitter.com/tomaszposzytek">Tomasz Poszytek&lt;/a>&lt;/p>
&lt;h2 id="how-do-we-send-an-adaptive-card-with-cli-microsoft-365">How do we send an Adaptive Card with CLI Microsoft 365?&lt;/h2>
&lt;p>Now that we know what is an Adaptive Card, let&amp;rsquo;s have a look on how we get this sent by CLI. The &lt;a href="https://pnp.github.io/cli-microsoft365/cmd/adaptivecard/adaptivecard-send/">documentation&lt;/a> provides us with a sample and this sample needs to have a URL. Where do we get this from? This depends on where you want to send this Adaptive Card to. In this example, I want to use Microsoft Teams as host application and therefore we will need to create an incoming webhook in Teams.&lt;/p>
&lt;h3 id="create-the-webhook">Create the Webhook&lt;/h3>
&lt;p>If you have no clue what is a webhook, no problem, &lt;a href="https://www.twitter.com/williamsrabia">Rabia Williams&lt;/a> got you covered with &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/how-to-configure-and-use-incoming-webhooks-in-microsoft-teams/ba-p/2051118">How to configure and use Incoming Webhooks in Microsoft Teams&lt;/a>.&lt;/p>
&lt;ul>
&lt;li>Open Microsoft Teams&lt;/li>
&lt;li>Click the ellipsis icon on the Teams channel that you want to send the Adaptive Card to&lt;/li>
&lt;li>Click &lt;strong>Connectors&lt;/strong>&lt;/li>
&lt;li>Search for &amp;lsquo;webhook&amp;rsquo;
&lt;img alt="incoming webhook in Teams" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-send-adaptivecards-with-CLIMicrosoft365/incoming-webhook.png">&lt;/li>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Add&lt;/strong> (yes, yet again)&lt;/li>
&lt;li>Give your webhook a name&lt;/li>
&lt;li>If you like to, you can upload a picture- messages sent via this webhook (our Adaptive Card) will have this image then as Profile Pic - this step is optional.&lt;/li>
&lt;li>Click &lt;strong>Create&lt;/strong>&lt;/li>
&lt;li>Copy the generated URL&lt;/li>
&lt;li>Click &lt;strong>Done&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>This URL is the URL we will need in the command later - If you want to, paste it into a notepad or similar.&lt;/p>
&lt;h3 id="author-your-card">Author your Card&lt;/h3>
&lt;p>Now it&amp;rsquo;s time to author your card. You can do this in a lot of editors, I use either the &lt;a href="https://adaptivecards.io/designer">Adaptive Cards Designer&lt;/a> or the &lt;a href="https://marketplace.visualstudio.com/items?itemName=tomlm.vscode-adaptivecards">Adaptive Cards Viewer Extension in Visual Studio Code&lt;/a>, but recently also found &lt;a href="https://marketplace.visualstudio.com/items?itemName=madewithcardsio.adaptivecardsstudiobeta">Adaptive Card Studio&lt;/a>. You can also use one of the &lt;a href="https://adaptivecards.io/samples/">provided samples&lt;/a> and use them as-is or adjust them to your needs.&lt;/p>
&lt;p>If you feel like authoring your Card in Designer or Visual Studio Code is fine for you right now, go ahead with this, otherwise you can also use the examples that are provided in the &lt;a href="https://pnp.github.io/cli-microsoft365/cmd/adaptivecard/adaptivecard-send/">documentation of CLI Microsoft 365&lt;/a>. I used the last one, &amp;lsquo;Send custom card with card data&amp;rsquo;.&lt;/p>
&lt;p>For everyone not very familiar writing and understanding code: We can make more sense of the snippet by copy/paste this into Visual Studio Code, and then press Alt+z to soft-wrap this and then separate the Adaptive Card from the rest:&lt;/p>
&lt;ul>
&lt;li>Select JSON in Language Mode&lt;/li>
&lt;li>Insert somenew lines right before the first &lt;code>{&lt;/code>&lt;/li>
&lt;li>Delete the &lt;code>'&lt;/code> at the beginning and the end of the snippet&lt;/li>
&lt;li>Format with Shift + Alt + f&lt;/li>
&lt;/ul>
&lt;p>Now go ahead and adjust the sample to work in your environment:&lt;/p>
&lt;ul>
&lt;li>replace the URL in the code with the URL that we copied when we added the webhook connector to Teams&lt;/li>
&lt;li>copy the whole code&lt;/li>
&lt;li>run it&lt;/li>
&lt;/ul>
&lt;p>⚡ Turned out, that there are some differences how to use quotes &lt;code>'&lt;/code> or &lt;code>&amp;quot;&lt;/code> - please consider which shell you are using before you copy/paste the code from &lt;a href="https://pnp.github.io/cli-microsoft365/cmd/adaptivecard/adaptivecard-send/">CLI Microsoft 365 documentation&lt;/a>.&lt;/p>
&lt;p>Congratulations 🚀- you sent your first adaptive Card with CLI.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>I hope you liked this little post on how to get started with CLI Microsoft 365 and learning how to send an Adaptive Card with it. I would like to learn, for which usecases you would do this? Please comment below!&lt;/p></description></item><item><title>Why I blog or Why Sharing IS caring</title><link>https://m365princess.com/blogs/2021-02-12-why-i-blog-or-why-sharing-is-caring/</link><pubDate>Fri, 12 Feb 2021 22:00:51 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-12-why-i-blog-or-why-sharing-is-caring/</guid><description>&lt;h1 id="why-i-blog-and-how">Why I blog and how&lt;/h1>
&lt;p>I run this blog for a few years now, and sometimes people outside of my filter bubble ask me why I do this, as no one pays me for that, and I don&amp;rsquo;t have any ads on my website. Instead of explaining this over and over again, I decided to write a metablog post on why I blog. If you come across my website for the very first time today: I mostly blog about Microsoft 365 and Power Platform; sometimes, I throw in some more personal posts as well. I write not only here, but also&lt;/p>
&lt;ul>
&lt;li>on &lt;a href="https://docs.microsoft.com/en-us/microsoft-365/community/">Community Docs&lt;/a>&lt;/li>
&lt;li>took lead of the &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/bg-p/Microsoft365PnPBlog">Microsoft 365 PnP Community on TechCommunity&lt;/a>&lt;/li>
&lt;li>run my own &lt;a href="https://thatkitchenprincess.com">food blog&lt;/a> and&lt;/li>
&lt;li>write the stories for &lt;a href="https://pyod.shop">PYOD news&lt;/a>, the blog of the sticker business that I run together with &lt;a href="https://eliostruyf.com">Elio Struyf&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>You can say that writing is an essential part of my life. I strongly believe in the &lt;strong>Sharing Is Caring&lt;/strong> mission, which most of you will heard about in the context of &lt;a href="https://aka.ms/m365PnP">Microsoft 365 PnP&lt;/a> and I feel it&amp;rsquo;s important to understand why blogging incorporates so much of #SharingIsCaring for me.&lt;/p>
&lt;h2 id="why-do-i-blog">Why do I blog?&lt;/h2>
&lt;h3 id="sharing-is-caring">Sharing Is Caring&lt;/h3>
&lt;p>It seems we already covered the first reason for me to blog: If I know something that could potentially be valuable for others, it will be smart for me to write it down and make it accessible to everyone. Some people then wonder that if I share my professional knowledge for free, that this would weaken my position, there will be less demand to hire me or others as consultants or developers. But I think that the opposite is true: By showing my knowledge and even sharing my learning journey in public, potential customers see what I am capable of, and they don&amp;rsquo;t need to guess if I have the right skillset nor if we have the right fit on a more personal level. Being open and sharing my knowledge has always been a significant driver in my career; running this blog is part of it!&lt;/p>
&lt;h3 id="solidifying-my-knowledge">Solidifying my knowledge&lt;/h3>
&lt;p>If we need to explain something we know to someone else, we will likely try to find examples, analogies, and metaphors that our audience will find relatable, which will increase their understanding. The same happens when we blog about a solution or write a how-to guide or elaborate on thoughts regarding a product or a specific behavior. We need to slow down our thinking and try to see our topic with different eyes. Changing the perspective will enable us to see the whole picture and will complement what we already know. It will allow us to flip sides and perceive our knowledge from a new view. And it will force us to get familiar with the &amp;lsquo;hows&amp;rsquo; and the &amp;lsquo;whys&amp;rsquo; of best practices or different approaches as we will need to explain everything in detail. This applies even more when we want to write bias- and assumption-free, beginner-friendly content.&lt;/p>
&lt;blockquote>
&lt;p>Blogging helps me solidifying my knowledge and broadening my view.&lt;/p>
&lt;/blockquote>
&lt;h3 id="exploring-new-areas">Exploring new areas&lt;/h3>
&lt;p>Sometimes, we write about what we already know, but verz often, we write about topics we want or need to explore. The fact that we need to understand the nitty-gritty details leads to discovering more and more levels- and very often, we will be surprised that the post took us more time to write, covers this topic in more depth, and that we learned so much by writing it. Of course, if we happily jump into a new rabbit hole, we can&amp;rsquo;t know how deep this is and how much time and effort it will take us to understand it. Perhaps we will notice half-way through, that our previous assumptions, where this post will lead to, were partially or entirely wrong, or we will learn how our freshly gained knowledge connects to and relates with the things we already know. This is very rewarding for me: not only learning facts but also connecting the dots and making sense of subjects that I didn&amp;rsquo;t (fully) understand before. As the world (and tech) gets more and more complex, it&amp;rsquo;s essential to understand the impacts of new features, changes in licensing, product launches, etc.&lt;/p>
&lt;blockquote>
&lt;p>Writing blog posts makes me more comfortable and more sustainably understand new things and connecting them with my existing knowledge&lt;/p>
&lt;/blockquote>
&lt;h3 id="extending-comfort-zone">Extending comfort zone&lt;/h3>
&lt;p>But there is another aspect of writing what we know or what we want to explore. It is about being open about our learning journey and finding joy in being a #LearnItAll. I don&amp;rsquo;t think that stepping outside of our comfort zones (and then returning to it) is something we should look for. I instead aim to &lt;em>extend&lt;/em> my comfort zone so that once I managed to do the thing outside of my comfort zone, my comfort zone (and me within) grows.&lt;/p>
&lt;blockquote>
&lt;p>Being a content creator allows me to grow both personally and professionally while sharing my passion with the community&lt;/p>
&lt;/blockquote>
&lt;h3 id="reuseability-repurposeability">Reuseability /Repurposeability&lt;/h3>
&lt;p>Once I wrote a blog post, I can repurpose the gained/solidified knowledge easily: I can give sessions, workshops, talks about it, participate with my perspective in panel discussions, TweetJams, or interviews, and do not need to limit myself to the fact that this is a one-time-publishing event. Reusing my content makes me aware of how much I already know and what is still not addressed to everyone. As we know, new people start every day, and we overestimate a lot of what people know. Therefore, it&amp;rsquo;s a good idea to reuse what we already published because it will expose us to new audiences and help them find their way into this community and pay it forward as well.&lt;/p>
&lt;h3 id="so-called-soft-skills">(so called) Soft Skills&lt;/h3>
&lt;p>Sometimes, I hear people saying, &amp;ldquo;Oh, I couldn&amp;rsquo;t write that often/about so much,&amp;rdquo; or &amp;ldquo;I wouldn&amp;rsquo;t know where to start&amp;rdquo; or even &amp;ldquo;wait, you write &lt;strong>after&lt;/strong> you already finished a long workday?&amp;rdquo;. Writing taught me some skills that I need in my day-to-day life:&lt;/p>
&lt;h4 id="persistence">Persistence&lt;/h4>
&lt;p>I want to write 50 blog posts for this year, which means ~1 blog post per week. This doesn&amp;rsquo;t sound that much, but actually, I need to find inspiration for a topic I want to blog about, and it does not only take the time to write down your thought, but to think, explore, try something out, talk with others, learn, exchange views, learn even more, etc. Being persistent and writing one of my habits that I &lt;em>nearly&lt;/em> do every day helps me with that: I get more easily in the mood of writing, which means that my mind talks more easily to my typing fingers eyes are reading what I am writing. When I am &lt;em>in the zone&lt;/em> I perceive this feeling of being in the flow: I forget time, I am highly focused on the topic and too relaxed. This feeling makes me feel good, and I get somehow a little bit addicted to it. Sometimes during the day, when I am stressed because of too many meetings or much task-switching, I am already looking forward to my writing session in the evenings.&lt;/p>
&lt;h4 id="critical-thinking">Critical Thinking&lt;/h4>
&lt;p>As I write so often, I do not only think about writing when I write, but I see everything also with the lens of: &amp;ldquo;Could this be something that I should elaborate on in a blog post?&amp;rdquo;. And by seeing, I mean all phases of critical thinking like&lt;/p>
&lt;ul>
&lt;li>Remembering&lt;/li>
&lt;li>Understanding&lt;/li>
&lt;li>Applying&lt;/li>
&lt;li>Analyzing&lt;/li>
&lt;li>Evaluating&lt;/li>
&lt;li>Creating&lt;/li>
&lt;/ul>
&lt;p>It is not enough to remember knowledge or understand it or use and implement it, but I also aim to organize, structure, and check what I learned. And then, of course, I love to create by designing and building solutions.&lt;/p>
&lt;blockquote>
&lt;p>Blogging gives me more opportunities to do new things&lt;/p>
&lt;/blockquote>
&lt;h4 id="empathy">Empathy&lt;/h4>
&lt;p>Changing perspectives and figuring out how others feel about something not only broaden my horizon but also affect my decision-making process and make me communicate better. As a result, empathy makes my content more accessible, more inclusive, and more bias-free.&lt;/p>
&lt;blockquote>
&lt;p>Creating Content makes me even more empathetic and lets me even more understand the real value of a holistic diversity of backgrounds and skills&lt;/p>
&lt;/blockquote>
&lt;h4 id="creativity">Creativity&lt;/h4>
&lt;p>This one sounds too apparent. When I &lt;em>create&lt;/em> a new blog post, this is about creativity. It is about the act of making &lt;em>something&lt;/em> out of &lt;em>nothing&lt;/em>. I do not only modify something, but produce something unique that represents my knowledge and beliefs and serves my purpose. Some people fight against the fear of the blinking cursor. But I managed to turn this uncertainty and my sometimes occurring doubts if my thoughts are worth reading into the freedom to express myself how I want it. I enjoy being a content creator and doing this on my terms on conditions. I don&amp;rsquo;t have any deadlines (although I set a goal on frequency), no topics to cover, no specific amount of lines to write. My blogs are an invitation for others to learn and feel inspired to share what they know.&lt;/p>
&lt;blockquote>
&lt;p>My blogging habits make me feel more confident about my skills&lt;/p>
&lt;/blockquote>
&lt;h2 id="how-to-i-blog">How to I blog&lt;/h2>
&lt;p>After the significant &lt;em>why&lt;/em> of blogging and why this represents so much of &lt;em>Sharing is Caring&lt;/em> for me, here is my&lt;/p>
&lt;h3 id="setting">setting&lt;/h3>
&lt;p>I write my blogposts as markdown-files in a public repository in GitHub.&lt;/p>
&lt;h4 id="github">GitHub&lt;/h4>
&lt;p>Some people might think about code or open-source at first when they hear GitHub and wouldn&amp;rsquo;t recommend this as a blogging tool in the first place, but for my purposes, it is incredible. Collaboration with others on blog posts works like a charm, and the commit history is an excellent visual representation of my blogging (and other) efforts. I do not do this to get more green tiles, but the green tiles show me my persistence, and it&amp;rsquo;s more comfortable to reach my goals then. Looking at my commit history is one part of keeping me motivated.&lt;/p>
&lt;p>&lt;img alt="my GitHub commit history in 2021" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/why-I-blog-or-why-sharing-IS-caring/GitHub-commit-history.png">&lt;/p>
&lt;h4 id="public">Public&lt;/h4>
&lt;p>&lt;em>But why on earth do you write in public?&lt;/em> people ask me. &lt;em>You could set up a private repo; once you commit, the whole world can see your &lt;del>shitty&lt;/del> non-mature first drafts, your rough ideas, your typos, and broken links&lt;/em>.&lt;/p>
&lt;p>Yes, that is true. But also, there is nothing wrong with not being perfect or about showing work in progress. I feel very comfortable with that. Plus, if someone finds a typo, they invited to PR that :-)&lt;/p>
&lt;p>&lt;img alt="read.me of luisefreese/blog on GitHub" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/why-I-blog-or-why-sharing-IS-caring/pr-welcome.png">&lt;/p>
&lt;h4 id="markdown">Markdown&lt;/h4>
&lt;p>This allows me to focus on my content instead of being distracted by a UI that gives me too many options to format my text. Concentrating on writing lets me get into this flow-feeling more easily, where words and thoughts seem to directly from my mind into my fingers.&lt;/p>
&lt;h3 id="publishing">Publishing&lt;/h3>
&lt;p>I publish my posts mostly on this website and my recipes on &lt;a href="https://thatkitchenprincess.com">ThatKitchenPrincess.com&lt;/a>, which both run on WordPress. I use the &lt;a href="https://github.com/gis-ops/wordpress-markdown-git/">Documents by Git plugin&lt;/a> to publish my markdown files on WordPress sites directly. The plugin uses the GitHub&amp;rsquo;s &lt;code>markdown&lt;/code> &lt;a href="https://docs.github.com/en/rest/reference/markdown">API&lt;/a> to render HTML. and is easy to configure:&lt;/p>
&lt;ul>
&lt;li>install the plugin&lt;/li>
&lt;li>activate it&lt;/li>
&lt;li>Get a personal access token from GitHub (you can find this in &lt;strong>Developer Settings&lt;/strong> in your GitHub account&lt;/li>
&lt;li>open &lt;code>config.json&lt;/code> and insert values for &lt;code>user&lt;/code> and &lt;code>token.&lt;/code>&lt;/li>
&lt;li>save &lt;code>config.json&lt;/code> file&lt;/li>
&lt;/ul>
&lt;p>To publish a post, I need to insert &lt;code>[git-github-markdown url=&amp;quot;{url of my blogpost from GitHub}&amp;quot;]&lt;/code> in the body of my post and add a title&lt;/p>
&lt;p>I also take care of the featured image and the Twitter Card preview Text.&lt;/p>
&lt;h3 id="marketing">Marketing&lt;/h3>
&lt;p>To share and reshare my blog posts, I add them into a queue so that they get automatically republished on Twitter and LinkedIn, &lt;a href="https://m365princess.com/how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing/">I blogged about how to do that here&lt;/a>.&lt;/p>
&lt;h2 id="conclusion-and-whats-next">Conclusion and What&amp;rsquo;s next?&lt;/h2>
&lt;p>How do you feel about blogging? What&amp;rsquo;s in it for you, and what is rewarding and challenging for you? I would love to learn from you!&lt;/p>
&lt;p>#SharingIsCaring&lt;/p></description></item><item><title>How to get started with HTTP requests in Power Automate</title><link>https://m365princess.com/blogs/2021-02-10-how-to-get-started-with-http-requests-in-power-automate/</link><pubDate>Wed, 10 Feb 2021 21:50:09 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-10-how-to-get-started-with-http-requests-in-power-automate/</guid><description>&lt;h1 id="how-to-get-started-with-http-requests-in-power-automate">How to get started with HTTP requests in Power Automate&lt;/h1>
&lt;p>If you ever wondered what is an HTTP request and why you would want to know how this works - this post is made for you.&lt;/p>
&lt;h2 id="what-is-a-http-request-and-why-would-i-need-it">What is a HTTP request and why would I need it?&lt;/h2>
&lt;p>HTTP requests are a super powerful thing - not only in Power Automate! We will first need to understand what this is in order to determine why we would like to know how to use them. But wait - HTTP?&lt;/p>
&lt;h3 id="what-is-http">What is HTTP?&lt;/h3>
&lt;p>Let&amp;rsquo;s first get us all an the same page. HTTP is the acronym for &lt;strong>Hypertext Transfer Protocol&lt;/strong>. Its purpose is to structure requests and responses over the internet (yeah, you heard of that one 😇) - Data needs to be transferred from Point A to Point B over the network.&lt;/p>
&lt;p>The transfer of resources (like html files, images, videos etc) happens with TCP - which again is acronym, for &lt;strong>Transmission Control Protocol&lt;/strong>. When you read this blog post, TCP manages the channels between your browser (hope you are using Microsoft Edge) and the server. TCP is used a lot for scenarios in which one computer sends something to another. Now what has TCP to do with HTTP? Think of HTTP as the command language for both computers so they are able to communicate.&lt;/p>
&lt;p>When you type a URL like &lt;strong>&lt;a href="https://www.m365princess.com">https://www.m365princess.com&lt;/a>&lt;/strong> into the address bar of your browser, your computer establishes first a TCP connection and then makes a request. We will call your computer now &lt;strong>client&lt;/strong>. The request is a &lt;strong>HTTP GET&lt;/strong> request, as we nicely ask to retrieve the website that the browser shall display. And as we send a (nice) request, the server (site that you requested) will send a response and close the TCP connection afterwards. Of course, there are more methods than just the &lt;strong>GET&lt;/strong> method, you will learn later more about methods &lt;strong>POST, PUT, PATCH, DELETE&lt;/strong>.&lt;/p>
&lt;p>Now that we know what an HTTP request does, we want to learn what it could do in Power Automate&lt;/p>
&lt;h2 id="what-can-http-requests-do-in-power-automate">What can HTTP requests do in Power Automate?&lt;/h2>
&lt;p>Power Automate offers you a huge variety of connectors and within those connectors, many actions which you can use to automate your processes. But although we have so many options, this won&amp;rsquo;t cover everything you need or that you might want to build in Power Automate, which is why we have an HTTP action in Power Automate as well. With the HTTP action we can invoke a REST API.&lt;/p>
&lt;h3 id="what-is-a-rest-api">What is a REST API?&lt;/h3>
&lt;p>Wait but what? Ok, let&amp;rsquo;s slow down a little bit. What is a REST API and would we want to invoke that?&lt;/p>
&lt;p>API is -yet again- an acronym for &lt;strong>application programming interface&lt;/strong> and it is a set of rules and mechanisms. By these an app or a component interacts with others. RESTful APIs (REST means &lt;strong>representational state transfer&lt;/strong>) can return data that you need for your app in a convenient format (for example JSON or XML). By using the &lt;strong>HTTP&lt;/strong> action in Power Automate we can &lt;em>invoke/call&lt;/em> an API by using methods &lt;strong>GET&lt;/strong> (read), &lt;strong>POST&lt;/strong> (write), &lt;strong>PUT&lt;/strong> (update), &lt;strong>PATCH&lt;/strong> (update, but only partially) or &lt;strong>DELETE&lt;/strong> (remove). The same way as our browser made a call towards a website and getting a response using HTTP, we now use HTTP to send a request to a service. In my example, I will use Microsoft Graph. Microsoft Graph is a RESTful API that enables you to access Microsoft Cloud service resources. It is literally THE way to read, create, update and delete resources (like files, teams, meetings etc.).&lt;/p>
&lt;p>💡 Microsoft provides us with an amazing tool to try out Microsoft Graph, it&amp;rsquo;s the &lt;a href="https://developer.microsoft.com/graph/graph-explorer">Graph Explorer&lt;/a>.&lt;/p>
&lt;h2 id="how-to-create-a-http-request-in-power-automate">How to create a HTTP request in Power Automate&lt;/h2>
&lt;p>Now how do we create an HTTP requests in Power Automate? First let me introduce everyone to our little&lt;/p>
&lt;h3 id="use-case">Use case&lt;/h3>
&lt;p>We want to use Power Automate to create a Team with some predefined content in it. To make things easier, we will use the mobile trigger and ask for Team Name, Team Description, and if a user wants a channel for &lt;strong>Learning&lt;/strong> and wants to pin training material (a website) as a tab to this channel
&lt;img alt="manual trigger of flow" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/manually-trigger.png"> (Of course, we would do that later in a form, an app, or a bot, but for understanding the logic of HTTP requests in Power Automate I will keep this as simple as possible)&lt;/p>
&lt;p>We will now add actions to create the team and then we add a condition: If user wants learning material, we want create a chabnnel called &lt;strong>Learning&lt;/strong> and want to pin a website to it.&lt;/p>
&lt;p>Unfortunatley, there is no action &amp;ldquo;pin a website to a channel in Teams&amp;rdquo; in Power Automate. Fortunately, we can still do this by making an HTTP request towards Microsoft Graph. This is why I added the HTTP action into the flow:&lt;/p>
&lt;p>&lt;img alt="HTTP request" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/condition1.png">&lt;/p>
&lt;p>You can see a lot of fields in that HTTP action, so I will make you understand them.&lt;/p>
&lt;h3 id="what-do-we-need-to-make-a-successfull-http-request">What do we need to make a successfull HTTP request?&lt;/h3>
&lt;p>💡 It is a very good idea to open documentation on &lt;a href="https://docs.microsoft.com/en-us/graph/api/overview?toc=.%2Fref%2Ftoc.json&amp;view=graph-rest-1.0">docs.microsoft.com&lt;/a> while buiding your flows that call Microsoft Graph. You will find in nearly all pages four things, that we need to consider when doing an HTTP request:&lt;/p>
&lt;h4 id="endpoint">Endpoint&lt;/h4>
&lt;p>First things first, if we want to call an API with HTTP, we need to know the right endpoint. Think of an endpoint like a phonenumber that you want to call. You need to know it, because otherwise you won&amp;rsquo;t reach the right person.&lt;/p>
&lt;p>An endpoint is a URL like this: &lt;code>https://graph.microsoft.com/v1.0/{resource}?[query_parameters]&lt;/code> and we will later use &lt;code>https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/tabs&lt;/code> to create this tab.&lt;/p>
&lt;h4 id="method">Method&lt;/h4>
&lt;p>Second thing we need to know is which method we want to use. As already explained,&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Method&lt;/th>
&lt;th style="text-align: center">Meaning&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">GET&lt;/td>
&lt;td style="text-align: center">📖 read&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">POST&lt;/td>
&lt;td style="text-align: center">✍ create&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">PUT&lt;/td>
&lt;td style="text-align: center">📰 update&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">PATCH&lt;/td>
&lt;td style="text-align: center">✒ update&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">DELETE&lt;/td>
&lt;td style="text-align: center">🗑 remove&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>If we now open the dropdown menu for the &lt;strong>Method&lt;/strong> field in the HTTP action, we will see a representation of that:&lt;/p>
&lt;p>&lt;img alt="different methods in HTTP action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/methods.png">&lt;/p>
&lt;p>As we want to &lt;em>create&lt;/em> a new tab in a channel, we will use &lt;strong>POST&lt;/strong>.&lt;/p>
&lt;h4 id="headers">Headers&lt;/h4>
&lt;p>Headers are not mandatory for all requests, but look like this: &lt;code>Content-type: application/json&lt;/code> - If they are needed, documentation will tell you.&lt;/p>
&lt;h4 id="data-or-body">Data (or body)&lt;/h4>
&lt;p>If we call an endpoint, it&amp;rsquo;s not enough to specify the URL the request needs to make to, but we will also need to post some additional info into the body of our requests. Most GET requests though don&amp;rsquo;t need information in the body, as they will only list the requested resources.&lt;/p>
&lt;h3 id="fill-in-the-http-action">Fill in the HTTP action&lt;/h3>
&lt;p>If we carefully follow the &lt;a href="https://docs.microsoft.com/en-us/graph/teams-configuring-builtin-tabs">Docs&lt;/a>, we will see that we should do this:&lt;/p>
&lt;p>&lt;code>POST https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/tabs&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>{
&amp;ldquo;displayName&amp;rdquo;: &amp;ldquo;M365Princess Blog&amp;rdquo;,&amp;ldquo;&lt;a href="mailto:teamsApp@odata.bind">teamsApp@odata.bind&lt;/a>&amp;rdquo; : &amp;ldquo;&lt;a href="https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/com.microsoft.teamspace.tab.web%22">https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/com.microsoft.teamspace.tab.web"&lt;/a>,
&amp;ldquo;configuration&amp;rdquo;: {
&amp;ldquo;contentUrl&amp;rdquo;: &amp;ldquo;&lt;a href="https://m365princess.com">https://m365princess.com&lt;/a>&amp;rdquo;,
&amp;ldquo;websiteUrl&amp;rdquo;: &amp;ldquo;&lt;a href="https://m365princess.com">https://m365princess.com&lt;/a>&amp;rdquo;
}
}&lt;/p>
&lt;/blockquote>
&lt;p>Some remarks on that:&lt;/p>
&lt;ul>
&lt;li>Choose Method &lt;strong>POST&lt;/strong> - we already figured that out&lt;/li>
&lt;li>&lt;a href="https://graph.microsoft.com/v1.0/teams/%7Bteam-id%7D/channels/%7Bchannel-id%7D/tabs">https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/tabs&lt;/a> is our URL, but we will need to replace &lt;code>{team-id}&lt;/code> and &lt;code>{channel-id}&lt;/code> with the actual dynamic content&lt;/li>
&lt;li>Choose a &lt;code>displayName&lt;/code> for the Tab as you wish&lt;/li>
&lt;/ul>
&lt;ol start="4">
&lt;li>&lt;code>&amp;quot;teamsApp@odata.bind&amp;quot;&lt;/code> is &amp;ldquo;&lt;a href="https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/com.microsoft.teamspace.tab.web%22">https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/com.microsoft.teamspace.tab.web"&lt;/a>&lt;/li>
&lt;li>Both &lt;code>websiteUrl&lt;/code> and &lt;code>contentUrl&lt;/code> are the full URL of the website you want to pin including &lt;code>https://&lt;/code>. If your website is only &lt;code>http://&lt;/code> you can&amp;rsquo;t use that inside of Teams.&lt;/li>
&lt;/ol>
&lt;p>In total, this looks like this:&lt;/p>
&lt;p>&lt;img alt="http request without auth" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/http-without-auth.png">&lt;/p>
&lt;h4 id="authentication-in-azure-ad">Authentication in Azure AD&lt;/h4>
&lt;p>We are almost there, but some critucal parts are missing. As you can see in the last image, there is a &lt;strong>Show advanced options&lt;/strong> link in the HTTP action and we need to click on it. Our HTTP request need authentication. We can authenticate via Azure Active Directory OAuth, but we will first need to have a representation of our app (yes, this flow that calls Graph is an application) in Azure AD.&lt;/p>
&lt;p>We will follow these steps to register an app in Azure AD:&lt;/p>
&lt;ul>
&lt;li>Go to portal.azure.com and log in&lt;/li>
&lt;li>Click &lt;strong>app registrations&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>New App registration&lt;/strong>&lt;/li>
&lt;li>Give your app a nice name&lt;/li>
&lt;li>Save tenant ID and Client(app) ID somewhere (notepad or similar)&lt;/li>
&lt;li>Click &lt;strong>API PERMISSIONS&lt;/strong> and select &lt;strong>Microsoft Graph&lt;/strong>&lt;/li>
&lt;li>Now look up the permissions needed for this action: [Add tabs to a channel(https://docs.microsoft.com/en-us/graph/api/channel-post-tabs?view=graph-rest-1.0):&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Permission type&lt;/th>
&lt;th style="text-align: center">Permissions (from least to most privileged)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">Application&lt;/td>
&lt;td style="text-align: center">TeamsTab.Create.Group*, TeamsTab.Create, TeamsTab.ReadWriteForTeam.All, TeamsTab.ReadWrite.All, Group.ReadWrite.All, Directory.ReadWrite.All&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>Select all these permissions&lt;/li>
&lt;li>Grant Admin consent&lt;/li>
&lt;li>Click &lt;strong>Certificates &amp;amp; secrets&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>New client secret&lt;/strong>&lt;/li>
&lt;li>Type in a description&lt;/li>
&lt;li>Click &lt;strong>Add&lt;/strong>&lt;/li>
&lt;li>Copy the value and save it in your notepad (you will need that later)&lt;/li>
&lt;/ul>
&lt;h4 id="write-the-ids-into-variables">Write the IDs into variables&lt;/h4>
&lt;p>In our flow, we will now initialize three variables at first level (before any condition) and set their values the copied values of Tenant ID, App ID and App Secret. All three variables are of type string.&lt;/p>
&lt;h4 id="complete-the-http-request">Complete the HTTP request&lt;/h4>
&lt;p>Now we will fill in some more information in the HTTP request:&lt;/p>
&lt;ul>
&lt;li>Authority: &lt;code>https://login.microsoftonline.com&lt;/code>&lt;/li>
&lt;li>Audience: &lt;code>https://graph.microsoft.com&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Besides that, we will use our three variables for Tenant ID, App ID and App Secret.&lt;/p>
&lt;p>&lt;img alt="auth in HTTP request" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/auth.png">&lt;/p>
&lt;p>Our flow should look like this:&lt;/p>
&lt;p>&lt;img alt="flow in total" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/flow-total.png">&lt;/p>
&lt;h2 id="celebrate">Celebrate&lt;/h2>
&lt;p>If we now run the flow and take a look at the new team in Microsoft Teams:&lt;/p>
&lt;p>&lt;img alt="Channel with Tab" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/how-to-get-started-with-http-requests-in-PowerAutomate/channel-with-tab.png">&lt;/p>
&lt;p>we can spot our freshly created tab with the the content we wanted to provide!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>HTTP requests re a super coo method to achieve a lot of things that are not actions in Power Automate, but can still be executed using Microsoft Graph (or other APIs!). What are you using HTTP requests for?&lt;/p></description></item><item><title>How to use Parse JSON action in Power Automate</title><link>https://m365princess.com/blogs/2021-02-08-how-to-use-parse-json-action-in-power-automate/</link><pubDate>Mon, 08 Feb 2021 23:15:04 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-08-how-to-use-parse-json-action-in-power-automate/</guid><description>&lt;h1 id="how-to-use-parse-json-action-in-power-automate">How to use Parse JSON action in Power Automate&lt;/h1>
&lt;p>We can see a a lot of JSON in our Power Automate flow run history, and if you wonder, how you can &lt;em>parse&lt;/em> JSON to make Dynamic Content (which is selectable) out of it so you can more easily make use ob an object, then this post is made for you.&lt;/p>
&lt;p>If you want to know what exactly is JSON and what you need to know about it, please read this &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/introduction-to-json/ba-p/2049369" title="Introduction to JSON">great article by Bob German in the Microsoft 365 PnP Community at TechCommunity&lt;/a> or watch this &lt;a href="https://www.sharepointsiren.com/2021/02/json-intro-for-microsoft-365-people/" title="JSON Intro for Microsoft 365 People">cool video by April Dunnam&lt;/a> first, I will just wait here for you.&lt;/p>
&lt;h2 id="a-little-use-case">a little use case&lt;/h2>
&lt;p>Back again? Cool. Now that you know what JSON is, here is a little use case. Let&amp;rsquo;s say we wanted to post a random item from a SharePoint list to twitter each day using Power Automate. This is a screenshot of my list:&lt;/p>
&lt;p>&lt;img alt="SharePoint list" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson-SPList.png" title="ThatKitchenprincess.com blog posts">&lt;/p>
&lt;p>and this is the overview of the flow that we are going to build:&lt;/p>
&lt;p>&lt;img alt="Flow Overview" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson-overview-flow.png" title="Overview about our flow">&lt;/p>
&lt;h3 id="trigger">Trigger&lt;/h3>
&lt;p>First things first, our trigger needs to be the &lt;strong>Recurrence&lt;/strong> trigger, in which we specify, in which rhythm this flow shall run.&lt;/p>
&lt;h3 id="sharepoint-get-items">SharePoint Get Items&lt;/h3>
&lt;p>Now our flow needs to get all items from the list that we want to randomly pick one item from.&lt;/p>
&lt;h3 id="compose">Compose&lt;/h3>
&lt;p>We need to do some magic so we get a random item, I used the following expression for that:&lt;/p>
&lt;p>&lt;img alt="Expression" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson-expression.png">&lt;/p>
&lt;p>&lt;code>body('Get_items')?['value'][rand(1,length(body('Get_items')?['value']))]&lt;/code>&lt;/p>
&lt;p>We use the rand() expression to get a random list item from that list. The arguments inside of the expression &lt;code>1,length(body('Get_items')?['value'])&lt;/code> mean that our flow needs to pick a random number between 1 and (as this value could change over time) the amount of list items (which is expressed by our &lt;code>length(body('Get_items')?['value']))&lt;/code>expression. The output of this Compose action will reflect a random list item.&lt;/p>
&lt;h3 id="parse-json">Parse JSON&lt;/h3>
&lt;p>Now to the interesting part of this flow: We want to exactly post this random list item but we if we look into our Dynamic Content, it gives us only content from the Get items action, but that is before we get a random list item, and as we do&amp;rsquo;t want to tweet ALL list items, this isn&amp;rsquo;t a good idea. How do we solve this now? Well, we parse JSON, which means that we turn the code into objects again and those objects are then reflected in the Dynamic Content in Power Automate.&lt;/p>
&lt;p>Before we add the Parse JSON action, we need to find out, WHICH JSON we need to parse. As already mentioned, we can see the JSON code in our run history, which is why we save our unfinished flow and let it run. Then we open the run history, and have a look at the Outputs of the &lt;strong>Compose&lt;/strong> action and copy everything inside of that box.&lt;/p>
&lt;p>&lt;img alt="Flow run history- Outputs Compose action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson-history.png">&lt;/p>
&lt;p>Now we edit our flow again, add the Parse JSON action, add the Outputs from our Compose Action as Inputs to that action and click the &lt;strong>Generate from sample&lt;/strong> button. We will now paste the copied JSON into the &lt;strong>Insert a sample JSON Payload&lt;/strong> box and click &lt;strong>Done&lt;/strong>. What we did with that is telling the flow which objects it needs to parse. If we now look at this action, we can see the JSON inside of our Parse JSON action, but all values from the run history are replaced by placeholders: &amp;ldquo;string&amp;rdquo; (if it was text), &amp;ldquo;boolean&amp;rdquo; (if it was a yes/no), etc.&lt;/p>
&lt;p>&lt;img alt="Flow run history- Outputs Compose action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson.png">&lt;/p>
&lt;p>Now that this action knows what to parse, we can proceed with the next action&lt;/p>
&lt;h3 id="send-a-tweet">Send a tweet&lt;/h3>
&lt;p>We can now see a lot of new Dynamic Content which comes from our &lt;strong>Parse JSON&lt;/strong> action.&lt;/p>
&lt;p>&lt;img alt="Dynamic Content" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson-dynamiccontent.png">&lt;/p>
&lt;p>We can now select all values we need in that tweet, plus some more or less generic hashtags (Pro&amp;rsquo;s will add hashtags into a dedicated column in SharePoint.) If we now save and run our flow, it will first GET all items from the list, then identify a random list element and send out a tweet with the Title and URL auf exactly that list item.&lt;/p>
&lt;p>&lt;img alt="Flow run history- Outputs Compose action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/parsejson-twitter.png">&lt;/p>
&lt;h2 id="conclusion--whats-next">Conclusion &amp;amp; What&amp;rsquo;s next?&lt;/h2>
&lt;p>The Parse JSON action can help you turn Outputs from previous actions into Dynamic Content which you can then use in your flow. I&amp;rsquo;d love to know what you do with Parse JSON, let me know!&lt;/p>
&lt;p>&lt;em>If you are now hungry because of recipes in the list: &lt;a href="https://thatkitchenprincess.com" title="my food blog 😋">ThatKitchenPrincess.com&lt;/a>&lt;/em>&lt;/p></description></item><item><title>How to add a simple preview in libraries in SharePoint</title><link>https://m365princess.com/blogs/2021-02-08-how-to-add-a-simple-preview-in-libraries-in-sharepoint/</link><pubDate>Mon, 08 Feb 2021 19:52:42 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-08-how-to-add-a-simple-preview-in-libraries-in-sharepoint/</guid><description>&lt;h1 id="m365hack---how-to-add-a-simple-preview-in-a-library-in-sharepoint">#M365Hack - How to add a simple preview in a library in SharePoint&lt;/h1>
&lt;p>After my last #M365Hack where I showed you how you can create a &lt;a href="https://m365princess.com/create-a-dynamic-preview-for-documents-in-sharepoint/">Dynamic preview for documents in SharePoint&lt;/a> a few people reached out and asked about an easy way to just have a simple preview of a document or image in a library.&lt;/p>
&lt;p>Although this trick isn&amp;rsquo;t new at all, many of my customers found it valuable, which is why I want to share it here as well.&lt;/p>
&lt;h2 id="-question-luise-can-you-build">❓ Question: Luise, can you build&amp;hellip;&lt;/h2>
&lt;p>a preview for images and documents in a SharePoint library?&lt;/p>
&lt;h2 id="-answer-yes-of-course-we-need">❗ Answer: Yes of course, we need&amp;hellip;&lt;/h2>
&lt;p>an additional column (single line of text will do the job) and name this column &lt;strong>Thumbnail&lt;/strong> - thats it. #Magic 🔮&lt;/p>
&lt;p>&lt;img alt="How to add a Thumnbail column to a library" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/Thumbnail.gif">&lt;/p>
&lt;p>Works with .docx, .pdf, .pptx, .xlsx files.&lt;/p></description></item><item><title>How to let people love metadata in SharePoint and Microsoft Teams</title><link>https://m365princess.com/blogs/2021-02-04-how-to-let-people-love-metadata-in-sharepoint-and-microsoft-teams/</link><pubDate>Thu, 04 Feb 2021 20:27:43 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-04-how-to-let-people-love-metadata-in-sharepoint-and-microsoft-teams/</guid><description>&lt;h1 id="how-to-let-people-love--metadata-in-sharepoint-and-microsoft-teams">How to let people love 💗 metadata in SharePoint and Microsoft Teams&lt;/h1>
&lt;p>I&amp;rsquo;ve been told since I started to work with SharePoint, that people hate metadata. They don&amp;rsquo;t understand why information architects need them, feel it&amp;rsquo;s too time-consuming to maintain them, and feel uncomfortable with using them as a method to retrieve the information they need to find.&lt;/p>
&lt;p>The fact that people only think about finding documents works, when they desperately need to know where THE MOST IMPORTANT FILE OF THE UNIVERSE is saved, is not helpful. This leads to &amp;ldquo;let&amp;rsquo;s stick to what we already know,&amp;rdquo; aka &amp;ldquo;we&amp;rsquo;ve always done it like this before&amp;rdquo; thinking. And this means:&lt;/p>
&lt;h2 id="the-evil-trinity-of-lousy-information-architecture">the evil trinity of lousy information architecture&lt;/h2>
&lt;h3 id="copies-of-copies-of-copies-of-copies">copies of copies of copies of copies&amp;hellip;&lt;/h3>
&lt;p>We know that most organizations have redundant content: copies of copies of copies of the same document in different locations. This originates in a massive abuse of email as a document management system. People attach copies without even realizing that those emails cause a bigger problem:&lt;/p>
&lt;p>Let&amp;rsquo;s say Person A sends an email with an attachment to Person B so he/she could change it and send it back. How many different copies would we now have created? Let&amp;rsquo;s count:&lt;/p>
&lt;ul>
&lt;li>1 Original&lt;/li>
&lt;li>2 A&amp;rsquo;s Sent Items folder in Outlook&lt;/li>
&lt;li>3 B&amp;rsquo;s Inbox folder in Outlook&lt;/li>
&lt;li>4 B saves the file in her/his file server&lt;/li>
&lt;li>5 B makes changes to the file and saves it as a separate file on her/his file server&lt;/li>
&lt;li>6 B attaches the file to an email and sends this back to A&lt;/li>
&lt;li>7 A&amp;rsquo;s Inbox folder in Outlook&lt;/li>
&lt;li>8 A saves the file as a separate copy on her/his file server&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="sending copies of copies" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/copies.gif">&lt;/p>
&lt;p>1 file - 7 additional copies. What if we don&amp;rsquo;t play this ping-pong game with 2, but with 5 people and not one time back- and forth, but 3 times? You see, this is the ugly truth of &amp;ldquo;have been sitting in documents and email the whole day&amp;rdquo;. Which one is the valid one? What is the final one?&lt;/p>
&lt;h3 id="awkward-files-names">awkward files names&lt;/h3>
&lt;p>To cope with that, people get creative and invent super awkward file names, like&lt;/p>
&lt;p>&lt;strong>ProjectDeathstar_MasterplanGoLive_DV_21-02-04_V3_final2_FINAL.docx&lt;/strong>&lt;/p>
&lt;p>I mean, wow, what is that? Shouldn&amp;rsquo;t they stick to a naming convention?&lt;/p>
&lt;p>&lt;img alt="naming convention Dilbert" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/AML-25602_577155_mutable_color.gif">&lt;/p>
&lt;p>What are people trying to do? They add some attributes of a file to its name to more easily know what this document is about before they need to open it. Versions, status, name of people who created/modified the document (but maybe that&amp;rsquo;s the name of a Project Manager, a role that needs to approve something?! We don&amp;rsquo;t know), and, almost mandatory, a date. Is this a due date, the date of creation, or the last edit? Again, nobody can know. We guesswork, we compare, we track changes, and send even more emails.. &lt;em>Could you please send me the LATEST version of that file?!&lt;/em>&lt;/p>
&lt;p>&lt;img alt="awkward file name contains metadata" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/metadata.gif">&lt;/p>
&lt;p>To be able to cope with these awkward file names, they try to segment information so that there are fewer items to scroll through:&lt;/p>
&lt;h3 id="cascading-folder-structures">cascading folder structures&lt;/h3>
&lt;p>Creating a gazillion of cascading folders seems to be the holy grail in some organizations. They are used to working with folders when it comes to paperwork and applying the working methods back then to their digital work. They did not evolve in their working behavior in the last (at least) 30 years. This is why they didn&amp;rsquo;t find approaches that support retrievability of information, but that map working with papers and that imitates as if they were still working on paper:&lt;/p>
&lt;p>A folder allows only one attribute for a file, and people need to pick one out of several attributes and prioritize precisely this. Let&amp;rsquo;s say our document belongs to Project DeathStar, is a Plan, and is still a draft? Should we then file it under the Project folder, the Plan folder, or the Draft Folder if they happen on the same level?&lt;/p>
&lt;p>What if we nest folders into other folders? Will this solve the problem? Well, let&amp;rsquo;s see: An accounting department needs to store invoices, purchase notices, and contracts. Shall we group those by type or by year?&lt;/p>
&lt;p>&lt;img alt="cascading folder structures" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/folderstypeandyear.gif">&lt;/p>
&lt;h2 id="how-does-this-lead-us-to-people-out-of-a-sudden-loving-metadata">How does this lead us to people out of a sudden loving metadata?&lt;/h2>
&lt;p>Most of them know that we can use metadata in SharePoint and Teams, but they will insist that it takes too much time to fill in columns with data to describe what a file is about. But once we explain the evil trinity (copies of copies of copies, awkward file names, cascading folder structures) and show them how time-consuming this behavior is, they will open their minds and get it:&lt;/p>
&lt;p>Yes, it WILL take some time to maintain metadata, but this will save everyone even more time because we can better filter and therefore faster retrieve what we were looking for in the first place. Plus: we will not have to deal with this tremendous amount of copies anymore, as SharePoint offers us version history.&lt;/p>
&lt;p>Please note that a folder per se isn&amp;rsquo;t evil, but that if we overdo (3 levels and more), we will create insane chaos.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next?&lt;/h2>
&lt;p>I would love to know what you do to convince people to choose metadata instead of folders? One of the blog posts could be around version history- what do you think?&lt;/p></description></item><item><title>Create a dynamic preview for documents in SharePoint</title><link>https://m365princess.com/blogs/2021-02-03-create-a-dynamic-preview-for-documents-in-sharepoint/</link><pubDate>Wed, 03 Feb 2021 15:57:12 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-03-create-a-dynamic-preview-for-documents-in-sharepoint/</guid><description>&lt;h1 id="create-a-dynamic-preview-for-documents-in-sharepoint">Create a dynamic preview for documents in SharePoint&lt;/h1>
&lt;p>Blew some minds at customers with SharePoint List formatting and Power Automate flows (read here &lt;a href="https://m365princess.com/sharepoint-list-formatting-made-easy/">Part 1&lt;/a>, &lt;a href="https://m365princess.com/how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing/">Part 2&lt;/a>, &lt;a href="https://m365princess.com/how-to-create-a-content-plan-for-your-social-media-activities-in-a-calendar-view-in-sharepoint-lists-automate-all-the-boring-work/">Part 3&lt;/a>) but the preview of an image was not, what they had in mind.&lt;/p>
&lt;h2 id="-question-luise-can-you-build">❓ Question: Luise, can you build&amp;hellip;&lt;/h2>
&lt;p>a dynamic preview, so that we can click through our PowerPoint presentations?&lt;/p>
&lt;h2 id="-answer-yes-of-course-we-need">❗ Answer: Yes of course, we need&amp;hellip;&lt;/h2>
&lt;p>a page, a library and 2 webparts for that :-)&lt;/p>
&lt;p>Here we go:&lt;/p>
&lt;ol>
&lt;li>Create a new page&lt;/li>
&lt;li>Clean up the page, we don&amp;rsquo;t want to display text here&lt;/li>
&lt;li>Insert the document library in the left column&lt;/li>
&lt;li>Insert the file viewer web part in the right column&lt;/li>
&lt;li>Cancel the dialog that asks which document you want to preview&lt;/li>
&lt;li>Click the Pencil Icon on the file viewer web part&lt;/li>
&lt;li>Click the Ellipsis Icon at the top right corner and select &lt;strong>Connect to source&lt;/strong>&lt;/li>
&lt;li>In the Dropdown Menu select your document library&lt;/li>
&lt;li>Publish your page&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Create a dynamic preview of documents in SharePint" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/Fileviewer.gif">&lt;/p>
&lt;p>💡 You can now click through presentations, scroll through lengthy Word files and preview Excel spreadsheets.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Sweet little #SharePoint hack 🚀&lt;/p></description></item><item><title>How to spend less time organizing your agenda with Bookings, FindTime, Cortana &amp; MyAnalytics</title><link>https://m365princess.com/blogs/2021-02-02-how-to-spend-less-time-organizing-your-agenda-with-bookings-findtime-cortana-myanalytics/</link><pubDate>Tue, 02 Feb 2021 21:07:55 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-02-how-to-spend-less-time-organizing-your-agenda-with-bookings-findtime-cortana-myanalytics/</guid><description>&lt;h1 id="how-to-spend-less-time-organizing-your-agenda">How to spend less time organizing your agenda&lt;/h1>
&lt;p>Keeping our calendars as a representation of our daily agenda is crucial, especially when we have more meetings than ever before. In this blog post, I will give you some ideas on how to let smart tools integrate with Outlook so that you need to spend less time scheduling meetings and have enough time to get the work in between your meetings done.&lt;/p>
&lt;h2 id="use-microsoft-bookings-to-schedule-meetings-with-customers-and-the-community">Use Microsoft Bookings to schedule meetings with customers and the community&lt;/h2>
&lt;p>Included in my Microsoft 365 license comes &lt;a href="https://docs.microsoft.com/en-us/microsoft-365/bookings/bookings-overview?view=o365-worldwide">Microsoft Bookings&lt;/a>, a service that lets externals book appointments via a website in free slots depending on your availability without letting them see your actual calendar. It automatically sends invites, confirmations, and reminders and integrates perfectly with Microsoft Teams meetings. Bookings is available for all customers with&lt;/p>
&lt;ul>
&lt;li>Microsoft 365 Business Premium&lt;/li>
&lt;li>Microsoft 365 Business Standard&lt;/li>
&lt;li>A3&lt;/li>
&lt;li>A5&lt;/li>
&lt;li>E3&lt;/li>
&lt;li>E5&lt;/li>
&lt;/ul>
&lt;p>licenses, and you will need Outlook on the web or Outlook mobile have enabled, which requires your mailbox to be in Exchange Online. The bookings calendar will create a new calendar in Exchange Online and a related entry in Azure Active Directory. You can add scheduling policies and define time increments to be able to book, lead time upfront of a meeting, and if you need to have time in between of meetings.&lt;/p>
&lt;p>I added a link to my bookings page to my Outlook signature and told my customers about it. Whenever a new customer-to-be or someone from the community wants to schedule a meeting, I reply with this link, and bookings does the rest for me. It reflects my availability, and I can exclude times from it as well. The beauty of it is that I do not need to send emails back and forth about scheduling a meeting anymore. This makes scheduling fast and effortless and looks very professional as well. &lt;a href="https://www.microsoft.com/en-us/microsoft-365/business/scheduling-and-booking-app">Find more information here&lt;/a>.&lt;/p>
&lt;h2 id="findtime-to-schedule-meetings-with-larger-groups-and-let-them-vote-for-a-date">FindTime to schedule meetings with larger groups and let them vote for a date&lt;/h2>
&lt;p>FindTime is an Outlook Add-In by Microsoft, which let&amp;rsquo;s sends some proposals for meeting times to one or more persons. You need to select those times by yourself and that the recipients will vote on the times if they can make it or not and what of the options would be their preferred time slot to meet. This way, you can create a poll and see which time slot you should pick because all or at least the majority of invitees confirmed the upcoming meeting.&lt;/p>
&lt;p>&lt;img alt="FindTime create meeting" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/findtimeaddin.gif">&lt;/p>
&lt;h2 id="use-cortana-to-schedule-time-for-weekly-meetings">Use Cortana to schedule time for weekly meetings&lt;/h2>
&lt;p>I use &lt;a href="https://calendar.help">Cortana&lt;/a> to schedule 1:1 weekly meetings that do not need to be on a predefined time/weekday each week, but can be handled flexibly, and also use it for (virtual) coffee meetings.&lt;/p>
&lt;p>&lt;img alt="Cortana Schedule" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/calendarhelp.png">&lt;/p>
&lt;p>After clicking on the tile, my Outlook client opens a new email with &lt;a href="mailto:cortana@calendar.help">cortana@calendar.help&lt;/a> in cc and a message to Cortana to book a meeting for 30 minutes every week. You can, of course, change the value and the default value as well. Cortana will now send email an email to the recipient and propose three different free timeslots and get back to you after the scheduling was successful.&lt;/p>
&lt;p>&lt;img alt="Cortana weekly" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/cortanaweekly.png">&lt;/p>
&lt;p>You might have noticed that you can also book focus time with Cortana, but I prefer to do this with Insights/My Analytics:&lt;/p>
&lt;h2 id="use-my-analyticsinsights-to-increase-awareness-protect-your-time-and-dont-miss-staying-on-track">Use My Analytics/Insights to increase awareness, protect your time, and don&amp;rsquo;t miss staying on track&lt;/h2>
&lt;p>I use a service called &lt;a href="https://docs.microsoft.com/en-us/workplace-analytics/myanalytics/mya-landing-page">My Analatics&lt;/a> to protect my precious time, makes me aware of unhealthy working habits, support me in staying in touch with people relevant to me and increase my reach&lt;/p>
&lt;h3 id="how-does-my-analytics-work">How does My Analytics work?&lt;/h3>
&lt;p>My analytics is a personal productivity tracker. Think about it as your Smart Watch, which tells you to go for a walk, stand up regularly, or measure how fast you run (when you go for a run) to establish healthy habits.&lt;/p>
&lt;p>My analytics works with 4 elements:&lt;/p>
&lt;ul>
&lt;li>MyAnalytics dashboard - which is where you can see how you spent your time over the four weeks across four different plans: focus, wellbeing, collaboration, and network&lt;/li>
&lt;li>Insights Outlook add-in - gives you suggestions based on your working behavior&lt;/li>
&lt;li>Digests - This is a summary of the last week with actionable insights&lt;/li>
&lt;li>Inline suggestions in Outlook - recommendations to improve your work patterns&lt;/li>
&lt;/ul>
&lt;p>Ever wondered how it can already be 5 pm and how the heck you spent your day? Have you been busy all day long but can&amp;rsquo;t tell how 9 hours could have passed, but you still couldn&amp;rsquo;t check off your most important tasks? Well, we all know those days and even weeks. The reasons are pretty obvious:&lt;/p>
&lt;ul>
&lt;li>poor planning and prioritizing of tasks / unclear commitment which tasks need to be completed today&lt;/li>
&lt;li>too many meetings that don&amp;rsquo;t add value to your work&lt;/li>
&lt;li>things get into the way of your planning, and it&amp;rsquo;s hard to stay on track&lt;/li>
&lt;li>it&amp;rsquo;s challenging to change habits&lt;/li>
&lt;/ul>
&lt;p>To tackle these, I do the following:&lt;/p>
&lt;ol>
&lt;li>All my tasks will be reflected in Microsoft To Do:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>personal tasks&lt;/li>
&lt;li>tasks that live in planner but can be accessed via To Do&lt;/li>
&lt;li>emails from external people that contain task, which can be accessed via To Do&lt;/li>
&lt;/ul>
&lt;p>Within To Do, we can find a very interesting feature called &amp;ldquo;My Day,&amp;rdquo; in which you daily commit to the tasks you want to work on today. Learn more about it in my blog about &lt;a href="https://m365princess.com/11-5-reasons-to-fall-in-love/">reasons why I fell in love with Microsoft to Do&lt;/a>. If you feel like having already too much on the plate, it can be useful to dig a little bit deeper into &lt;a href="https://m365princess.com/how-to-avoid-overcommitting/">How to stop overcommitting&lt;/a>&lt;/p>
&lt;ol start="2">
&lt;li>I decide very carefully which meetings I attend. If I can&amp;rsquo;t learn something in a meeting that I work towards my team&amp;rsquo;s objectives or if I can&amp;rsquo;t contribute in a meaningful way, I will not participate. If you want to know more about why and how I do this, read my blog post about &lt;a href="https://m365princess.com/digital-declutter-time/">digital clutter and how we use our time&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>To protect my focus time, I use My Analytics:&lt;/p>
&lt;p>&lt;img alt="focus plan" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/insights-focusplan2.png">&lt;/p>
&lt;p>The Focus Plan gives me an overview of what happened in the past four weeks and encourages me to change what didn&amp;rsquo;t go as planned. It lets me schedule my focus time (which will automatically set my Microsoft Teams status to &lt;strong>DND&lt;/strong>) and review overlapping events in my calendar, not to double-book.&lt;/p>
&lt;p>&lt;img alt="wellbeing plan" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/insights-wellbeingplan.png">&lt;/p>
&lt;p>The Wellbeing Plan gives me insights into my ability to disconnect and recharge my inner batteries. It calls me out on working crazy hours and suggests taking real breaks. Being confronted with real numbers instead of just guessing is very valuable because I am very hesitant about taking time off.&lt;/p>
&lt;p>Ever wondered if the time you spend with meetings is adding value? The collaboration plan confronts me with my meeting habits. If I joined on time, got distracted by writing emails during meetings, if I added a Teams link or if my meetings overlapped. This report is gold because it reminds me that I am responsible for getting the most out of the very precious time when I meet with others. If we try to multitask, need to join late, and leave early, I will probably prioritize other things. I find it very helpful to readjust my commitments and make more informed decisions on what to work on.&lt;/p>
&lt;p>&lt;img alt="collaboration plan" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/insights-collaborationplan.png">&lt;/p>
&lt;p>Networking plan makes sure you don&amp;rsquo;t stay in your filter bubble only but expand your network.&lt;/p>
&lt;p>&lt;img alt="network plan" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/insights-networkplan.png">&lt;/p>
&lt;ol start="3">
&lt;li>Things that get in the way&lt;/li>
&lt;/ol>
&lt;p>Now that My Analytics/Insights is protecting my focus time, evenings and nudges me into healthy meetings, it is less likely, that things conflict with how I planned my time. Of course, we all know those emergency cases, but they don&amp;rsquo;t occur every single day.&lt;/p>
&lt;ol start="4">
&lt;li>Habits&lt;/li>
&lt;/ol>
&lt;p>Well, changing your habits is hard. Patterns are not only well-trained and familiar; they also originate in some common beliefs/ mental models. If you want to learn more about those, read the article about &lt;a href="https://docs.microsoft.com/en-us/microsoft-365/community/why-m365-adoption-projects-fail">Why Microsoft 365 adoption projects fail&lt;/a> on docs.microsoft.com - collaboration with &lt;a href="https://gezeitenbrand.de">Michael Roth&lt;/a>&lt;/p>
&lt;p>If change is that hard- how can we still master it?&lt;/p>
&lt;p>We need to stick to a process:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>set a specific goal
Without a clear purpose, you can&amp;rsquo;t improve and can&amp;rsquo;t track your progress. Set a goal like &amp;ldquo;Over the next two weeks, I will batch process my emails at 10 am and 3 pm.&amp;rdquo;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>plan&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>It would help if you were very clear and concise about why you want to do that and what drives you to set up this goal. Our example could be that it reduces your stress level, leads to fewer interruptions, and more focused working hours.&lt;/p>
&lt;ol start="3">
&lt;li>practice&lt;/li>
&lt;/ol>
&lt;p>Now comes the most challenging part - DOING what we planned. And now My Analytics comes into play: For example, with its Outlook Add-in, that prompts me to make an appointment with myself and book focus time. I can also book focus time regularly in the Dashboard, but it comes very handy in Outlook, where we still receive and process our mails and do lots of meeting planning.&lt;/p>
&lt;p>&lt;img alt="Outlook Insights Add In" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/insights-outlook-addin.png">&lt;/p>
&lt;ol start="4">
&lt;li>celebrate&lt;/li>
&lt;/ol>
&lt;p>Celebrating the small wins is essential - we can reflect on our journey and feel what&amp;rsquo;s right for us! It is also an excellent opportunity to share what helps us to inspire more people.&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next&lt;/h2>
&lt;p>Using a combination of different tools for different workloads helps me getting the most out of my day - which can be quite busy. I would love to know how you schedule your meetings, protect your focus time and manage to get your work done! Which tools do you use, which approaches help you?&lt;/p></description></item><item><title>How to create a Content plan for your Social Media activities in a calendar view in SharePoint lists &amp; automate all the boring work</title><link>https://m365princess.com/blogs/2021-02-02-how-to-create-a-content-plan-for-your-social-media-activities-in-a-calendar-view-in-sharepoint-lists-automate-all-the-boring-work/</link><pubDate>Tue, 02 Feb 2021 13:50:47 +0000</pubDate><guid>https://m365princess.com/blogs/2021-02-02-how-to-create-a-content-plan-for-your-social-media-activities-in-a-calendar-view-in-sharepoint-lists-automate-all-the-boring-work/</guid><description>&lt;h1 id="how-to-create-a-content-plan-for-your-social-media-activities-and-automate-all-the-boring-work">How to create a Content plan for your Social Media activities and automate all the boring work&lt;/h1>
&lt;p>I love SharePoint and I am amazed by lists - regardless if I use them standalone as Microsoft Lists, in SharePoint or in Microsoft Teams. One thing, all my customers want to know, are calendar views. I run, together with &lt;a href="https://eliostruyf.be">Elio Struyf&lt;/a> a &lt;a href="https://pyod.shop">sticker shop&lt;/a> and we both do not only aim for delivering amazing stickers, but also continuesly improve our business. You could read in my last posts about list formatting &lt;a href="https://m365princess.com/sharepoint-list-formatting-made-easy/">Part 1&lt;/a> and &lt;a href="https://m365princess.com/how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing/">Part 2&lt;/a>, how I automated some social media activities. In this post I will show you how to create a content calendar and also automate creating actionable tasks from that as well.&lt;/p>
&lt;h2 id="create-a-list-with-calendar-view">Create a list with calendar view&lt;/h2>
&lt;p>I created a list in SharePoint and added columns for date, tweettext, hashtags URL and additional actions. Important: The Column, that contains the date needs to be a date column. I created some list items and created a new view:&lt;/p>
&lt;ul>
&lt;li>Click &lt;strong>All Items&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Create New View&lt;/strong>&lt;/li>
&lt;li>Give your view a nice name&lt;/li>
&lt;li>Click &lt;strong>Show as &lt;code>Calendar&lt;/code>&lt;/strong>&lt;/li>
&lt;li>Click &lt;strong>Create&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Create a list with calendar view" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/listformattingcalendarview.gif">&lt;/p>
&lt;h2 id="automate-creating-tasks-in-microsoft-planner">automate creating tasks in Microsoft Planner&lt;/h2>
&lt;p>We use Microsoft Planner for our tasks, which is why I want the additional tasks, that come along with an event in this calendar to appear in our Planner board. Instead of copy-pasting everything manually, I built a flow that does exactly that for me automatically:&lt;/p>
&lt;p>&lt;img alt="flow that creates tasks in plannner fromk list item" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/flowlisttoplanner.png">&lt;/p>
&lt;p>This flow is triggered when a new item is created or modified and then checks if the column with additional tasks is not empty. If that condition returns &lt;code>true&lt;/code>, it creates a task with a due date and assignes me to that task. After that, the flow updates that task with a description and a Link to the list item.&lt;/p>
&lt;h2 id="automate-tweeting-about-the-content-from-calendar">automate tweeting about the content from calendar&lt;/h2>
&lt;p>As I already have content in the list to be tweeted, I now want to post the tweets automatically on value in my &lt;code>Date&lt;/code> column. I created a scheduled cloud flow, which runs every day and gets only the items from my list where the Date equals today&amp;rsquo;s date.&lt;/p>
&lt;p>To achieve this, I used a Filter Query in the &lt;strong>Get items&lt;/strong> action: &lt;code>Date eq 'utcnow('yyyy-MM-dd')'&lt;/code> and then created and shared an update in Buffer:&lt;/p>
&lt;p>&lt;img alt="flow that tweets a list item from if Date is today" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/flowlisttotwitterbybuffer.png">&lt;/p>
&lt;h2 id="conclusion--whats-next">Conclusion &amp;amp; What&amp;rsquo;s next?&lt;/h2>
&lt;p>With a list, a view and 2 flows I could automate another piece of content management and daily marketing in this small business scenario. I would love to know what you like to use calendar view for and what you want to automate.&lt;/p></description></item><item><title>How we use SharePoint list formatting and Power Automate at PYOD to ease our marketing</title><link>https://m365princess.com/blogs/2021-01-30-how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing/</link><pubDate>Sat, 30 Jan 2021 23:49:13 +0000</pubDate><guid>https://m365princess.com/blogs/2021-01-30-how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing/</guid><description>&lt;h1 id="how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing">How we use SharePoint list formatting and Power Automate at PYOD to ease our marketing&lt;/h1>
&lt;p>Together with &lt;a href="https://www.eliostruyf.com">Elio Struyf&lt;/a>, I run an online sticker shop called &lt;a href="https://pyod.shop">PYOD - pimpyourowndevice.com&lt;/a>. Elio described, &lt;a href="https://www.eliostruyf.com/running-online-store-powerplatform-azure/">how we use Power Platform and Azure Functions&lt;/a> to keep the store up-to-date and to print the labels for our envelopes to send out the stickers. This blog post shows, how I use SharePoint and Power Platform to market our products and make our day-to-day work even easier. We strongly believe that low-code and custom code complement each perfectly and that we can achieve more together.&lt;/p>
&lt;h2 id="posting-a-random-sticker">Posting a random sticker&lt;/h2>
&lt;p>We use a SharePoint list for our inventory. This means, that each sticker is an item in the list and that we have dedicated columns for all attributes like price, dimensions, material in that list. These attributes are of course reflected in the store. To automate sharing our stickers on socials, I added some more columns like twitter text, twitter hashtags, and a shortlink to this sticker in the store. I experimented a bit with Buffer to automate posting on Social Media, but I was not happy with it, as I couldn&amp;rsquo;t find an easy way to post stickers in a certain rhythm. My solution: building a Power Automate flow, which posts a random item from said SharePoint list.&lt;/p>
&lt;p>The flow looks like this:&lt;/p>
&lt;p>&lt;img alt="PYOD-twitter" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/pyod-twitter-flow-full.png">&lt;/p>
&lt;h3 id="how-does-this-flow-work">How does this flow work?&lt;/h3>
&lt;p>It is a scheduled cloud Power Automate flow, which runs every 15 hours and gets all items from the inventory that are for sale. The compose action will get a random item, because I do not want to post every item but only one. The expression is:&lt;/p>
&lt;p>&lt;code>body('Get_items')?['value'][rand(1,length(body('Get_items')?['value']))]&lt;/code>&lt;/p>
&lt;p>I use the rand() function to get a random value between 1 and the amount of list items in this list.&lt;/p>
&lt;p>In the next step, I parse the JSON code from the Outputs of my compose action so that I get all keys as dynamic content in my flow. Now I will create an update in Buffer, with the title of my item, description, hashtags and the link to the shop. Finally, I share the created update from Buffer. The Flow &lt;a href="https://pimpyourowndevice.com/stickers/automagically-large/">automagically 🦄&lt;/a> creates an &lt;code>Apply to Each&lt;/code> loop for me, as it could be, that there is more than one update created.&lt;/p>
&lt;h3 id="why-does-this-run-every-15-hours">why does this run every 15 hours?&lt;/h3>
&lt;p>Well, there are timezones, and as this shop sells and ships worldwide it&amp;rsquo;s beneficial to not always post at the same time to reach different people. I recreated the same flow for our different social media accounts and let each of them run with a delay of a couple of hours to maximize reach and visibility of the brand.&lt;/p>
&lt;h2 id="ooops-something-went-wrong">Ooops, something went wrong&lt;/h2>
&lt;p>Sometimes, we make mistakes, like broken links because of a typo in our list, for example. As this doesn&amp;rsquo;t look good, I created a &lt;strong>oops, something went wrong&lt;/strong> column in our SharePoint list and associated a flow with it:&lt;/p>
&lt;p>&lt;img alt="tweet this item now" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/pyod-twitter-now.png">&lt;/p>
&lt;h3 id="how-does-this-flow-work-1">How does this flow work?&lt;/h3>
&lt;p>This flow is very similar to the first one. I used the &lt;strong>For a selected Item&lt;/strong> trigger, get the item of the list that matches the &lt;code>ID&lt;/code> of the selected item and then update and create my update via Buffer as shown before.&lt;/p>
&lt;h3 id="how-does-this-flow-work-in-the-sharepoint-list">How does this flow work in the SharePoint list?&lt;/h3>
&lt;p>To have a nice trigger in the list, I formatted the &lt;strong>oops, something went wrong&lt;/strong> column with &lt;a href="https://github.com/pnp/sp-dev-list-formatting/blob/master/column-samples/generic-start-flow/start-flow-button.json">this sample&lt;/a>. To learn, how you can apply column samples to your SharePoint lists, you can read my blog about &lt;a href="https://m365princess.com/sharepoint-list-formatting-made-easy/">SharePoint list formatting made easy&lt;/a>&lt;/p>
&lt;p>This particular sample will create a button in each row of your list which executes a flow. To know, which flow it needs to execute (as you can run several flows associated to the same list), you will need to provide the ID of the flow that you want to run. You can find it in URL of the flow. You can change text, backgroundcolor, icon next to the text and more, please use &lt;a href="https://developer.microsoft.com/en-us/fluentui#/styles/web/icons">Fluent UI Icons&lt;/a> for reference.&lt;/p>
&lt;p>Of course, we do not only use that for typos, but also if we get a question on socials about a certain sticker to easily tweet it. I created this flow two times for our different social media profiles and formatted the list columns accordingly:&lt;/p>
&lt;p>&lt;img alt="SharePoint list with flow action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/pyod-twitter-list.png">&lt;/p>
&lt;h2 id="news-by-rss">News by RSS&lt;/h2>
&lt;p>Blogging for me does not stop with my own blog and &lt;a href="https://www.thatkitchenprincess.com">my foodblog&lt;/a>, but I also write stories on &lt;a href="https://pimpyourowndevice.com/news">PYOD News&lt;/a>. Because I want to automatically tweet these micro blog posts, I build another flow to do exactly that, after Elio prepared an rss feed for the news site.&lt;/p>
&lt;p>&lt;img alt="PYOD post rss" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/pyod-ideas-news-rss.png">&lt;/p>
&lt;h2 id="ideation-and-decision-making-progress">Ideation and Decision making progress&lt;/h2>
&lt;p>Running a sticker business means, that all &lt;a href="https://pimpyourowndevice.com/news/2021/01/how-we-started-pixelart-stickers/">cool ideas for new stickers&lt;/a> need to have a decent home. That&amp;rsquo;s where our Ideas lists comes into play. Each idea is an item and we work each week through a defined process that is reflected in the &lt;strong>Status&lt;/strong> and &lt;strong>Next Step&lt;/strong> columns. We define, if we need to still work on the idea until it is ready to be printed, who will design the sticker or if we will defer the idea. To determine, how much we believe in the sticker, we use 3 columns: &lt;strong>Rating Elio&lt;/strong>, &lt;strong>Rating Luise&lt;/strong> and &lt;strong>Rating&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>RatingElio&lt;/strong> and &lt;strong>RatingLuise&lt;/strong> are Number Columns, with a nice rainbow-heart-effect formatting. The column expects values between 1 (not very convinced) and 5 (super convinced) and shows emoji hearts instead of a number:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;$schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://developer.microsoft.com/json-schemas/sp/v2/column-formatting.schema.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;div&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;children&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;span&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;💛&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$RatingElio] &amp;gt;= 1, &amp;#39;inherit&amp;#39;,&amp;#39;none&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;span&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;🧡&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$RatingElio] &amp;gt;= 2, &amp;#39;inherit&amp;#39;,&amp;#39;none&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;span&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;💖&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$RatingElio] &amp;gt;= 3, &amp;#39;inherit&amp;#39;,&amp;#39;none&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;span&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;💜&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$RatingElio] &amp;gt;= 4, &amp;#39;inherit&amp;#39;,&amp;#39;none&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;elmType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;span&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;txtContent&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;💙&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;style&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;display&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;=if([$RatingElio] &amp;gt;= 5, &amp;#39;inherit&amp;#39;,&amp;#39;none&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Rating&lt;/strong> is a calculated column and will sum up &lt;strong>RatingElio&lt;/strong> and &lt;strong>RatingLuise&lt;/strong>&lt;/p>
&lt;p>&lt;img alt="PYOD ideas list" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/pyod-ideas-list-format.png">&lt;/p>
&lt;p>You can find a similar sample here: &lt;a href="https://github.com/LuiseFreese/sp-dev-list-formatting/tree/master/column-samples/number-conditional-format">conditional format&lt;/a>&lt;/p>
&lt;h2 id="conclusion--feedback">Conclusion &amp;amp; Feedback&lt;/h2>
&lt;p>I love to automate everything AND to have awesome looking lists with rainbows, unicorns and special buttons that make my life easier as I don&amp;rsquo;t need to care about all day-to-day tasks, but can focus on ideation and growing the business. I am curious for which use cases you use lists and flows?&lt;/p>
&lt;p>I would love to hear from you!&lt;/p>
&lt;p>&lt;em>#SharingIsCaring ❤&lt;/em>&lt;/p></description></item><item><title>SharePoint list formatting made easy</title><link>https://m365princess.com/blogs/2021-01-28-sharepoint-list-formatting-made-easy/</link><pubDate>Thu, 28 Jan 2021 01:02:52 +0000</pubDate><guid>https://m365princess.com/blogs/2021-01-28-sharepoint-list-formatting-made-easy/</guid><description>&lt;h1 id="modern-sharepoint-list-formatting">Modern SharePoint list formatting&lt;/h1>
&lt;p>This article shall give you guidance and inspiration on how to turn your classic boring lists into interactive modern list experiences, which will wow your users, let them get information at first sight, and increase overall productivity. This post is not about the Microsoft 365 list templates; I will cover them in one of the next blog posts.&lt;/p>
&lt;p>If you never heard anything about modern SharePoint list formatting, don&amp;rsquo;t worry; I will guide you through this.&lt;/p>
&lt;h2 id="why-would-we-use-sharepoint-lists">Why would we use SharePoint lists&lt;/h2>
&lt;p>First things first: Why should we use SharePoint lists and not use - for instance - an Excel spreadsheet? Because we don&amp;rsquo;t need to hide information in cascading folders, that should be at the user&amp;rsquo;s fingertips. The beauty of lists lies in their simplicity and flexibility to organize work and track the information that matters most to our businesses.&lt;/p>
&lt;p>Creating, sharing, and tracking lists is easy and available on any device; everyone stays in the loop, and we can use lists for all kinds of purposes like tracking issues, assets, routines, contacts, inventory, and more. Lists can easily be customized to make them visually more appealing.&lt;/p>
&lt;h2 id="how-can-we-turn-on-modern-experience">How can we turn on modern experience&lt;/h2>
&lt;p>Now that we are teased into modern lists and libraries in SharePoint, it&amp;rsquo;s time to turn on modern experiences. We can do it like this in the classic experience:&lt;/p>
&lt;ul>
&lt;li>select &lt;strong>Library Settings&lt;/strong> or &lt;strong>List Settings&lt;/strong> on the ribbon&lt;/li>
&lt;li>select &lt;strong>Advanced settings&lt;/strong> and select &lt;strong>List experience&lt;/strong>&lt;/li>
&lt;li>select &lt;strong>New experience&lt;/strong>&lt;/li>
&lt;li>save with &lt;strong>Ok&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h3 id="how-can-we-change-the-look-and-feel-of-a-list-in-the-ui">How can we change the look and feel of a list in the UI&lt;/h3>
&lt;p>Lists can contain different columns, and each column has a certain column type, depending on the kind of value we want to store in that column like text, numbers, dates, choice, persons, locations, links, or images. As lists can contain much information, its brilliant if we emphasize crucial parts by formatting them in a way that they are&lt;/p>
&lt;ul>
&lt;li>easier to read&lt;/li>
&lt;li>give a grasp on what is going on&lt;/li>
&lt;li>are mobile-friendly&lt;/li>
&lt;/ul>
&lt;p>Already built-in, we will find options to format columns and views. Formatting a view means modifying the way the entire list is displayed. Formatting a column means changing the way this particular column looks like.&lt;/p>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/list-formatting-create.png" width="350">
&lt;h4 id="formatting-views">Formatting Views&lt;/h4>
&lt;ul>
&lt;li>We can change the format view&lt;/li>
&lt;/ul>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/list-formatting-format.png" width="350">
&lt;ul>
&lt;li>and also display a gallery view&lt;/li>
&lt;/ul>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/list-formatting-formatgallery.png" width="350">
&lt;h4 id="formatting-columns">Formatting columns&lt;/h4>
&lt;ul>
&lt;li>If we want to change the column&amp;rsquo;s appearance, we can do that very end-user-friendly directly in the UI:&lt;/li>
&lt;/ul>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/list-formatting-formatcolumns.png" width="350">
* and even with rules like if - then - else:
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/list-formatting-formatrules.png" width="350">
&lt;h3 id="how-can-i-apply-conditional-formatting-aka-rules">How can I apply conditional formatting, aka rules&lt;/h3>
&lt;p>Rules are a powerful feature to determine how a column should look like. Let&amp;rsquo;s say we want to apply a background color depending on a number value. If the number is below 30, the field should be red; between 30 and 70, it should yellow, and above 70, it should be green. Let&amp;rsquo;s see how this looks like:&lt;/p>
&lt;p>&lt;img alt="list formatting in action" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/list-formatting.gif">&lt;/p>
&lt;h3 id="how-can-we-change-the-look-and-feel-of-a-list-with-json">How can we change the look and feel of a list with JSON&lt;/h3>
&lt;p>Sometimes, even if those options are already cool, we need some more flexibility.&lt;/p>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/whatif.jpg" width="350">
&lt;p>There is a way to format both columns and views beyond what is already offered, as seen above. Perhaps you might have noticed the little link &lt;strong>Advanced mode&lt;/strong>? This is where we will find the cool tools to play with!&lt;/p>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/advanced-mode.png" width="350">
&lt;p>This field expects you to put some JSON code in it to format this column. If you never heard about JSON, you can quickly get started with &lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/introduction-to-json/ba-p/2049369">Intro to JSON&lt;/a> by Bob German, &lt;a href="https://www.youtube.com/watch?v=iiADhChRriM">this video&lt;/a>, or you can learn more at &lt;a href="https://www.w3schools.com/js/js_json_intro.asp">w3schools&lt;/a>.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/iiADhChRriM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div>
&lt;h2 id="samples">Samples&lt;/h2>
&lt;p>We will browse to the &lt;a href="https://github.com/pnp/sp-dev-list-formatting">Microsoft 365 PnP List formatting repository on GitHub&lt;/a> and open the folder &lt;code>column samples&lt;/code>. In here, we will find free-to-use samples to make our lists look fantastic.&lt;/p>
&lt;p>Instead of having a list like this:&lt;/p>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/example%20list-BEFORE.png" width="1200">
&lt;p>we can now look at a list like that:&lt;/p>
&lt;img src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/example%20list.png" width="1200">
&lt;h3 id="how-can-we-apply-a-sample">How can we apply a sample?&lt;/h3>
&lt;ul>
&lt;li>Get familiar with the samples that are available on GitHub&lt;/li>
&lt;li>regularly check for new ones / pin the repo&lt;/li>
&lt;li>select the one that is interesting for you&lt;/li>
&lt;li>read the &lt;code>readme.md&lt;/code> file to know the requirements for your column. Some samples only work with choice, text, or number columns&lt;/li>
&lt;li>open the JSON file&lt;/li>
&lt;li>copy the code to your clipboard&lt;/li>
&lt;li>go to your SharePoint list&lt;/li>
&lt;li>go to column settings &amp;ndash;&amp;gt; format this column&lt;/li>
&lt;li>click on &lt;strong>Advanced mode&lt;/strong>&lt;/li>
&lt;li>paste the code&lt;/li>
&lt;li>click &lt;strong>Save&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>&lt;img alt="Use samples from M365 PnP repository" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/formatsplist.gif">&lt;/p>
&lt;p>One more example how amazing a list can look like? I run a the &lt;a href="pyod.shop">PimpYourOwnDevoce.com&lt;/a> sticker store together with &lt;a href="https://www.eliostruyf.com">Elio Struyf&lt;/a> and we use a SharePoint list as our inventory (&lt;a href="https://www.eliostruyf.com/running-online-store-powerplatform-azure/">Elio blogged about the whole architecture here&lt;/a>. With &lt;a href="https://lists.handsontek.net/format-image-column-preview-microsoft-lists-sharepoint/">this sample&lt;/a> we can hover over our images to have a big preview:&lt;/p>
&lt;p>&lt;img alt="pyod inventory list" src="https://raw.githubusercontent.com/LuiseFreese/blog/main/media/listformat-pyod.gif">&lt;/p>
&lt;p>That&amp;rsquo;s it!&lt;/p>
&lt;p>We will find &lt;a href="https://github.com/pnp/sp-dev-list-formatting/tree/master/view-samples">view samples&lt;/a> in the same GitHub repository.&lt;/p>
&lt;h2 id="want-to-learn-more">Want to learn more?&lt;/h2>
&lt;p>You can find helpful resources to learn more here:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://aka.ms/m365pnp">aka.ms/m365pnp&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://techcommunity.microsoft.com/t5/microsoft-365-pnp-blog/">Microsoft 365 PnP Community on TechCommunity&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/pnp/sp-dev-list-formatting">Microsoft 365 PnP List formatting repository on GitHub&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Have fun and happy Modern SharePoint list formatting -
&lt;em>#SharingIsCaring ❤&lt;/em>&lt;/p>
&lt;p>PS: Did you like this post? &lt;a href="https://m365princess.com/how-we-use-sharepoint-list-formatting-and-power-automate-at-pyod-to-ease-our-marketing/">I wrote even more about list formatting&lt;/a>&lt;/p></description></item><item><title>digital clutter and how we use our time</title><link>https://m365princess.com/blogs/2021-01-24-digital-declutter-time/</link><pubDate>Sun, 24 Jan 2021 14:11:36 +0000</pubDate><guid>https://m365princess.com/blogs/2021-01-24-digital-declutter-time/</guid><description>&lt;h1 id="digital-clutter-and-how-we-use-our-time">Digital Clutter and how we use our time&lt;/h1>
&lt;p>Do you sometimes have that feeling of being busy and under pressure for the whole day (or even the entire week) and still couldn&amp;rsquo;t check off all tasks from your list? Do you have the presentiment that although we all focus on productivity, efficiency, and getting the most out of everything, we still scratch the surface and even get in our way with how we structure our workdays? Well, welcome to the club; you are not alone.&lt;/p>
&lt;p>This is the 3rd part of my series about digital declutter - you can catch up with &lt;a href="https://github.com/LuiseFreese/blog/blob/main/digital-clutter-is-evil.md">Part 1 Digital clutter is evil&lt;/a> and &lt;a href="https://github.com/LuiseFreese/blog/blob/main/digital%20declutter-tabs.md">Part 2 digital clutter and your 168 open tabs&lt;/a> before you continue reading.&lt;/p>
&lt;h2 id="focus-time--meetings">focus time &amp;amp; meetings&lt;/h2>
&lt;p>Collaboration is critical; we are better together in achieving goals, and teamwork is our reality. But sometimes, it seems as if we forget that there are not only teamwork-oriented periods but that we also need focus-time to prepare, process, and evaluate content as well as leverage ideas. How often are our days full of back-to-back meetings, how often do sessions overlap? How often do we feel that we do not even have enough time to eat, go to the restroom, get a fresh coffee? Not to mention taking a break or get some fresh air? I know, we are all swamped, our days are all tightly timed, even lunchtime needs to be productive.&lt;/p>
&lt;p>But why do we attend all these meetings without taking the time to capture and condense the insights, process information, and reevaluate decisions? How intense is our commitment towards these meetings&amp;rsquo; goals if they only consume our time, but we don&amp;rsquo;t make time to reflect on them? How do we decide if we should be part of a meeting? Because we&amp;rsquo;ve been invited to it? Because a particular process requires the attendance of the role that I incorporate? Because we noticed a lack of information flow and want to talk with a group of people synchronously? I like to apply the &amp;lsquo;rule of two feet&amp;rsquo; from the Barcamp approach: If I can&amp;rsquo;t learn nor contribute in a meaningful way, I will leave (or not even accept the invite) respectfully - and no one feels upset. Why? Because my presence is not required in all meetings and a good summary sometimes is more valuable than trying to squeeze in another meeting.&lt;/p>
&lt;p>These days, it sometimes feels as I was talking 8 hours straight into my webcam, which is energy-draining not only because I miss meeting people in reality but also because quickly switching contexts and needing to focus on the next bunch of people is exhausting. No time to take a moment and work through what happened in a meeting makes many meetings pointless. Good meetings add value to our work, and we can add value to other&amp;rsquo;s work. And: in good discussions, every attendee is present.&lt;/p>
&lt;h3 id="if-you-zone-out-leave">If you zone out, leave&lt;/h3>
&lt;p>Heard your colleagues saying: &amp;ldquo;I will mute myself because I need to work on something aside, but I still listen with one ear&amp;rdquo;? Please ask them to leave in these cases. Multitasking is an illusion, and if some participants can&amp;rsquo;t fully commit to contribute, it&amp;rsquo;s instead a waste of time than adding value in a meaningful way.&lt;/p>
&lt;p>Sometimes, people zone out not on purpose; it just happens. This is a coping mechanism to deal with information overload. Meetings all day long, all week without time for ourselves result in our minds going elsewhere, which again means that we joined a meeting but are not present in the moment.&lt;/p>
&lt;h3 id="throwing-tech-solutions-on-people-problems">Throwing Tech solutions on people problems&lt;/h3>
&lt;p>Working in tech and loving all things Microsoft 365 would be easy to search for technical solutions to solve these issues. And of course, some features assist us to maintain a healthy work behavior:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>notification in Microsoft Teams that the meeting will end in 5 minutes, to make everyone aware, to not go over time&lt;/p>
&lt;/li>
&lt;li>
&lt;p>setting in Outlook to end meetings a couple of minutes earlier by default so that we at least have time to grab a coffee.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="the-real-issue">the real issue&lt;/h3>
&lt;p>But as much as I appreciate a hot coffee, these 5 minutes in between are more cover the problem that we don&amp;rsquo;t take time to reflect on and force every colleague to come up with a workaround how to solve that instead of working on an organization-wide healthy strategy how to avoid mental overload and aim for a corporate culture in which having focus time is as important as collaboration time.&lt;/p>
&lt;h3 id="dont-accept-meetings-by-default">Don&amp;rsquo;t accept meetings by default&lt;/h3>
&lt;p>To determine if you should attend a meeting and then also commit to be present, you can ask yourself:&lt;/p>
&lt;ul>
&lt;li>What&amp;rsquo;s the meeting&amp;rsquo;s purpose?&lt;/li>
&lt;li>What value can you can add?&lt;/li>
&lt;li>What is the most critical information you want to deliver, and what more do you like to contribute?&lt;/li>
&lt;li>Do you have an essential role to play?&lt;/li>
&lt;li>What exactly do you hope to learn from the call?&lt;/li>
&lt;li>Which specific questions do you have?&lt;/li>
&lt;/ul>
&lt;p>Once you can answer these questions, even jot down your answers, you can more easily make a better decision whether to attend or to decline and still be prepared to continue collaboration. Taking time to clarify intent is a step that we should consider to do before taking action.&lt;/p>
&lt;h2 id="structure-your-work">structure your work&lt;/h2>
&lt;p>The best life advice I ever became, was from the flight attendant in any plane: &amp;ldquo;Put your own oxygen mask before you help others.&amp;rdquo; Some suggestions:&lt;/p>
&lt;ul>
&lt;li>No back to back meetings, schedule half an hour upfront and after a meeting to prepare/postprocess to reflect and note down which impact the content of the meeting has on your work and about which implications you need to think&lt;/li>
&lt;li>Beyond that, schedule focus time. I use &lt;a href="https://calendar.help">Cortana&lt;/a> for that and I never postpone my focus time to squeeze in a another meeting.&lt;/li>
&lt;li>Don&amp;rsquo;t read or process your emails as the first thing you do in the morning, but reserve a slot for processing mails. To enjoy my inbox zero, &lt;a href="https://m365princess.com/how-to-stay-organized-with-your-emails-in-outlook-like-a-rockstar/">I follow this process&lt;/a>&lt;/li>
&lt;li>schedule your breaks. This is even more important when working from home.&lt;/li>
&lt;/ul>
&lt;p>If we first schedule breaks, processing time and focus time to actually get work done, and then see, if meetings fit in our calendar as well, we will establish more healthy working habits as if we want to be everywhere and block ourselves because we can&amp;rsquo;t get anything done anymore. My post on &lt;a href="https://m365princess.com/how-to-avoid-overcommitting/">how to avoid overcommitting&lt;/a> relates to this.&lt;/p>
&lt;h2 id="what-does-this-mean-in-the-context-of-this-series-to-digital-declutter">What does this mean in the context of this series to digital declutter?&lt;/h2>
&lt;p>Conscious decisions on how to spend our time will improve our focus and minimize distractions. It&amp;rsquo;s of course not the goal to exclude ourselves from collaboration or all meetings, but once meetings and a fragmented calendar eat up our time, we will need to find balance and ways to protect our productivity. Awareness of our goals and of which commitments will work towards them can help us to select those meetings which are important to us in the context of a shared vision with our coworkers and teammembers.&lt;/p>
&lt;h3 id="how-to-start-and-whats-next">How to start and what&amp;rsquo;s next&lt;/h3>
&lt;ul>
&lt;li>Start to elaborate on your personal goals and the goals of your team&lt;/li>
&lt;li>work out, which committments work towards these goals&lt;/li>
&lt;li>go over your calendar and get rid of jour fixes and similar events, that don&amp;rsquo;t support these goals&lt;/li>
&lt;li>eliminate as well meetings that are usually poorly prepared , lack of an agenda or tend to go over time&lt;/li>
&lt;/ul>
&lt;p>What are your strategies to feel connected and get work done? How do you plan your days, weeks, months? Or do you more have that feeling, that other plan work for you? How do you perceive that in terms of sovereignty and being in control and charge? Curious to learn what you think, please let me know.&lt;/p></description></item><item><title>Templatize a Collaboration Contract workshop and provision teams that organizations will love</title><link>https://m365princess.com/blogs/2021-01-11-templatize-a-collaboration-contract-workshop-and-provision-teams-that-organizations-will-love/</link><pubDate>Mon, 11 Jan 2021 21:52:40 +0000</pubDate><guid>https://m365princess.com/blogs/2021-01-11-templatize-a-collaboration-contract-workshop-and-provision-teams-that-organizations-will-love/</guid><description>&lt;p>##&lt;img loading="lazy" class="wp-image-570 alignnone" src="https://m365princess.com/wp-content/uploads/2021/01/lego-team-300x167.png" alt="" width="553" height="308" srcset="https://m365princess.com/wp-content/uploads/2021/01/lego-team-300x167.png 300w, https://m365princess.com/wp-content/uploads/2021/01/lego-team-1024x571.png 1024w, https://m365princess.com/wp-content/uploads/2021/01/lego-team-768x428.png 768w, https://m365princess.com/wp-content/uploads/2021/01/lego-team.png 1280w" sizes="(max-width: 553px) 100vw, 553px" />&lt;/p>
&lt;h2 id="intro">intro&lt;/h2>
&lt;p>In my day to day life as a consultant, my customers need a lot of guidance on HOW to work with Microsoft Teams. After I educated them in what is Teams and everything in Microsoft 365 is interwoven, they ask:&lt;/p>
&lt;ul>
&lt;li>which channels do we need?&lt;/li>
&lt;li>which apps and services should we pin in our channels?&lt;/li>
&lt;li>how many libraries do we need in SharePoint? And with which additional columns?&lt;/li>
&lt;li>how about lists?&lt;/li>
&lt;li>and buckets in Planner?&lt;/li>
&lt;li>who should be a Teams Owner?&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://github.com/LuiseFreese/blog/blob/main/media/itdepends.png" target="_blank" rel="noopener noreferrer">&lt;img loading="lazy" class="alignnone" src="https://github.com/LuiseFreese/blog/raw/main/media/itdepends.png" alt="it depends" width="550" height="408" />&lt;/a>&lt;/p>
&lt;p>My answer, as a consultant, will always be a “it depends”. It depends on their objectives. To get them going, we would do a workshop to define their collaboration contract. In this workshop, I would provide them with knowledge about tools and working behavior in Microsoft 365 and then give them some options what to do. We would write down everything and then someone would need to not only create the team and invite the members, but also:&lt;/p>
&lt;ul>
&lt;li>create channels&lt;/li>
&lt;li>create additional libraries and lists&lt;/li>
&lt;li>create columns&lt;/li>
&lt;li>create views&lt;/li>
&lt;li>pin the libraries to the teams channel they belong to&lt;/li>
&lt;li>create buckets in the Planner board&lt;/li>
&lt;li>pin learning resources for beginners into the channel general&lt;/li>
&lt;li>pin the collaboration contract&lt;/li>
&lt;li>delete the wiki tab&lt;/li>
&lt;/ul>
&lt;p>These workshops are very effective, as people&lt;/p>
&lt;ul>
&lt;li>gain knowledge&lt;/li>
&lt;li>reflect on their working behavior&lt;/li>
&lt;li>are enabled to make better decisions&lt;/li>
&lt;/ul>
&lt;p>But those workshops are not efficient as well, as they&lt;/p>
&lt;ul>
&lt;li>are time consuming&lt;/li>
&lt;li>don’t scale&lt;/li>
&lt;li>are expensive&lt;/li>
&lt;/ul>
&lt;p>Also, I got tired of asking and answering the same questions over and over again. Especially in this pandemic-driven world, which worked as an accelerator on Teams rollouts, I just got bored. And whenever I get bored to do a certain thing, I will automate it.&lt;/p>
&lt;p>This blog post is about a work in progress.&lt;/p>
&lt;h2 id="a-iduser-content-solution--team-up-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormdsolution--team-up-aria-hiddentrueasolution--team-up">&lt;a id="user-content-solution--team-up" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#solution--team-up" aria-hidden="true">&lt;/a>solution &amp;amp; team up&lt;/h2>
&lt;p>My idea was to templatize those workshops as an app. The app should:&lt;/p>
&lt;ul>
&lt;li>educate users about everything they should know to make decision that we would typically do in a collaboration contract&lt;/li>
&lt;li>get user’s input on channel names, team members, metadata etc.&lt;/li>
&lt;li>store all inputs in a list (SharePoint)&lt;/li>
&lt;/ul>
&lt;p>(You can also use a table in Dataverse for Teams or even an Excel Table (although I wouldn’t recommend it)).&lt;/p>
&lt;p>To keep the app simple and adjustable I want to keep the whole logic in a flow (either Power Automate or Azure Logic Apps). For that, I teamed up with a rising star in the community, &lt;a href="https://twitter.com/CarmenYsewijn" rel="nofollow">Carmen Ysewijn&lt;/a>. Carmen is a PowerPlatform Architect working at &lt;a href="https://www.qubix.be/" rel="nofollow">Qubix&lt;/a>, Belgium and I met her last year in Warsaw at a Power Platform conference. I was more than impressed by her conciseness and #learnitall mindset. Carmen will help to transition my Power Automate flow into Azure Logic Apps – she wanted to learn something new and we thought that an open source project would be a good idea to contribute to.&lt;/p>
&lt;h2 id="a-iduser-content-a-simple-powerapp-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormda-simple-powerapp-aria-hiddentrueaa-simple-powerapp">&lt;a id="user-content-a-simple-powerapp" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#a-simple-powerapp" aria-hidden="true">&lt;/a>a simple PowerApp&lt;/h2>
&lt;p>&lt;a href="https://github.com/LuiseFreese/blog/blob/main/media/TA-home.png" target="_blank" rel="noopener noreferrer">&lt;img loading="lazy" class="alignnone" title="Purpose of this app is guide users through the art of teamwork and proviosion the team of their dreams for them" src="https://github.com/LuiseFreese/blog/raw/main/media/TA-home.png" alt="Home screen of Teams Advisor App" width="877" height="492" />&lt;/a>&lt;/p>
&lt;p>Our Canvas App should be a simple interface to provide knowledge and get user’s input, which is why we will have info-screens, which will display knowledge around a certain topic, like “What are channels” and process-screens, which will ask users with a form, what they want, like private or standard channels and which apps and sites and files should already be pinned to these channels.&lt;/p>
&lt;p>As this is a longer process we also want&lt;/p>
&lt;ul>
&lt;li>to guide users so that they know at which point of the app they are right now&lt;/li>
&lt;li>them to be able to go back and forth between screens and edit what they already put in&lt;/li>
&lt;li>show them a log which input they already gave&lt;/li>
&lt;/ul>
&lt;p>This makes sure that users stay motivated to proceed in the app. This gif illustrates what users will experience:&lt;/p>
&lt;p>&lt;a href="https://github.com/LuiseFreese/blog/blob/main/media/TA-decision.gif" target="_blank" rel="noopener noreferrer">&lt;img loading="lazy" class="alignnone" title="Guiding users through knowledge and decisions when it comes to create the teams of their dreams" src="https://github.com/LuiseFreese/blog/raw/main/media/TA-decision.gif" alt="first steps of TeamsAdvisor App" width="879" height="491" />&lt;/a>&lt;/p>
&lt;p>For each thing, we need to have a decision, I will follow these steps:&lt;/p>
&lt;ol>
&lt;li>provide knowledge&lt;/li>
&lt;li>ask users in a form what they want to do&lt;/li>
&lt;li>store input in variables&lt;/li>
&lt;/ol>
&lt;p>At the end of the whole process, I will update my SharePoint with a new item.&lt;/p>
&lt;h2 id="a-iduser-content-a-complex-system-of-flows-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormda-complex-system-of-flows-aria-hiddentrueaa-complex-system-of-flows">&lt;a id="user-content-a-complex-system-of-flows" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#a-complex-system-of-flows" aria-hidden="true">&lt;/a>a complex system of flows&lt;/h2>
&lt;p>Now that I described the very straightforward app, it’s time to have look at the logic. For my minimal lovable ♥ product, I decided to use Power Automate, but as already said in the intro, the goal is to transition to Azure Logic Apps.&lt;/p>
&lt;h3 id="a-iduser-content-power-automate-or-azure-logic-apps-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormdpower-automate-or-azure-logic-apps-aria-hiddentrueapower-automate-or-azure-logic-apps">&lt;a id="user-content-power-automate-or-azure-logic-apps" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#power-automate-or-azure-logic-apps" aria-hidden="true">&lt;/a>Power Automate or Azure Logic Apps?&lt;/h3>
&lt;p>Both systems have their pros and cons, but as much as it it easy to automate your personal work challenges in Power Automate, you shouldn’t use it for organizational, business critical processes. Power Automate flows run in the context of a user, which means that we have the same challenges with files stored in OneDrive. People get different jobroles, leave the company or simply abandon this lovely pet project (which outgrew the sideproject level by far and should have been handed over to someone who can take better care of it). Also, we have better options to have the run history of a Logic App.&lt;/p>
&lt;p>Carmen had the very neat idea to split the huge flow (with 20+ API calls) into one parent flow and several child flows to make it easier for everyone to exactly pick what they need.&lt;/p>
&lt;p>This is only an exerpt of the flow to give you an idea that for most of the things I wanted to do, there are no predefined actions in Power Automate (nor in Logic Apps) but that we needed to define our http calls:&lt;/p>
&lt;p>&lt;a href="https://github.com/LuiseFreese/blog/blob/main/media/TA-flow.png" target="_blank" rel="noopener noreferrer">&lt;img loading="lazy" class="alignnone" title="a lot of https actions in a flow can make it a little confusing" src="https://github.com/LuiseFreese/blog/raw/main/media/TA-flow.png" alt="exerpt of the flow showing some http actions" width="846" height="680" />&lt;/a>&lt;/p>
&lt;h3 id="a-iduser-content-http-request-to-sharepoint-or-graph-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormdhttp-request-to-sharepoint-or-graph-aria-hiddentrueahttp-request-to-sharepoint-or-graph">&lt;a id="user-content-http-request-to-sharepoint-or-graph" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#http-request-to-sharepoint-or-graph" aria-hidden="true">&lt;/a>HTTP request to SharePoint or Graph?&lt;/h3>
&lt;p>In our minimal lovable product, we use the SharePoint list item creation as a trigger and create channels, libraries, columns, views as requested by our user. I worked with both “send an HTTP request to SharePoint” actions and HTTP action towards Graph API (which I find better documented). The advantage of the HTTP request to SharePoint is, that you do not need to register an app in Azure AD, but as we need that for all calls via Graph, this is not an argument anymore.&lt;/p>
&lt;h3 id="a-iduser-content-in-complex-flows-follow-these-steps-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormdin-complex-flows-follow-these-steps-aria-hiddentrueain-complex-flows-follow-these-steps">&lt;a id="user-content-in-complex-flows-follow-these-steps" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#in-complex-flows-follow-these-steps" aria-hidden="true">&lt;/a>in complex flows, follow these steps&lt;/h3>
&lt;ul>
&lt;li>Read the docs. Seriously.&lt;/li>
&lt;li>Try out your call in Graph Explorer. In case you don’t know what this is, check out here:&lt;/li>
&lt;li>Try in a test flow&lt;/li>
&lt;li>if it doesn’t work, write a test protocol&lt;/li>
&lt;/ul>
&lt;h2 id="a-iduser-content-next-steps--open-souce-approach-classanchor-hrefhttpsgithubcomluisefreeseblogblobmainteamsadvisormdnext-steps--open-souce-approach-aria-hiddentrueanext-steps--open-source-approach">&lt;a id="user-content-next-steps--open-souce-approach" class="anchor" href="https://github.com/LuiseFreese/blog/blob/main/teamsadvisor.md#next-steps--open-souce-approach" aria-hidden="true">&lt;/a>Next steps &amp;amp; Open Source approach&lt;/h2>
&lt;p>Next steps to evolve from a minimal lovable product to a good solution?&lt;/p>
&lt;ul>
&lt;li>Put some more work into the UI of the app&lt;/li>
&lt;li>finish the Azure Logic Apps part&lt;/li>
&lt;/ul>
&lt;p>As we want to open source this solution, we will&lt;/p>
&lt;ul>
&lt;li>export app and flows&lt;/li>
&lt;li>document everything and share it on GitHub&lt;/li>
&lt;/ul>
&lt;p>This way, everyone can use it, share it, learn from it, and adapt it to their needs. We are open for all kind of feedback from you!&lt;/p>
&lt;h2 id="call-to-action">Call to action&lt;/h2>
&lt;ol>
&lt;li>follow &lt;a href="https://twitter.com/CarmenYsewijn" rel="nofollow">Carmen Ysewijn&lt;/a> and myself on twitter&lt;/li>
&lt;li>stay tuned, and give us feedback, we highly appreciate it!&lt;/li>
&lt;li>If you want to watch me explaining the solution, you can do this &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/blogs/microsoft-365-pnp-weekly-episode-109/" target="_blank" rel="noopener">in episode 109 of PnPWeekly&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>#EverythingIsAwesome 🙂&lt;/p></description></item><item><title>#digitalclutter and our 168 open tabs -Part 2 of #LetItGo series</title><link>https://m365princess.com/blogs/2021-01-06-digitalclutter-and-our-168-open-tabs-part-2-of-letitgo-series/</link><pubDate>Wed, 06 Jan 2021 21:24:49 +0000</pubDate><guid>https://m365princess.com/blogs/2021-01-06-digitalclutter-and-our-168-open-tabs-part-2-of-letitgo-series/</guid><description>&lt;p>Ok, we made it to Jumanji, Part 2, Level 1. By the time I write this, it’s still early January 2021 (and this is the first time I wrote 2021 ), and I promised to write a series about how to declutter your life and work digitally. This is the second part; if you need to catch up on WHY we keep digital stuff and why it is so difficult (but also most rewarding) to let digital things go, you can read the first part here: &lt;a href="https://m365princess.com/digital-clutter-is-evil/">https://m365princess.com/digital-clutter-is-evil/&lt;/a>&lt;/p>
&lt;p>This part is about the tabs in your browser. A lot of our work these days takes place on websites, which means that we live in our browsers and open tabs over tabs to research, procrastinate, get work done, distract ourselves, contact information, log into different platforms, search for stuff we searched already 96 times before and so on. This results in *SOME* open tabs.&lt;/p>
&lt;p> &lt;/p>
&lt;img loading="lazy" class="alignnone wp-image-552 size-full" src="https://m365princess.com/wp-content/uploads/2021/01/mybrainhastoomanytabsopen.png" alt="" width="607" height="436" srcset="https://m365princess.com/wp-content/uploads/2021/01/mybrainhastoomanytabsopen.png 607w, https://m365princess.com/wp-content/uploads/2021/01/mybrainhastoomanytabsopen-300x215.png 300w" sizes="(max-width: 607px) 100vw, 607px" />
&lt;h2 id="ok-but-what-does-it-matter">Ok, but what does it matter?&lt;/h2>
&lt;p>Why should we care if we have 4 or 40 tabs open? Because whatever is open, unresolved, undecided, pending or active, keeps our minds busy. It increases our mental load and sets us under stress, even if we are not aware of it. If we deal with overload for a more extended period, we perceive it as overwhelming as our normality, which means that we drive with the handbrake.&lt;/p>
&lt;p>Don’t we all&lt;/p>
&lt;ul>
&lt;li>rely on all these open tabs?&lt;/li>
&lt;li>buy decent machines with a tremendous amount of RAM so that they don’t overheat?&lt;/li>
&lt;li>love those posts about mental health and letting go and digital detox?&lt;/li>
&lt;/ul>
&lt;p> &lt;/p>
&lt;p>Oh right, the latter was just about me 🙂 Let’s have a look into this working habit of having A LOT of tabs open. Some categories would help us first see what they are to determine if we need them, harm us, or only a better solution to achieve the same goal. In my day-to-day life as a consultant, I never focus on tools but use cases. Let’s try this approach here as well:&lt;/p>
&lt;h3 id="tabs-we-have-open-while-bloggingresearch">tabs we have open while blogging/research&lt;/h3>
&lt;p>When I researched for and wrote this blog post, I had these tabs open:&lt;/p>
&lt;ol>
&lt;li>my website m365princess.com, to be able to refer to older blog posts in this blog post easily&lt;/li>
&lt;li>GitHub, as I write my posts in GitHub these days&lt;/li>
&lt;li>Twitter (to be able to make a poll and search what others already said about ‘too many tabs open’ )&lt;/li>
&lt;li>one other tab to do research, like searching for the right image or a quote or googling a fact I can’t remember anymore.&lt;/li>
&lt;/ol>
&lt;p>It turns out that I always use this set of tabs when I write a blog post.&lt;/p>
&lt;h3 id="tabs-we-have-open-to-learn">tabs we have open to learn&lt;/h3>
&lt;p>Well, learning, of course, is a good thing. When I learn, I open&lt;/p>
&lt;p> &lt;/p>
&lt;ol>
&lt;li>the content site (YouTube, MSLearn, Pluralsight, etc. )&lt;/li>
&lt;li>a website to try out what I learn (Power Platform, Graph Explorer, Azure Portal)&lt;/li>
&lt;li>Twitter to quickly ask questions or express my moments&lt;/li>
&lt;li>one other tab to research, like googling when I don’t know a specific thing that seems to be mandatory to understand the tutorial I am following&lt;/li>
&lt;/ol>
&lt;h3 id="tabs-we-have-open-for-bug-fixing">tabs we have open for bug fixing&lt;/h3>
&lt;p>I think this tweet explains everything I could write in a whole paragraph.&lt;/p>
&lt;p> &lt;/p>
&lt;img loading="lazy" class="wp-image-550 alignnone" src="https://m365princess.com/wp-content/uploads/2021/01/bugfix-300x102.png" alt="" width="556" height="189" srcset="https://m365princess.com/wp-content/uploads/2021/01/bugfix-300x102.png 300w, https://m365princess.com/wp-content/uploads/2021/01/bugfix-1024x349.png 1024w, https://m365princess.com/wp-content/uploads/2021/01/bugfix-768x262.png 768w, https://m365princess.com/wp-content/uploads/2021/01/bugfix.png 1133w" sizes="(max-width: 556px) 100vw, 556px" />
&lt;h3 id="tabs-we-have-open-because-we-stumbled-upon-them-and-might-need-them-later">tabs we have open because we stumbled upon them and *might* need them later&lt;/h3>
&lt;p> &lt;/p>
&lt;p>Well, we are now entering the *just in case* phase. While we were browsing, researching, and looking for inspiration, we found a site that would be helpful- later. We can’t take advantage of it right now (because we need to focus on that other thing we need to get done eventually, but perhaps, in the future, we will get back to that site). We keep it, just in case. Do we recognize that this is the same thing as keeping other digital stuff? Do we realize that even keeping this tab open will keep at least a part of your mind busy? These open tabs distract us, similar to people interrupting our focus work. Does this sound familiar to you?&lt;/p>
&lt;img loading="lazy" class="wp-image-551 alignnone" src="https://m365princess.com/wp-content/uploads/2021/01/focus-197x300.png" alt="" width="422" height="643" srcset="https://m365princess.com/wp-content/uploads/2021/01/focus-197x300.png 197w, https://m365princess.com/wp-content/uploads/2021/01/focus-673x1024.png 673w, https://m365princess.com/wp-content/uploads/2021/01/focus.png 701w" sizes="(max-width: 422px) 100vw, 422px" />
&lt;p>I get it; we want to keep this URL, but we do not necessarily have this tab open.&lt;/p>
&lt;h2 id="but-what-would-be-a-better-solution">But what would be a better solution?&lt;/h2>
&lt;p>Before we can throw tech solutions on people problems, let’s investigate what happens:&lt;/p>
&lt;ol>
&lt;li>Event: On top, there is the event that is visible to us and attentive to others. In our case:  &lt;em>We deal with a tremendous mental load and are easily distracted&lt;/em>&lt;/li>
&lt;li>Pattern and trends: underneath the visible event, we could look into the patterns and trends that lead to these events. Here that would be: &lt;em>the more different workloads I need to manage, the more overwhelmed I am&lt;/em>&lt;/li>
&lt;li>System that supports these patterns: Let’s now have a look into the system: &lt;em>We need to react quickly to whatever happens, and this reactive working mode puts us in a kind of emergency/attention position which also causes stress and leads to more ineffective work styles&lt;/em>&lt;/li>
&lt;li>What is the common belief that holds this system in place: If we now dive deeper into our beliefs, we will conclude that although we are praying that “always-on” is not a solution, that mental health is essential, that it is crucial to set boundaries and that you should have your focus work time actually to get stuff done, we merely don’t act like that. We reply on emails and messages almost immediately, we have all websites always open to quickly jump to other tasks and put tremendous pressure on us with that. But: &lt;em>_Busy ain’t productive!&lt;/em>_&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="alignnone wp-image-548 " src="https://m365princess.com/wp-content/uploads/2021/01/busyaintproductive.png" alt="" width="528" height="523" srcset="https://m365princess.com/wp-content/uploads/2021/01/busyaintproductive.png 899w, https://m365princess.com/wp-content/uploads/2021/01/busyaintproductive-300x297.png 300w, https://m365princess.com/wp-content/uploads/2021/01/busyaintproductive-150x150.png 150w, https://m365princess.com/wp-content/uploads/2021/01/busyaintproductive-768x760.png 768w" sizes="(max-width: 528px) 100vw, 528px" />
&lt;p>If we now consciously think about how we want to work (and let’s assume we want more “productive” and less “busy”), we will have some principles:&lt;/p>
&lt;ul>
&lt;li>we will only have a few priorities&lt;/li>
&lt;li>we will distinguish between our different hats/workloads/use cases&lt;/li>
&lt;li>we will not try to multitask nor to quickly task-switch&lt;/li>
&lt;li>we will be conscious about our commitments so that we can still handle our workload&lt;/li>
&lt;/ul>
&lt;p>If we now only have a few priorities, we will be more easily able to let stuff go, or in this case: close a tab, even if it MIGHT be a useful resource for later. We will learn to focus on the essential things.&lt;/p>
&lt;p>We will use smart tech to group the tabs that we need for the different workloads, which will also prevent us from multitasking:&lt;/p>
&lt;p>I work with Microsoft Edge browser, and I love collections. If you don’t know what a group is, here is the quick intro: A collection is a set of websites that can be opened in different browser windows.&lt;/p>
&lt;p> &lt;/p>
&lt;p>Once you are done with a workload represented by this collection, you close the whole window. When I need to explain collections on a non-technical level, I always say: These are desks with all the working material you need to accomplish a specific task or group of tasks (similar to a Microsoft Teams channel). I have many collections, but for the sake of an example: one is a collection for managing the &lt;a href="http://pyod.shop">sticker-store&lt;/a>, that I run together with &lt;a href="http://eliostruyf.com">Elio Struyf&lt;/a>.&lt;/p>
&lt;ul>
&lt;li>newsletter tool&lt;/li>
&lt;li>SharePoint site&lt;/li>
&lt;li>order management tool&lt;/li>
&lt;li>shop itself&lt;/li>
&lt;li>social media tool&lt;/li>
&lt;/ul>
&lt;p> &lt;/p>
&lt;p>Why I organize my workloads in collections? So that I can consciously decide which kind of task I want to spend time on instead of chaotically jump from task to task and from tab to tab. The consciousness is the crucial part of that. I work with Microsoft To Do for my personal task management, and I commit each morning to just a few tasks. During the day, I will only open those tabs (organized in collections) to accomplish the task. Whenever I randomly open other sites, I can be sure that I am procrastinating what I should do right now and take a break.&lt;/p>
&lt;p> &lt;/p>
&lt;img loading="lazy" class="alignnone wp-image-553 " src="https://m365princess.com/wp-content/uploads/2021/01/tomorrow.png" alt="" width="529" height="466" srcset="https://m365princess.com/wp-content/uploads/2021/01/tomorrow.png 726w, https://m365princess.com/wp-content/uploads/2021/01/tomorrow-300x264.png 300w" sizes="(max-width: 529px) 100vw, 529px" />
&lt;p> &lt;/p>
&lt;p>Two more cool things about Edge collections: They are stored in the user profile and sync – when you are logged in – across your devices. If you now combine this with the multiple profiles feature you will turn your browser into a focus-machine! As a consultant, I work in different tenants of my customers and profiles make it easy for me to switch between them. When I now sync the collections in my profiles to my phone or second laptop, I can really work seamlessly.&lt;/p>
&lt;p>When I stumble upon a site that MIGHT be interesting, I put it into a collection “review” and I have a weekly task on reviewing that collection. Nothing is allowed to stay there: either the site becomes a valuable content resource – then I will store it in another collection, or I can let it go. This ensures, that I don’t hoard websites just because I can.&lt;/p>
&lt;h2 id="conclusion--what8217s-next">Conclusion &amp;amp; What’s next&lt;/h2>
&lt;p>There is no short tour to let stuff go, but we can use tech to stay focused, prioritize easier and be productive instead of just busy. I would love to hear how you manage your tabs and thought and digital clutter. As this is part 2 of a series about digital clutter, please stay tuned for the next part!&lt;/p>
&lt;p> &lt;/p></description></item><item><title>Let it go – digital clutter</title><link>https://m365princess.com/digital-clutter-is-evil/</link><pubDate>Sun, 27 Dec 2020 18:18:03 +0000</pubDate><guid>https://m365princess.com/digital-clutter-is-evil/</guid><description>&lt;p>Apart from dresses and shoes, I don’t like to own much stuff; I prefer to have only things that relate to who I am as a person, and I can easily detach emotionally and get rid of things.&lt;/p>
&lt;p>Reading and watching &lt;em>Fight Club&lt;/em> was one of my eye-openers here, and I can never forget the very famous quote:&lt;/p>
&lt;p>&lt;em>The things you own end up owning you&lt;/em>&lt;/p>
&lt;p>By no means am I a minimalist; I don’t count my things, nor do I raise the question, if a pair of socks is 1 or 2 items or if a complete drumset is 1 or 21 or 68 items. I do not find value in those discussions. But I think it’s worth investigating the impact of things in my life. And when I do so, I realize that apart from the things I have and heavily use, there is also clutter, AKA &lt;em>stuff&lt;/em>.&lt;/p>
&lt;h2 id="what-is-_stuff_">What is &lt;em>stuff&lt;/em>?&lt;/h2>
&lt;p>Stuff is every item that you keep for the sake of having it. Perhaps it served a purpose when you bought it, found it, or when someone gave it to you, but now you don’t have any use for it any more than just owning it. Some examples?&lt;/p>
&lt;ul>
&lt;li>the ugly mug that was a present from a college friend – you keep it because it reminds you of that friend. Last contact? 14 years ago.&lt;/li>
&lt;li>the nice mug that doesn’t fit all your other mugs, and you stopped drinking coffee – last use of that mug? Well, you first needed to dust it off…&lt;/li>
&lt;li>the books you bought that only serve as a credibility bookcase for your Zoom calls&lt;/li>
&lt;/ul>
&lt;p>This list could be a lot longer, but I will leave it by that as I think you got the idea of what I am talking about.&lt;/p>
&lt;h2 id="negative-impact-of-hoarding">negative impact of hoarding&lt;/h2>
&lt;p>Technically, we know that clutter isn’t good for us. Clutter, both analog and digital, keeps our space, money, energy, and focus. It draws our attention from the things we want to DO to the things we HAVE. While a bit more space makes up room for new experiences and where we want to grow, a full space makes change and growth impossible as all areas are already occupied. Clutter shifts our drive from acting and growing to having and hoarding. And this represents living in the past or unrealistic mental images of yourself:&lt;/p>
&lt;ul>
&lt;li>You own a lot of sports gear but don’t exercise with them regularly? Then you just fell in love with the &lt;em>idea&lt;/em> of being a sporty person, but you aren’t sporty.&lt;/li>
&lt;li>You have a whole collection of musical instruments but never play them? Then it is more your idea of being a musician that you like to become a reality than it represents who you are.&lt;/li>
&lt;li>Is your bookshelf full of books you never read? Well, still a great scene for your virtual meetings, but other than that: You fill up space in your surroundings to fill the void inside of you.&lt;/li>
&lt;/ul>
&lt;p>As clutter takes much space, there is less space for the things you love, which truly represents who you are. Why do we give things that don’t relate to our lives so much room?&lt;/p>
&lt;h2 id="why-do-we-keep-everything">Why do we keep everything?&lt;/h2>
&lt;p>Just in case. In case of what? In case we need it.&lt;/p>
&lt;p>Although most of us grew up in the great luck of not having to experience any deficiency or lack of things they desperately needed; still we were raised with some beliefs, like ‘keeping stuff even for unlikely events is a good thing.’ We even have dedicated rooms in our houses for ‘stuff that we don’t regularly use’ like attics and basements. Storage room seems to be a significant advantage, even bigger than having room for yourself. All this represents your past, which you still want to hold in your hands. I am not talking about precious memories like family photos or practical things you need and use a few times a year (like a waffle maker or a drill). I am talking about the stuff we keep because something in us isn’t ready to let it go: We keep stuff:&lt;/p>
&lt;ul>
&lt;li>for ourselves (in case I ever fit again into that jeans from 1992) –&amp;gt; represents an unrealistic mental image of yourself and shows that you didn’t let go of what’s already past&lt;/li>
&lt;li>for others (these baby clothes will someday be for a kid in my extended family) –&amp;gt; projection&lt;/li>
&lt;li>when we can’t admit that we failed in doing something (in case I restart to play chess which I lastly did in 1992 (you know, when that jeans fit))&lt;/li>
&lt;li>to remind ourselves that we didn’t always suck but also achieved some cool things (keeping that trophy of the 3rd place at the Junior District Championships)&lt;/li>
&lt;li>because we fear deficiency&lt;/li>
&lt;li>to impress others (bookshelf/zoom calls)&lt;/li>
&lt;li>to impress ourselves (mostly related to things we would like to do but don’t execute)&lt;/li>
&lt;/ul>
&lt;p>And yet again, a &lt;em>Fight Club&lt;/em> quote comes to my mind: &lt;em>We buy things we don’t need with money. We don’t have to impress people we don’t like.&lt;/em>&lt;/p>
&lt;p>You will probably say: But I don’t care about the stuff in the basement, about the things I keep for others, or about the 691 little things in drawers (your cable box? your lonely socks? your old phones?). Then I need to tell you that&lt;/p>
&lt;ol>
&lt;li>you just learned to ignore stuff, but it still impacts you whether you like to admit this or not&lt;/li>
&lt;li>whatever you ignore will somewhen return and miscue you, so it’s better if you care.&lt;/li>
&lt;/ol>
&lt;h2 id="the-perverted-approach">the perverted approach&lt;/h2>
&lt;p>Our idea is no longer ‘having something to do something with it,’ but ‘having something until it bothers me so much that I need to get rid of it.’ And we do a lot not to get bothered. We buy new shelves, new drawers. And then we buy decorations because the new stand looks so empty. And then we buy another rack, because all the decoration doesn’t fit anymore. And then we move into a bigger house. And we need a bigger basement, because of the stuff we need to outsource, and then we rent a storage room outside of the city… Or we build a carport, so that more stuff fits into the garage after the car moved out of it. Even if you didn’t need to buy new stuff to give other stuff a place to be, you would see that stuff seeks your time and attention. Every item needs a place to belong to (or you will be busy putting it from left to right and from right to left again), and it needs to be cleaned and maintained. Having a thing is always a commitment. Before I buy something, I do not only think if I can afford this, but how much time it will take me to act according to its purpose. Buying a book is easy, but how likely will I spend 10-20 hours in the next three months on reading it?&lt;/p>
&lt;h2 id="digital-stuff">digital stuff&lt;/h2>
&lt;p>This brings me to some thoughts about stuff that doesn’t take much physical space but still increases our mental load. Enrolling a new course to learn something? &lt;em>Wow, it’s free. CLICK HERE. Yes, subscribe to the newsletter.&lt;/em> How likely is it that I will not only not read this newsletter but also defer unsubscribing from it? How often will this draw my attention while it’s already hard for me to stay focused? How likely will I not just watch the intro of the new course? And how probably will this book, course, podcast end up in that &lt;em>pile of guilt&lt;/em>? There are more examples:&lt;/p>
&lt;ul>
&lt;li>the app you never use&lt;/li>
&lt;li>the accounts that announce their blog posts on Twitter, I do not remember you finding time to read&lt;/li>
&lt;li>your 42 open tabs (but!)&lt;/li>
&lt;li>16 variations of the same photo&lt;/li>
&lt;li>your email trashbin&lt;/li>
&lt;li>chats with your ex&lt;/li>
&lt;li>abandoned side projects&lt;/li>
&lt;/ul>
&lt;h2 id="how-do-we-declutter">How do we declutter&lt;/h2>
&lt;p>This shouldn’t be a full list of where you can find clutter on your phone or computer. But once you are aware of WHY you unhealthily keep stuff, you will more easily recognize where you waste space and focus. Apart from the examples that I already gave you, it could be that&lt;/p>
&lt;ul>
&lt;li>– you still have a lot of Bluetooth devices in the list that you don’t use anymore,&lt;/li>
&lt;li>– you follow too many accounts on social media to &lt;em>follow&lt;/em> what they post,&lt;/li>
&lt;li>– you have contacts in your contact list you don’t even know who that is&lt;/li>
&lt;/ul>
&lt;p>You will find many how-to blog posts once you start to google ‘digital clutter’, but the key is to understand what triggers you to collect more and why it is hard to let things go: I am on my phone a lot. Some would say that I have a deep relationship with it. If you are a little like me, you will merely not use it as a phone, but as a remote control for your whole life (and with a remote control for the remote control aka Apple Watch. We quickly have 100+ apps installed, and because we need to record 4k video, have thousands of songs on our phones, and hoard all kinds of other data, we need phones with an insane storage capacity. And now that we have that capacity, we hoard even more. And then we pay cloud services to outsource apps and files… Do you recognize a pattern? We repeat the behavior we show in our analog lives in our digital lives. So would it help if we asked ourselves why we keep the digital stuff? Why do we not delete chats of people we are not in contact with anymore? Why do we still maintain that domain we once registered but then never worked on it again? Same reasons as you keep physical stuff: JUST. IN. CASE.&lt;/p>
&lt;p>Not that this case will EVER occur, though.&lt;/p>
&lt;p>What happens once we dig a little bit deeper into what triggers us to keep and hoard? We will gain clarity about what drives us- and what holds us (back). Because, if we own everything, our hands aren’t free, we can’t &lt;em>hand-le&lt;/em> anything anymore, literally. The perfidy of digital clutter is, though, that we can more easily hide it. But once we know if we want to impress ourselves, still live in the past, or want to produce a specific image of ourselves, it’s easier to get rid of that, be honest, and truthful with ourselves. It’s relieving to toss stuff that weighed a lot. And once you did, you feel alleviated and calmed.&lt;/p>
&lt;h2 id="where-do-i-start">Where do I start?&lt;/h2>
&lt;p>Well, I am a consultant, so the answer needs to be &lt;em>It depends&lt;/em>. Here are some tips:&lt;/p>
&lt;ul>
&lt;li>Don’t just &lt;em>organize&lt;/em> apps on your phone in groups.&lt;/li>
&lt;li>Ask yourself, which value an app, a contact, a chat, a connection still offers you. And if this is about lost hopes or reality.&lt;/li>
&lt;li>What’s the worst-case scenario if you delete something?&lt;/li>
&lt;li>this blog post is the first of a small series of posts around digital clutter. Read next week, how I tame my alerts and notifications&lt;/li>
&lt;/ul>
&lt;h2 id="feedback--what8217s-next">Feedback &amp;amp; What’s next&lt;/h2>
&lt;p>If you liked this post, leave a comment and tell me what resonated the most to you. How do you deal with digital clutter? Does it bother you? Do you have the feeling that stuff consumes too much of your attention? T I would love to hear your feedback.&lt;/p></description></item><item><title>my 2020 or jumping into the next rabbit hole</title><link>https://m365princess.com/blogs/2020-12-21-my-2020/</link><pubDate>Mon, 21 Dec 2020 16:24:11 +0000</pubDate><guid>https://m365princess.com/blogs/2020-12-21-my-2020/</guid><description>&lt;h1 id="2020-what-a-year">2020-WHAT. A. YEAR.&lt;/h1>
&lt;p>This was a special one for me, like for so many of us. When we were still traveling and making big plans in the first weeks of 2020, no one had a clue that we all would be sitting at home (office) for most of the time and struggle not to meet people nor see other places. But as this is a recap of my year 2020, I won’t focus on the pandemic side of events but on how I perceived this year. If you want to catch up first with #2019recap, you can do this here: &lt;a href="https://m365princess.com/2019-or-how-i-started-to-believe-in-myself/" target="_blank" rel="noopener">2019 or how I started to believe in myself&lt;/a>&lt;/p>
&lt;p>I run my own one-woman-army and offer Microsoft 365 consultancy. In the last years, I used to say that I do the ‘non-technical’ part of a project, focusing on adoption and communications and making excuses for my non-traditional background. I stopped doing this, and I developed my perception of myself and grew into a different role. This happened not by coincidence, but because I was focusing on some topics. As this will take some time to tell, you should grab a coffee; I will wait here.&lt;/p>
&lt;h2 id="how-it-all-started">How it all started&lt;/h2>
&lt;p>Got a coffee or a drink? Cool, let’s continue. Everything started in this pandemic- and lockdown situation. Remember when I was giving away stickers and send them around the world in 2019? That was a huge success, and I quickly needed to develop an excellent solution to handle and automate it. &lt;a href="https://m365princess.com/using-microsoft-flow-to-automate-my-process-of-sending-stickers/" target="_blank" rel="noopener">I used Power Automate to make the process of printing envelopes easier and faster&lt;/a>. During the early phase of Covid19 lockdown, I teamed up with &lt;a href="https://eliostruyf.com" target="_blank" rel="noopener">Elio Struyf&lt;/a> and together we founded our startup &lt;a href="https://https://pimpyourowndevice.com" target="_blank" rel="noopener">pimpyourowndevice.com&lt;/a> which is an online sticker shop. Both of us were designing and sharing stickers before, and it seemed to be a logical step. As both of us wanted to learn from each other and explore what we could do with our skills, we did not go with a standard approach of having a decent out-of-the-box solution to run a store but developed our way to handle things. We both love to tell our #BetterTogether story- me as a maker, he as a developer. You can read more about how we run this business in &lt;a href="https://www.eliostruyf.com/running-online-store-powerplatform-azure/" target="_blank" rel="noopener">Elio’s blog.&lt;/a>&lt;/p>
&lt;h2 id="fusiondev--developing-my-maker-skills">FusionDev &amp;amp; developing my maker skills&lt;/h2>
&lt;p>As I now had a real-world project with real products, real customers, and real issues, I wanted to step up my Power Platform game. I’ve already been quite familiar with Power Automate, gave many sessions at conferences, blogged about it, and of course, enabled my customers to simplify their processes as well. I also used Power Virtual Agents in some cool solutions; you can watch one of them in this video: &lt;a href="https://channel9.msdn.com/Shows/Less-Code-More-Power/Power-up-Teams-with-Power-Virtual-Agent-with-Luise-Freese">Power up Teams with Power Virtual Agent with Luise Freese | #LessCodeMorePower | Channel 9 (msdn.com)&lt;/a>&lt;/p>
&lt;p>But for some reason, I did not have any experience with Power Apps. Time to change that. I released my very first Canvas App on 2020-05-04 (the Star Wars nerds will get it ). It was an app to help kids practice calculating; you can read more about it here: [&lt;a href="https://m365princess.com/may-the-force-be-with-me-my-first-canvas-app-in-power-apps/" target="_blank" rel="noopener">May the force be with me&lt;/a>). This app was somehow a breakthrough for me, as I learned to make apps in Power Apps and needed to find a way to make a math app while I have dyscalculia. If you are not familiar with that term, dyscalculia is a learning disorder, like dyslexia. People with dyscalculia struggle with maths and numbers in general. I use to explain it like that:&lt;/p>
&lt;p>*Every time I see a math problem, it looks like this:*&lt;/p>
&lt;p>&lt;em>*”If I have 10 ice cubes and you have 11 apples, how many pancakes will fit on the roof?*&lt;/em>&lt;br>
&lt;em>*Answer? Purple. Because aliens don’t wear hats.”*&lt;/em>&lt;/p>
&lt;p>Sounds perhaps a Lil funny, but in fact, it affects me every single day- So I chose my ‘end of the level baddie’ to make an app. I did not only learn how to use the different controls and how to low-code, but also took all my knowledge from consulting clients (understanding the problem that needs to be solved, envisioning a solution, building a ‘minimal lovable product’ and adapt it again and again after reviews) into my app making process.&lt;/p>
&lt;p>This led to more and more apps and sessions, where I explained how to get started with Power Apps. The fact that I got quite some visibility and exposure led to customers asking for Power Apps development and empowerment. And while I designed training and workshops for them, I learned different approaches to solve problems and explain everything so that it made sense to different target audiences. Some of my workshop attendees are end-users, some of them are experienced citizen developers who were not familiar with Power Platform but already built solutions on other platforms, some of them are web developers looking into faster ways to achieve their goals, and some of them were looking for a more institutionalized way to solve issues in their organization. As I learned in my startup, that #FusionDev, the mixed team of makers and traditional developers, is beneficial for a project, I could show and tell first hand how makers and developers could team up to achieve more together.&lt;/p>
&lt;p>As a result, I successfully shifted my own business from being a business consultant in Microsoft 365 universe to a more technical role as a Low Code developer and consultant. How did it happen that fast? Because I enjoy growth and problem-solving. When I want to learn something, I immerse myself in that topic. I follow hashtags and people on Twitter, watch YouTube videos, read blog posts, and of course, tell anyone that I want to learn that thing. And then, I start exploring and building—basically, I &lt;a href="https://twitter.com/search?q=%40donasarkar%20%23DoTheThing&amp;src=typed_query" target="_blank" rel="noopener">#DoTheThing -thanks Dona!&lt;/a>&lt;/p>
&lt;p> &lt;/p>
&lt;img loading="lazy" class="aligncenter size-medium wp-image-519" src="https://m365princess.com/wp-content/uploads/2020/12/change-300x297.png" alt="" width="300" height="297" srcset="https://m365princess.com/wp-content/uploads/2020/12/change-300x297.png 300w, https://m365princess.com/wp-content/uploads/2020/12/change-150x150.png 150w, https://m365princess.com/wp-content/uploads/2020/12/change-768x759.png 768w, https://m365princess.com/wp-content/uploads/2020/12/change-45x45.png 45w, https://m365princess.com/wp-content/uploads/2020/12/change.png 806w" sizes="(max-width: 300px) 100vw, 300px" />
&lt;h2 id="microsoft-graph--dev-community">Microsoft Graph &amp;amp; Dev Community&lt;/h2>
&lt;p>I knew this from my Power Automate journey in the previous year – if I wanted to do cool stuff, I needed to use Microsoft Graph. In 2020, this did not only mean extending my knowledge of using the Graph API in my Power Platform solutions but also learning about Microsoft Graph Toolkit (MGT). I blogged a whole [&lt;a href="https://m365princess.com/exploring-microsoft-graph-toolkit-lap-as-non-developer" target="_blank" rel="noopener">series about my experiences as a non-traditional developer with MGT&lt;/a> and had quite some AHA! Moments when I realized that there is so much more for Low Coders like me beyond Power Platform and that people spend too much energy distinguishing between makers and developers instead of looking deeper into what unites us and how we could benefit from each other.&lt;/p>
&lt;p>After I started blogging about MGT, I got invited to the team of the fantastic group of people that ran the &lt;a href="https://developer.microsoft.com/en-us/graph/blogs/announcing-a-lap-around-microsoft-graph-toolkit-blog-series/" target="_blank" rel="noopener">Lap around Microsoft Graph Toolkit.&lt;/a> And I learned some things that would be very important to me:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>DevCommunity is a fantastic place to be, and everyone nicely ‘adopted’ me while I love to advocate for this #BetterTogether&lt;/p>
&lt;/li>
&lt;li>
&lt;p>My not-code-centric perspective was helpful for others and helped them gain a different angle&lt;/p>
&lt;/li>
&lt;li>
&lt;p>I extended again and again my comfort zone and decided to never return to it anymore. Growth, to me, means keeping on boldly going.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>One way to learn were the 3 &lt;a href="https://m365princess.com/m365-developer-bootcamp/" target="_blank" rel="noopener">Microsoft 365 Developer Bootcamps&lt;/a> that I attended under, let’s say, ‘special conditions’:&lt;/p>
&lt;p> &lt;/p>
&lt;p>&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/hairdresser.png">&lt;img loading="lazy" class="alignleft wp-image-478 size-thumbnail" src="https://m365princess.com/wp-content/uploads/2020/10/hairdresser-150x150.png" alt="" width="150" height="150" srcset="https://m365princess.com/wp-content/uploads/2020/10/hairdresser-150x150.png 150w, https://m365princess.com/wp-content/uploads/2020/10/hairdresser-45x45.png 45w" sizes="(max-width: 150px) 100vw, 150px" />&lt;/a>&lt;/p>
&lt;p>* 1*  at my hairdresser’s, with tin foil in my hair while coding&lt;/p>
&lt;p>* 1* at my nail artist’s – working temporarily handicapped, as I only could use one hand&lt;/p>
&lt;p>* 1* at 4 am after my own ‘in-Lockdown-Birthday-party.’&lt;/p>
&lt;p>I like to tell this because it shows that I can gain skills and grow but still stay the same multidimensional person – who loves to joyfully jump into the rabbit hole and enjoys life (even in a pandemic).&lt;/p>
&lt;h2 id="microsoft-365-pnp">Microsoft 365 PnP&lt;/h2>
&lt;p>After I’ve been a guest in &lt;a href="https://developer.microsoft.com/en-us/office/blogs/pnp-weekly-episode-71/)," target="_blank" rel="noopener">PnP Weekly ep. 71&lt;/a> I started to join Microsoft 365 PnP calls because I loved to gain more knowledge about low-code and coded solutions in Microsoft 365. Later, I participated in the &lt;a href="https://pnp.github.io/sharing-is-caring/" target="_blank" rel="noopener">SharingIsCaring initiative&lt;/a>. My friends &lt;a href="https://twitter.com/DavidWarnerII" target="_blank" rel="noopener">David Warner&lt;/a> and &lt;a href="http://(https://twitter.com/bernierh" target="_blank" rel="noopener">Hugo Bernier&lt;/a> let me tame this GitHub monster for me so that I am not scared anymore. As a result, I became a contributor to &lt;a href="https://docs.microsoft.com/en-us/microsoft-365/community/)" target="_blank" rel="noopener">Microsoft 365 Community Docs&lt;/a> which happens to take place in GitHub. 4 weeks ago, I had the great honor to &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/blogs/new-microsoft-365-patterns-and-practices-pnp-team-members-2" target="_blank" rel="noopener">become a member of the Microsoft 365 PnP core team&lt;/a> – My mission is to emphasize that we can achieve more together. I will be taking the lead of initiatives to make this community a virtual home for everyone who wants to extend Microsoft 365 regardless of technical background.&lt;/p>
&lt;h2 id="mental-health-and-clarity">Mental health and clarity&lt;/h2>
&lt;p>Like most of us, I struggled with being at home a lot, and I established some bad habits. After I finished my ‘work work’ (this is how I call my billable hours), I started to do ‘work’ ( this is how I call community work, such as writing blogs, preparing sessions, recording podcasts, etc.). I worked crazy hours at night, didn’t take breaks, and was just happy to be busy enough not to feel alone. This led to overcommitment, which was grounded in a false belief that ‘more is better’. You can read more about it in my last blog post about &lt;a href="https://m365princess.com/how-to-avoid-overcommitting/" target="_blank" rel="noopener">How to avoid overcommitting&lt;/a> I solved that not by ‘learning to say no’ but by taking a more in-depth look into the root of the cause. When I wrote my list of contributions in this blog post’s preparations, I got slightly shocked. In total, I&lt;/p>
&lt;ul>
&lt;li>attended 13 events&lt;/li>
&lt;li>delivered 39 sessions at virtual events&lt;/li>
&lt;li>wrote 22 blog posts&lt;/li>
&lt;li>co-organized 4 conferences&lt;/li>
&lt;/ul>
&lt;p>all while living 24/7 on Twitter 🙂&lt;/p>
&lt;p>I was shocked, because I did not imagine this amount of contributions, but when I reflected this thought with two close friends, they were not surprised at all – which means that my perception and how others perceive me don’t match.&lt;/p>
&lt;p>It took me a whole afternoon to make this list, and yes, I know, I should update my list after every contribution. But as this simple thing so far didn’t ‘just happen,’ I asked myself why I refused to update a list. As I was already working on understanding what drives me, I took some time investigating that. When I felt #comfortablynumb with being busy, I tried to avoid clarity about what I was already doing regarding community work – and a list would have led to clarification. Now that I regained control and overview of all initiatives that I am running, I have an intrinsic interest in knowing what I do and succeed. So I will update my list of contributions and take time to reflect on what I learned (about tech, community, and myself).&lt;/p>
&lt;p>Sounds like a plan – read in my #2021recap how this turned out.&lt;/p>
&lt;h2 id="please-staysafe-hope-to-meet-you-in-2021">Please #staySafe, hope to meet you in 2021!&lt;/h2></description></item><item><title>How to avoid overcommitting</title><link>https://m365princess.com/how-to-avoid-overcommitting/</link><pubDate>Thu, 10 Dec 2020 21:46:54 +0000</pubDate><guid>https://m365princess.com/how-to-avoid-overcommitting/</guid><description>&lt;h1 id="intro">Intro&lt;/h1>
&lt;p>Besides my work as an independent consultant and low code developer, I do a lot of community work. I am an active Microsoft MVP, new member of the PnP team, run a blog, am a speaker at conferences, live on Twitter, record podcasts, draw sketchnotes, mentor fellow members of the community and support many initiatives that make tech a welcoming and inclusive space for everyone.&lt;/p>
&lt;p>For my (paid) day-to-day job, I exactly know how much time I will spend on work with my customers. I have my due dates and deadlines, organize everything very clearly, and if I see, that I won’t be able to do something in the requested time, I will tell my customer upfront—everything under control.&lt;/p>
&lt;p>This does not apply to my community work. Someone will ask me to work together on something, and because I am interested in many things AND get distracted easily, I can lose my focus. This leads to saying ‘yes’ way too often, losing track, and having that feeling of juggling too much at the same time, I found myself drowning in my community activities.&lt;/p>
&lt;img loading="lazy" class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido1.png" alt="" width="934" height="700" />
&lt;h1 id="impact">Impact&lt;/h1>
&lt;p>Overpromising leads to bad quality of results, because you need to work through things in a rush or it damages your reputation because you do not meet the expectations of others. You will also most likely blame yourself and feel bad about it which could lead to a vicious circle of feeling guilty, trying to compensate that with the next commitment and not achieving what you had in mind in the first place. And then you feel bad about feeling guilty. Sigh. We all joke about abandoned side projects and domains we would never put into use, there are even websites on which you can trade abandoned side projects-but in reality, this is just the tip of the iceberg of overcommitment.&lt;/p>
&lt;img class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido2.png" alt="" />
&lt;p style="text-align: left;">
Source: &lt;a href="https://twitter.com/ThePracticalDev/status/792730113158868993">https://twitter.com/ThePracticalDev/status/792730113158868993&lt;/a>
&lt;/p>
&lt;h1 id="the-core-of-the-problem">The core of the problem&lt;/h1>
&lt;h2 id="so-what-is-overcommitting">So what is overcommitting?&lt;/h2>
&lt;p>To prepare for this blog post, I googled a bit, and I found two fascinating definitions: Of course, the Merriam-Webster Dictionary gives us an obvious explanation of what is overcommitment, and I will refer to that in this post: &lt;em>obligating yourself beyond the ability of fulfilment.&lt;/em>&lt;/p>
&lt;img class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido3.png" alt="" />
&lt;p style="text-align: left;">
Source: &lt;a href="https://www.merriam-webster.com/dictionary/overcommit">&lt;span style="font-size: 9pt;">https://www.merriam-webster.com/dictionary/overcommit&lt;/span>&lt;/a>&lt;span style="font-size: 9pt;">&lt;em>>&lt;br /> &lt;/em>&lt;/span>
&lt;/p>
&lt;p>But as I work in tech, ‘Memory overcommitment’ came to my mind as well: It is a concept that allows a virtual machine to use more memory space than the physical host has available. And this made me think: ‘Do we try to act like VMs, which can just use MORE memory space than it has allocated?’ If you still read this, then chances are high, that you will answer this question with a ‘yes’.&lt;/p>
&lt;img class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido4.png" alt="" />
&lt;p style="text-align: left;">
Source: &lt;a href="https://en.wikipedia.org/wiki/Memory_overcommitment">Memory overcommitment &amp;#8211; Wikipedia&lt;/a>
&lt;/p>
&lt;p>Although we know that we only have limited capacity to work on things, process information and focus to on spend on, we try to behave like machines to make still it happen. We cut on sleep, to even make it work, which won’t lead to a better outcome. We lose overview about the things we want to do, or we promised to do. And if we suffer too much, we will question ourselves, why we cause so much stress and what we could do about that. And then we read something like ‘learn to say “no”‘. But that is easier said than done, because it is only curing the symptoms, but doesn’t go to the root of the cause.&lt;/p>
&lt;h2 id="systemic-thinking">Systemic thinking.&lt;/h2>
&lt;p>If we approach that with systematic thinking, we can have a more in-depth look on that: We will be able to see several levels&lt;/p>
&lt;img class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido5.png" alt="" />
&lt;ol>
&lt;li>React&lt;/li>
&lt;/ol>
&lt;p>we only focus on the events, that happen, like ‘I am catching a cold’ or even ‘I feel stressed’. This top-level is visible to us and noticeable to others very quickly. It is how we react to circumstances and decisions.&lt;/p>
&lt;ol start="2">
&lt;li>Anticipate&lt;/li>
&lt;/ol>
&lt;p>The layer underneath those events would be anticipating and reviewing patterns and trends, like ‘I’ve been catching a cold more often when sleeping less’ or ‘I felt more often stressed when I lost the overview of what I already committed to’. We now make a guess, that if we sleep less, this will lead to getting sick more likely, we anticipate, that no overview will result in stress.&lt;/p>
&lt;ol start="3">
&lt;li>Design&lt;/li>
&lt;/ol>
&lt;p>If we still want to go a level deeper, we will seek the underlying structures of the patterns we identified in the last level like ‘what has influenced the patterns?’ And ‘how are the patterns interwoven’, ‘what does their relationship look like?’. In our cases, this could lead to considerations like ‘more stress, lack of healthy food, challenging times led to a lack of sleep which resulted in catching colds more easily’ and ‘saying automatically “yes” to all kinds of community activity without checking your availability, mental load and capacity or even energy and focus on achieving things, will make you feel stressed more likely’.&lt;/p>
&lt;ol start="4">
&lt;li>Transform&lt;/li>
&lt;/ol>
&lt;p>Our final level will be the mental models that sit underneath and support the structures described in level 3. Which assumptions, beliefs and values do people hold to keep this system in place where you feel forced to behave in a certain way? In our examples, it would be ‘career is the most important thing, even a higher priority than getting enough sleep to stay healthy’ and ‘the more you contribute (and not say ‘no’ to an opportunity), the more you are valuable to the community’.&lt;/p>
&lt;h2 id="how-does-the-model-now-work-in-our-case">How does the model now work in our case?&lt;/h2>
&lt;p>The mental model of ‘the more you contribute, the more valuable you are’ (intensified by the fear of losing your MVP status or losing credibility as a leader) will lead to feel a constant pressure of creating content, getting involved and seeking for opportunities. And although we all love to do community work and strongly believe in the sharing is caring statement, chances are high, that we at some point overcommit. And as you are STILL reading this article, it’s very likely, that you found yourself overcommitting as well.&lt;/p>
&lt;h1 id="approaches-to-solve-this">Approaches to solve this&lt;/h1>
&lt;p>If we only work on the first (few) levels, we are just scratching the surface, which means that we won’t sustainably solve. Of course we could cure some symptoms like ‘taking some more vitamins against catching a cold’ or ‘booking a massage against feeling stressed’, but this isn’t a solution as it doesn’t work sustainably. We will very likely still catch colds because of a lack of sleep or feel stressed because of unhealthy working ethics.&lt;/p>
&lt;h2 id="learning-to-say-no">Learning to say no?&lt;/h2>
&lt;p>If you strongly believe in ‘the more you do, the more value you add’, you will not learn to say ‘no’. Of course, you will neglect some asks after you decided to ‘learn to say “no”‘, but very soon you will return to your old patterns – because you didn’t change the mental model. And yes, it is tempting to fulfil asks – because it means, you count. And your work is valuable. YOU have been asked.&lt;/p>
&lt;h2 id="food-for-thought">Food for thought&lt;/h2>
&lt;p>Let’s reverse the model that led to unhealthy community work ethics:&lt;/p>
&lt;p>Mental model&lt;/p>
&lt;p>What if ‘less is more’ is true in this case? What if focusing on some things rather than trying to be everywhere would be the key for you? Think again about what you wanted to achieve with community work: being recognized for helping others? In that case, specialization and scaling could be a good idea:&lt;/p>
&lt;p>Structure&lt;/p>
&lt;p>Sharpen your profile to be the expert and master of a few things instead of being the Jack of all Trades. This will also condense your brand and will lead to let you work in the community on your sweet spot. You will still have more than enough opportunity to grow and extend your comfort zone.&lt;/p>
&lt;p>Trends and patterns&lt;/p>
&lt;p>If you change your mind towards the Marie Kondo method ‘If it doesn’t spark joy, toss it’, you will surely adapt to be more selective of your commitments and be more likely only to commit if you mean it because you analyzed impact, costs and investments.&lt;/p>
&lt;p>Events&lt;/p>
&lt;p>This will lead to you not overcommitting again, because you solved the issue at its root and didn’t just care about a symptoms.&lt;/p>
&lt;p>But what if you now know about the impact of your mental model but still need to deal with your previous choices? How can we gain control over our own desire to be recognized as a helpful leader?&lt;/p>
&lt;h3 id="visualization">Visualization&lt;/h3>
&lt;p>A good starting point to regain control and to be aware of what you already committed to, and which activities depend on other activities and how everything is interwoven would be visualizing everything. You could make a list; I chose to put all my commitments (the things I promised and the things I want to do, all my rough ideas, that I want to elaborate on) in a Microsoft Planner board. The tool itself doesn’t matter, make sure that you make everything visible. Collecting everything from different lists, conversations, notebooks, whiteboards etc. could take some time, but it is worth it. When I did that, I felt a bit scared, how much was going on in my mind (and task lists) and how heavy my mental load was.&lt;/p>
&lt;img class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido6.png" alt="" />
&lt;h3 id="clarify-impact">Clarify Impact&lt;/h3>
&lt;p>Now that you pulled out everything, it’s a good time to think about the impact each task (in my case, it was a card) has. Determine the effect of DOING the thing and of NOT doing it for you and community. What happens community-wise, if you don’t write THAT blogpost? If you don’t participate in THAT tweet jam or if you won’t co-organize THAT conference? Probably, you will allow someone else to do it. Now think about what happens to you, if you skip that task: Will you still be a valuable member of the community? Of course? Will you improve because of caring for yourself and better mental health? Sure!&lt;/p>
&lt;h3 id="pick-one-thing-that-is-very-close-to-your-heart">Pick one thing that is very close to your heart&lt;/h3>
&lt;p>As we do not only make reasonable decisions, but are of course emotionally involved, it is now a good idea to pick one thing, that you are passionate about. This is your ‘feel good commitment’.&lt;/p>
&lt;h3 id="estimate-your-costs">Estimate your costs&lt;/h3>
&lt;p>Try to estimate now, how much time you need to invest for each commitment and compare the desired outcome (support others, solidifying your knowledge, proving you are a valuable member of community/thought leader) with the actual impact of not doing it and the time you will need to spend on it.&lt;/p>
&lt;h3 id="make-decisions">make decisions&lt;/h3>
&lt;p>Little impact but colossal investment? Please don’t do it. Conflicts with your values of family, self-care, being thoughtful, inclusive etc.? Please don’t do it.&lt;/p>
&lt;h2 id="better-choices">Better choices&lt;/h2>
&lt;p>To make better decisions for the future, I collected some things that I started to do, which could be helpful for you as well:&lt;/p>
&lt;h3 id="everything-needs-to-be-on-the-board-of-commitments">Everything needs to be on the board of commitments&lt;/h3>
&lt;p>Have your list/board/visualization at hand. Whenever you get asked ‘can you do x’ or ‘would you like to collaborate with me on y’ and even if you think ‘I should blog/vlog about z’, have a look on your board of commitments and oblige yourself to put everything on it. Sometimes, already this will lead to saying ‘no’ or putting that on your backlog. But watch out that your backlog doesn’t grow to a pile of guilt.&lt;/p>
&lt;h3 id="no-last-minutes">No last minutes&lt;/h3>
&lt;p>Don’t commit to last-minute duties. Consider to check your calendar. If it looks like this, don’t do it!&lt;/p>
&lt;img loading="lazy" class="aligncenter" src="https://m365princess.com/wp-content/uploads/2020/12/121020_2146_Howtoavoido7.png" alt="" width="810" height="487" />
&lt;p style="text-align: left;">
Source: &lt;a href="https://twitter.com/DugarToGo/status/1333676357512028163?s=20">https://twitter.com/DugarToGo/status/1333676357512028163?s=20&lt;/a>
&lt;/p>
&lt;p>Don’t say yes immediately, think about: costs, impact and consequences, if you don’t do it. Then decide without pressure. This will even sound reasonable, if you tell: ‘I feel honoured /flattered/appreciated to be asked, but I first want to consider, if I can deliver that in a given quality without getting into trouble. I will get back to you on Monday.’&lt;/p>
&lt;h3 id="even-small-commitments-add-up">Even small commitments add up!&lt;/h3>
&lt;p>Even if you think, that a commitment is just a tiny thing: Consider your mental load and that you can focus only on a few things.&lt;/p>
&lt;h3 id="follow-the-process">Follow the process&lt;/h3>
&lt;p>Make it easier to follow your process of balancing mental load and commitments than working around it. Have your board/list at hand and write down everything. Also put your estimated time investment plus impact on the card/into the list and separate ‘having an idea’ or ‘being invited to collaborate on an idea’ from ‘deciding actually to do that and commit to it’. Once you committed, think about a due date. Check your calendar and block the time dedicated to this thing. If you don’t find a free slot, (not extending your working hours to work nightshifts, too) consider to do it later (change the due date) or not at all. Then put the card into your backlog (if it is still a good idea for you) or sponsor someone else in the community with a good idea. #SharingIsCaring&lt;/p>
&lt;h1 id="feedback-and-what8217s-next">Feedback and what’s next?&lt;/h1>
&lt;p>What do you think? What helped you to be aware of overcommitting and how did you stop to do that? Would love to host a panel discussion around that – what’s on your mind?&lt;/p></description></item><item><title>Announcement: Luise is now member of PnP Core Team</title><link>https://m365princess.com/blogs/2020-12-01-announcement-luise-is-now-member-of-pnp-core-team/</link><pubDate>Tue, 01 Dec 2020 18:06:59 +0000</pubDate><guid>https://m365princess.com/blogs/2020-12-01-announcement-luise-is-now-member-of-pnp-core-team/</guid><description>&lt;p style="margin: 0in; font-family: Calibri; font-size: 11.0pt;">
Absolutely thrilled to announce that as of last week, I joined the &lt;a href="https://pnp.github.io/#team">Microsoft 365 PnP Core Team&lt;/a> as a member and will help to expand Microsoft 365 PnP to be truly inclusive for everyone. I will be leading initiatives to make this community become a virtual home for everyone who wants to build something on Microsoft 365 regardless of technical background, or toolset they use. Rather than distinguishing different approaches to reach the same goal or separating people from each other, I aim to unite people no matter which kind of service, app, automation, or extension they want to build. I want to break down siloes, which will lead to better connectiveness, understanding and also improved solutions.
&lt;/p>
&lt;p style="margin: 0in; font-family: Calibri; font-size: 11.0pt;">
My mission is to emphasize that we can achieve more together and that we all belong to this community, where everyone supports and cares for everyone. This community helped me so much on my way and I love to give back as well.
&lt;/p>
&lt;p style="margin: 0in; font-family: Calibri; font-size: 11.0pt;">
I could not feel more blessed to be part of an awesome team, in which everyone strongly believes in spreading the word for the #sharingiscaring movement.
&lt;/p>
&lt;p style="margin: 0in; font-family: Calibri; font-size: 11.0pt;">
I will use our new hashtag &lt;a href="https://twitter.com/search?q=%23M365PnP&amp;src=typed_query">#m365pnp&lt;/a> for Microsoft 365 PnP Community related tweets and would love if you join me! Not sure what is Microsoft 365 PnP? Start here: &lt;a href="https://pnp.github.io">PnP | Microsoft 365&lt;/a>
&lt;/p></description></item><item><title>How to deploy Microsoft Teams app templates in your tenant</title><link>https://m365princess.com/blogs/2020-10-28-how-to-deploy-microsoft-teams-app-templates-in-your-tenant/</link><pubDate>Wed, 28 Oct 2020 18:39:51 +0000</pubDate><guid>https://m365princess.com/blogs/2020-10-28-how-to-deploy-microsoft-teams-app-templates-in-your-tenant/</guid><description>&lt;p>This blog post shall guide you through the process of App deployments with Microsoft Teams App templates.&lt;/p>
&lt;p>Microsoft Teams is an excellent hub for collaboration, and the most fantastic thing about it is its extensibility. Custom development can close some gaps between out of the box features and specific business needs. But we do not always need to reinvent the wheel completely. You can find &lt;a href="https://docs.microsoft.com/en-us/microsoftteams/platform/samples/app-templates">&lt;span style="color: #5b9bd5;">amazing ready-to-deploy app-templates&lt;/span>&lt;/a>. Of course, as a developer, you may want to tweak those templates, but they are ready to use in production and, of course, great to understand how deployment works in Azure.&lt;/p>
&lt;p> &lt;/p>
&lt;div style="width: 640px;" class="w-100">
&lt;!--[if lt IE 9]>&lt;![endif]-->&lt;video class="wp-video-shortcode" id="video-481-1" width="100%!important" preload="metadata" controls="controls">&lt;source type="video/mp4" src="https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4?_=1" />
&lt;p>&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4">&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4">https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4&lt;/a>&lt;/a>&lt;/video>&lt;/p>
&lt;/div>
&lt;p>⚡ Please don’t test in your production environment. If you are new to this, &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/dev-program">&lt;span style="color: #5b9bd5;">get a free Microsoft 365 developer tenant&lt;/span>&lt;/a>, it’s way safer for you.&lt;/p>
&lt;p>As an example, I will use the &lt;a href="https://docs.microsoft.com/en-us/microsoftteams/platform/samples/app-templates">&lt;span style="color: #5b9bd5;">Groups Activity App&lt;/span>&lt;/a> to show the steps that we usually need to do to deploy an app in our tenant.&lt;/p>
&lt;p>You can get the complete documentation, including all files, deployment guides, and architecture overview &lt;a href="https://github.com/OfficeDev/microsoft-teams-apps-groupactivities">&lt;span style="color: #5b9bd5;">on GitHub&lt;/span>&lt;/a>. In this article, I will make you aware of some essential steps, which you might forget or give you some context.&lt;/p>
&lt;h2 id="a-nameregister-an-app-in-azure-adaregister-an-app-in-azure-ad">&lt;a name="register-an-app-in-azure-ad">&lt;/a>Register an App in Azure AD&lt;/h2>
&lt;p>First, you will need to register an Application in Azure AD, create an app secret and enable GRAPH API permissions.&lt;/p>
&lt;ul>
&lt;li>Remember to save your App secret immediately after creating it – you won’t be able to read or copy it as soon as you click on something else in Azure Portal.&lt;/li>
&lt;li>To know which permissions you need, read the documentation of the app template.&lt;/li>
&lt;li>Don’t forget to grant admin consent for the permissions&lt;/li>
&lt;/ul>
&lt;div style="width: 640px;" class="w-100">
&lt;video class="w-100" id="video-481-2" width="100%!important" preload="metadata" controls="controls">&lt;source type="video/mp4" src="https://m365princess.com/wp-content/uploads/2020/10/Step1RegisterApp.mp4?_=2" />&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step1RegisterApp.mp4">https://m365princess.com/wp-content/uploads/2020/10/Step1RegisterApp.mp4&lt;/a>&lt;/video>
&lt;/div>
&lt;h2 id="a-namedeploy-templateadeploy-template">&lt;a name="deploy-template">&lt;/a>Deploy template&lt;/h2>
&lt;p>As we deal with One-Click-Deployment, you will need to click that magic &lt;span style="font-family: Consolas;">deploy to Azure&lt;/span> button in the deployment guide and&lt;/p>
&lt;ul>
&lt;li>create a new resource group&lt;/li>
&lt;li>paste Tenant ID, AppID, App Secret into the form,&lt;/li>
&lt;li>be a little bit patient. It can take a while. You don’t need to stay on that site. You can check later on as well.&lt;/li>
&lt;/ul>
&lt;div style="width: 640px;" class="w-100">
&lt;video class="wp-video-shortcode" id="video-481-3" width="100%!important" preload="metadata" controls="controls">&lt;source type="video/mp4" src="https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4?_=3" />&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4">https://m365princess.com/wp-content/uploads/2020/10/Step2DeployTemplate.mp4&lt;/a>&lt;/video>
&lt;/div>
&lt;h2 id="a-nameadd-authenticationaadd-authentication">&lt;a name="add-authentication">&lt;/a>Add authentication&lt;/h2>
&lt;p>Azure AD can easily manage authentication, connect our bot with it, and set the scope as in our app registration.&lt;/p>
&lt;div style="width: 640px;" class="w-100">
&lt;video class="wp-video-shortcode" id="video-481-4" width="100%!important" height="679" preload="metadata" controls="controls">&lt;source type="video/mp4" src="https://m365princess.com/wp-content/uploads/2020/10/Step4AddAuthentication2.mp4?_=4" />&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step4AddAuthentication2.mp4">https://m365princess.com/wp-content/uploads/2020/10/Step4AddAuthentication2.mp4&lt;/a>&lt;/video>
&lt;/div>
&lt;h2 id="a-namepackage-appapackage-app">&lt;a name="package-app">&lt;/a>Package App&lt;/h2>
&lt;p>Now it’s already time to create our App package. For this, we will need the manifest.json file and two .png files as App icons which we will find in the GitHub repo.&lt;/p>
&lt;p>In the manifest.json we will replace placeholders for&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;span style="font-size: 12pt;">developer name,&lt;br /> &lt;/span>&lt;/p>
&lt;/li>
&lt;li>
&lt;div>
&lt;span style="font-size: 12pt;">URLs for&lt;br /> &lt;/span>
&lt;/div>
&lt;ul>
&lt;li>&lt;span style="font-size: 12pt;">site,&lt;br /> &lt;/span>&lt;/li>
&lt;li>privacy,&lt;/li>
&lt;li>terms of use,&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>BOT ID (our App ID),&lt;/p>
&lt;/li>
&lt;li>
&lt;p>validDomains –&amp;gt; [BaseResourceName].azurewebsites.net,&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>and zip this &lt;span style="font-family: Consolas;">manifest.json&lt;/span> with &lt;span style="font-family: Consolas;">outline.png&lt;/span> and &lt;span style="font-family: Consolas;">color.png&lt;/span> from GitHub repo in a GroupActivities.zip file.&lt;/p>
&lt;p>⚡ Watch out, sometimes the &lt;span style="font-family: Consolas;">outline.png&lt;/span> provided on GitHub are not transparent, and then your app won’t pass App validation. If this is the case for you, make it transparent, add it again to your .zip file.&lt;/p>
&lt;div style="width: 640px;" class="w-100">
&lt;video class="wp-video-shortcode" id="video-481-5" width="100%!important" preload="metadata" controls="controls">&lt;source type="video/mp4" src="https://m365princess.com/wp-content/uploads/2020/10/Step5Package.mp4?_=5" />&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step5Package.mp4">https://m365princess.com/wp-content/uploads/2020/10/Step5Package.mp4&lt;/a>&lt;/video>
&lt;/div>
&lt;h2 id="a-namepublishapublish">&lt;a name="publish">&lt;/a>Publish&lt;/h2>
&lt;p>The easiest way to publish your app is via Microsoft Teams App Studio – but you can also do it with Visual Studio Code Teams Toolkit.&lt;/p>
&lt;p>If you choose App Studio, upload your .zip file and select publish and then app catalog.&lt;/p>
&lt;p>If you choose Teams Toolkit, you can see potential error even before trying to publish, and the error/warning notifications give you more detail about what was wrong, while App Studio just returns a ‘Something went wrong.’&lt;/p>
&lt;p>Time for a happy dance!&lt;/p>
&lt;div style="width: 640px;" class="w-100">
&lt;video class="wp-video-shortcode" id="video-481-6" width="100%!important" height="360" preload="metadata" controls="controls">&lt;source type="video/mp4" src="https://m365princess.com/wp-content/uploads/2020/10/Step6Publish2.mp4?_=6" />&lt;a href="https://m365princess.com/wp-content/uploads/2020/10/Step6Publish2.mp4">https://m365princess.com/wp-content/uploads/2020/10/Step6Publish2.mp4&lt;/a>&lt;/video>
&lt;/div></description></item><item><title>M365 Developer Bootcamp</title><link>https://m365princess.com/blogs/2020-10-19-m365-developer-bootcamp/</link><pubDate>Mon, 19 Oct 2020 18:35:41 +0000</pubDate><guid>https://m365princess.com/blogs/2020-10-19-m365-developer-bootcamp/</guid><description>&lt;h1 id="being-voluntold">Being volunTOLD&lt;/h1>
&lt;p>Early October, I saw an &lt;a href="https://twitter.com/waldekm/status/1314510933784506368?s=20" target="_blank" rel="noopener noreferrer">announcement on twitter regarding #M365Bootcamp&lt;/a>, a global virtual event, consisting of 2 different labs to build solutions with Microsoft Graph, Teams, SharePoint, held in 3 different timezones across 2 days.&lt;/p>
&lt;p>My dear friend Dona Sarkar assigns homework to nearly everyone and I got volunTOLD to build more. Encouraged by Waldek Mastykarz to join and trust that the labs will be doable and that there is also support available, I registered for both Bootcamps, and this was literally me jumping into the rabbit hole.&lt;/p>
&lt;h1 id="a-fantastic-experience">a fantastic experience&lt;/h1>
&lt;p>After registration, I got a confirmation email and another email as well with a new account, as these Bootcamps are held in a special tenant from Microsoft for those community events. You become a member in that Microsoft Teams team, which gives you more permission as if you were joining as a guest. I attended both Bootcamps in EMEA timezone and they were both incredibly well organized and executed. Bob German (&lt;a href="https://github.com/OfficeDev/M365Bootcamp-TeamsEmergencyResponse" target="_blank" rel="noopener noreferrer">Lab No. 1: “Build an Emergency Response solution with Teams and SharePoint”&lt;/a>) and Ayca Bas (&lt;a href="https://github.com/OfficeDev/M365Bootcamp-TeamsOneProductivityHub" target="_blank" rel="noopener noreferrer">Lab No. 2: “Build “One Productivity Hub” using Microsoft Teams and Microsoft Graph Toolkit”&lt;/a>) did amazing jobs as they:&lt;/p>
&lt;ul>
&lt;li>designed real-world scenarios, completely doable for all developers regardless of their previous experience or level of expertise&lt;/li>
&lt;li>provided excellent material on GitHub: detailed, bias-free, accessible and inclusive for every developer&lt;/li>
&lt;li>explained the overall architecture of the solution we wanted to build&lt;/li>
&lt;li>contextualized the solutions to developers new to the Microsoft 365 ecosystem without overwhelming them&lt;/li>
&lt;li>gave enough room for an awesome group of proctors who helped answering questions and keeping motivation high&lt;/li>
&lt;li>made this whole bootcamp a very interactive hands-on experience&lt;/li>
&lt;/ul>
&lt;p>As everything was prepared, documented and structured in a way, that we learnt and built bite-sized, it felt as easy as building something with LEGO bricks- while it was still tricky enough to do it right, but fun enough to stay motivated. It was the kind of work you LOVE to do: You are neither underwhelmed nor overwhelmed, you immerse yourself into a challenge and totally don’t realize how time flies, while you are surrounded by like-minded #LearnItAlls.&lt;/p>
&lt;p>Bobs’ and Aycas’ investment to prepare everything really paid off, because it meant that they (and of course Microsoft Graph) already did the heavy lifting for us. They also provided an atmosphere that all questions are welcome and valid feedback.&lt;/p>
&lt;h1 id="build-an-emergency-response-solution-with-teams-and-sharepoint-with-bob-german">Build an Emergency Response solution with Teams and SharePoint with Bob German&lt;/h1>
&lt;p>After housekeeping, some gamification and presentation of the solution to build we actually started:&lt;/p>
&lt;h2 id="lab-setup-8211-day-1">Lab setup – Day 1&lt;/h2>
&lt;ul>
&lt;li>a developer tenant (YAY! it’s free and you should have one as well!)&lt;/li>
&lt;li>enabling of Teams App upload ( just a toggle!)&lt;/li>
&lt;li>a SharePoint app catalog&lt;/li>
&lt;li>a Microsoft Teams team (I promise, from now on I will just say “Team” )&lt;/li>
&lt;/ul>
&lt;p>and this was already the first exercise. Bob checked frequently, if everyone reached the next exercise and proctors offered help, support and nerdy jokes to keep up the good moral.&lt;/p>
&lt;h2 id="sharepoint-news">SharePoint News&lt;/h2>
&lt;p>First steps, to build our solution:&lt;/p>
&lt;ul>
&lt;li>connect with SharePoint News in Teams&lt;/li>
&lt;li>add some news to the SharePoint site&lt;/li>
&lt;li>add News tab to Teams&lt;/li>
&lt;/ul>
&lt;h2 id="sharepoint-list-tab">SharePoint List Tab&lt;/h2>
&lt;p>Next steps were&lt;/p>
&lt;ul>
&lt;li>create a SharePoint list&lt;/li>
&lt;li>change column formatting with JSON code snippets that were provided (copy paste FTW!)&lt;/li>
&lt;li>add the list as a tab to Teams&lt;/li>
&lt;li>use APP STUDIO (this will become your favorite app for Teams, I promise) to create a “real” Teams app.&lt;/li>
&lt;li>fill out a form which is called manifest editor&lt;/li>
&lt;li>upload two images which are provided for you&lt;/li>
&lt;li>change a URL&lt;/li>
&lt;li>install the app&lt;/li>
&lt;li>optional: install and pin the app using App policies&lt;/li>
&lt;/ul>
&lt;p>Until now, this whole thing was taking place in nice UIs, nothing too scary, and my success on doing this relied on my ability to carefully read exercise instructions and my experience in SharePoint, Teams and App Studio as I already built a lot of (low-code-) applications and bots. Now off to new adventures with&lt;/p>
&lt;h2 id="sharepoint-framework-tabs">SharePoint Framework Tabs&lt;/h2>
&lt;p>Before we could start we needed to make to have&lt;/p>
&lt;ul>
&lt;li>Node.js version 10.x to run the dev toolchain and&lt;/li>
&lt;li>yeoman&lt;/li>
&lt;/ul>
&lt;p>so we could proceed with the download of the source code and building the project (HOW EXCITING!). I could have used SharePoint public CDN to make it work regardless which computer I want to use for it, but as I love my Surface Laptop 3, I decided to work on a local web server.&lt;/p>
&lt;p>After that, it was time to&lt;/p>
&lt;ul>
&lt;li>upload the bundle to the SharePoint App Catalog&lt;/li>
&lt;li>approve permissions&lt;/li>
&lt;li>install the app in the Team&lt;/li>
&lt;/ul>
&lt;p>One more thing to do: For bing maps you will need a bing maps key, for which you need to register, sign in, find this key, copy-paste it. Detailed instructions on this were available in the exercise, but Bob just provided temporary keys in the Teams chat- Thanks, that’s what I call service! Next step was already last exercise in this lab:&lt;/p>
&lt;h2 id="calling-the-microsoft-graph">Calling the Microsoft Graph&lt;/h2>
&lt;p>After cloning the repository we&lt;/p>
&lt;ul>
&lt;li>write a `sendToChannel` function (we need that later on)&lt;/li>
&lt;li>import a reference to Teams JavaScript SDK&lt;/li>
&lt;li>get the context of the Team from this SDK&lt;/li>
&lt;li>send a message to the current channel with Graph&lt;/li>
&lt;/ul>
&lt;p>All code snippets were available to copy-paste, I took my time to read them 3 times to understand what was happening there. Next steps were updating the `getMapPoints` function and adding an item for the correct permission (`channelMessage.Send`) in the package-solution.json file.&lt;/p>
&lt;p>After each step, Bob linked to how the could should look like, for self-assessment and of course, if you failed doing on step, you could still proceed.&lt;/p>
&lt;p>Final steps:&lt;/p>
&lt;ul>
&lt;li>Re-Deployment of the SharePoint solution package &amp;amp; approvement of permissions (repeating previous steps!)&lt;/li>
&lt;li>test – and celebrate – YAY IT WORKS!&lt;/li>
&lt;/ul>
&lt;h1 id="build-8220one-productivity-hub8221-using-microsoft-teams-and-microsoft-graph-toolkit8221-with-ayca-bas">Build “One Productivity Hub” using Microsoft Teams and Microsoft Graph Toolkit” with Ayca Bas&lt;/h1>
&lt;p>After Bootcamp seems to be before next Bootcamp and on the very next day, I aimed to build a ‘One Productivity Hub’. Very confident because of my experience that I made in Bob’s, I joined Ayca’s session – and to put the spoiler first: I was not disappointed! We started with housekeeping and sweet Kahoot! gamification and got an awesome introduction into Microsoft Graph Toolkit. To be very honest: I didn’t need to hear that, because I already worked with it and blogged a whole series about it, but it was still good to hear everything by Ayca. She encouraged everyone, that it will be totally doable to build an app in a short amount of time , yet again, Microsoft Graph Toolkit reduces complexity as it provides us with web components, easy to use but still adjustable with custom attributes, properties and CSS.&lt;/p>
&lt;h2 id="lab-setup--day-2">Lab setup -Day 2&lt;/h2>
&lt;p>To be able to complete this Bootcamp, we need to&lt;/p>
&lt;ul>
&lt;li>have a developer tenant&lt;/li>
&lt;li>have enabled upload of custom Teams apps&lt;/li>
&lt;li>have Visual Studio Code installed&lt;/li>
&lt;/ul>
&lt;p>which I already had so that I only needed to&lt;/p>
&lt;ul>
&lt;li>have Microsoft Teams Toolkit installed&lt;/li>
&lt;li>have Node.js installed&lt;/li>
&lt;li>have setup Ngrok&lt;/li>
&lt;/ul>
&lt;p>I installed the software, signed up for Ngrok, got my auth token and ran the 4 commands to set it up.&lt;/p>
&lt;h2 id="custom-teams-tab">Custom Teams Tab&lt;/h2>
&lt;p>After signing in (dev tenant), I created a new Teams app with capability TAB and ran `npm install` and `npm start` in Terminal. Couple of minutes later () I created a tunnel by running `Ngrok http 3000` and replaced a placeholder base URL with my NGrok URL so that I would able to test my app later on.&lt;/p>
&lt;h2 id="initialize-mgt-and-auth-page">Initialize MGT and auth page&lt;/h2>
&lt;ul>
&lt;li>Register my app in Azure Active Directory&lt;/li>
&lt;/ul>
&lt;p>I registered my app in AAD (for everyone who never did that: this is literally 5 clicks plus copy-paste of the Ngrok URL for redirect URI) and copied my client ID which I would need in the next step.&lt;/p>
&lt;ul>
&lt;li>Add MGT and build an auth pop-up page&lt;/li>
&lt;/ul>
&lt;p>To add the MGT I needed to add references to the Teams JS client SDK and the Teams provider (no worries, just copy paste two lines of code). After that, I initialized the provider and replaced a placeholder ID with the AAD app client ID.&lt;/p>
&lt;ul>
&lt;li>For the pop-up page I created a new `auth.html` file and copy-paste the code provided to call the `TeamsProvider.handleAuth()` method.&lt;/li>
&lt;/ul>
&lt;h2 id="design-my-tab">Design my tab&lt;/h2>
&lt;p>Now it was time to already add the components (remember, LEGO bricks!) to my `index.html`&lt;/p>
&lt;p>After creating three columns, I added provided CSS code to my `index.css` file. In those 3 columns I added the components for agenda, tasks (from planner) and mails (with the placeholder component `get`- code provided in MGT Playground – with which I played a couple of weeks ago already).&lt;/p>
&lt;p>I like pink, which is why I changed some box-shadows in the `index.css`&lt;/p>
&lt;h2 id="test-my-app-in-teams-studio">Test my app in Teams Studio&lt;/h2>
&lt;ul>
&lt;li>Before I wanted to upload my app, I wanted to test the `manifest.json` file using the `validation` tab in App Studio – it returned with 0 errors- YAY!&lt;/li>
&lt;li>To build and run my app, I ran `npm start` and imported my app in App Studio and installed it.&lt;/li>
&lt;/ul>
&lt;p>Time to try it out!&lt;/p>
&lt;ul>
&lt;li>Click in the Left rail on `OneProductivityHub` app (this is the one I made – how cool it that?!)&lt;/li>
&lt;li>Click the SIGN IN button in the pop-up page and sign in with m username/password and consent on behalf of my organization. BOOM! I see my agenda, planner tasks assigned to me and my mails!&lt;/li>
&lt;/ul>
&lt;h1 id="conclusion-of-attending-2-bootcamps">Conclusion of attending 2 Bootcamps&lt;/h1>
&lt;p>Yes, I built 2 apps, but more than that happened:&lt;/p>
&lt;ul>
&lt;li>community spirit&lt;/li>
&lt;/ul>
&lt;p>This Bootcamp was absolutely outstanding, as it was not “yet another online-conference”. I really felt connected and in the middle of co-workers, who all wanted to achieve something cool together. I can totally recommend this experience. This was truly amazing, not only because of the good and well-prepared content, the very responsive teams of Bob &amp;amp; Ayca and their proctors, but also because of the community-spirit.&lt;/p>
&lt;ul>
&lt;li>learning&lt;/li>
&lt;/ul>
&lt;p>Not only did I learn a ton (speaking of it; I learned a lot about myself, my problem solving skills, my ability to focus and unstuck if I am stuck) and gained experience in building real-world scenarios (both Bob and Ayca put all those learning nuggets together into solutions that really makes sense!) but I also connected with other M365 Developers who shared their struggles, learning, journey so that we were already peers when it was time to celebrate success! THAT feeling when Bob and Ayca asked in the chat, if anyone already completed the last exercise and I saw more and more confirming messages coming in until I finally could say: YES! I made it as well.&lt;/p>
&lt;ul>
&lt;li>
&lt;div>
&lt;p>
4 Fun Facts:
&lt;/p>
&lt;div>
&lt;img loading="lazy" class="w-100" src="https://m365princess.com/wp-content/uploads/2020/10/hairdresser.png" alt="" width="404" height="710" srcset="https://m365princess.com/wp-content/uploads/2020/10/hairdresser.png 996w, https://m365princess.com/wp-content/uploads/2020/10/hairdresser-171x300.png 171w, https://m365princess.com/wp-content/uploads/2020/10/hairdresser-582x1024.png 582w, https://m365princess.com/wp-content/uploads/2020/10/hairdresser-768x1351.png 768w, https://m365princess.com/wp-content/uploads/2020/10/hairdresser-873x1536.png 873w" sizes="(max-width: 404px) 100vw, 404px" />
&lt;/div>
&lt;ul>
&lt;li>
I attended the (first) BootCamp working on my 13&amp;#8243; Laptop &lt;a href="https://twitter.com/LuiseFreese/status/1316002807317762048?s=20" target="_blank" rel="noopener noreferrer">while sitting at my hairdresser&lt;/a>
&lt;/li>
&lt;li>
I finished the 2nd BootCamp way ahead of time
&lt;/li>
&lt;li>
I am not a developer, but I can obviously develop apps
&lt;/li>
&lt;li>
2 days after Bootcamp, one of my customers asked me to help deploy some Teams applications &amp;#8211; feeling confident enough to do that!
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>Why is this important to you?&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>If you are Maker/Citizen Developer wondering, where is the limit for you, I can tell you out of experience, that the limit is not Power Platform. You can build apps for Teams &amp;amp; SharePoint, if you feel comfortable with low-code development. You can expand your expertise with Microsoft Graph Toolkit or with Teams app templates. These solutions use the same approach as Power Apps – they reduce complexity because you stand on the shoulders of giants who give you ready-to-use bricks which you only need to put together. There is a lot of fuzz about how we distinguish between no-code, low-code and pro-code. I think it doesn’t really matter where we draw lines and if we really need to do that or if we evolve into a thinking to empower every developer regardless of their technical background or experience. Those Bootcamps are very beginner-friendly, but will still get makers out of their comfort zone to extend their comfort zones. I think this is a good thing, because at the end of comfort zone is where the magic happens. Probably I would not have needed these Bootcamps to get started with building real-world scenarios. All inputs needed were already available on docs.microsoft.com but this guided experience was a good start for me. Get more info on M365Bootcamps on twitter and &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/bootcamps" target="_blank" rel="noopener noreferrer">M365 Bootcamps&lt;/a>. Recording of Bob’s workshop is also &lt;a href="https://www.youtube.com/watch?v=JaQSJsxOS0E&amp;feature=youtu.be" target="_blank" rel="noopener noreferrer">available here.&lt;/a>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/JaQSJsxOS0E?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div>
&lt;/p></description></item><item><title>Power Platform Licensing QnA – Key take-aways</title><link>https://m365princess.com/blogs/2020-10-09-power-platform-licensing-qna-key-take-aways/</link><pubDate>Fri, 09 Oct 2020 12:58:56 +0000</pubDate><guid>https://m365princess.com/blogs/2020-10-09-power-platform-licensing-qna-key-take-aways/</guid><description>&lt;p>Early October 2020, I hosted together with Elio Struyf a QnA session about Power Platform licensing. As experts, we invited Ryan Cunningham and Chris Huntingford. Upfront, we asked Tech Community on twitter, what they don&amp;rsquo;t understand in terms of licensing, because we couldn&amp;rsquo;t explain it in detail as well. Session was scheduled to be ~ 60 minutes, but we extended to 90 minutes – thanks again to our speakers!&lt;/p>
&lt;p>Here are our key-take-aways which shall make it easier for you to get an overview about licensing in Power Platform:&lt;/p>
&lt;ul>
&lt;li>
&lt;div>
We distinguish between Power Apps for Office 365 and Power Apps Standalone licensing.
&lt;/div>
&lt;ul>
&lt;li>Power Apps for Office 365 means customizing and extending Office with low code. We can build and use unlimited amount of apps using SharePoint, Excel and dozens of other standard connectors. New to this party: Project Oakdale in Teams (CDS light)&lt;/li>
&lt;li>Power Apps Standalone means enterprise LOB applications backed by enterprise data. We get the FULL CDS, all premium connectors and on-prem gateways.&lt;/li>
&lt;li>Standard license includes all things on the Microsoft 365 platform. If you want to go beyond, it&amp;rsquo;s premium and you will need the Standalone license.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>The &amp;lsquo;all you can eat buffet&amp;rsquo; approach = Per User = 20 USD per user per month&lt;/p>
&lt;ul>
&lt;li>
&lt;p>The à la carte menu approach = Per App = 5 USD per user per app per month &lt;div>
For Power Apps Standalone we have two options:&lt;/p>
&lt;/div>
&lt;p>Discounts available at scale&lt;/li> &lt;/ul> &lt;/li> &lt;/ul>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;img class="w-100" src="https://m365princess.com/wp-content/uploads/2020/10/100920_1246_PowerPlatfo1.png" alt="" />
&lt;ul>
&lt;li>No user needs to worry to not have the right license – if they don&amp;rsquo;t have a premium license but it is required to run an app or a flow, this will prompt a message for a 30 day trial. Admins get reports about those trials&lt;/li>
&lt;li>Any flow that&amp;rsquo;s working on the same data as the app, doesn&amp;rsquo;t require an additional license for the flow: We don&amp;rsquo;t need multiple licenses just to run one use case.&lt;/li>
&lt;li>It&amp;rsquo;s so friggin&amp;rsquo; expensive because it has WAY more power than we understand – time to educate us! Although it provides a lot of value in the long run, lots of customers start small, and do not eat the whole cake at once.&lt;/li>
&lt;/ul>
&lt;img class="w-100" src="https://m365princess.com/wp-content/uploads/2020/10/100920_1246_PowerPlatfo2.png" alt="" />
&lt;ul>
&lt;li>Dataverse for Teams is basically Dataverse speaking of database and environment but with a more simple security model, but it&amp;rsquo;s the basic structure of a really high scale relational database with tables and the ability to use them in an app.&lt;/li>
&lt;li>For AI Builder, there is a gorgeous calculator! [&lt;span style="font-family: Segoe UI;">https://powerapps.microsoft.com/en-us/ai-builder-calculator/&lt;/span>][2]&lt;/li>
&lt;li>Power Platform is a maker platform, but it is a good place for developers to explore. Licensing for HTTP triggers will likely not change, which might keep most developers away as they will need Premium for simple things they want to automate. Dataverse for Teams solves a part of this licensing issue, but when not working within Teams, you still have to pay.&lt;/li>
&lt;li>Multiplexing is not a drinking game, but an approach to get around an (expensive) Dynamics license. If you don&amp;rsquo;t have Dynamics, you don&amp;rsquo;t need to worry about it!&lt;span style="color: #323130; font-family: Times New Roman; font-size: 10pt;">&lt;br /> &lt;/span>&lt;/li>
&lt;/ul></description></item><item><title>About Me</title><link>https://m365princess.com/about/</link><pubDate>Thu, 24 Sep 2020 11:07:10 +0600</pubDate><guid>https://m365princess.com/about/</guid><description>&lt;h2 id="i-help-organizations-with-azure--power-platform---but-without-the-fluff">I help organizations with Azure &amp;amp; Power Platform - but without the fluff!&lt;/h2>
&lt;ul>
&lt;li>Getting started securely&lt;/li>
&lt;li>Building and shipping solutions that are performant, secure, accessible and intuitive to use&lt;/li>
&lt;li>Understanding and making the best out of licensing&lt;/li>
&lt;li>Extending low-code with code-first approaches when needed&lt;/li>
&lt;/ul>
&lt;h2 id="im-not-your-usual-consultant">I&amp;rsquo;m not your usual consultant&lt;/h2>
&lt;p>If you want to work with me, we will work radically - meaning we will always get to the root of the causes instead of just treating symptoms. I am accurate and creative, a serial critical thinker and professional learner. A princess, punk at heart and advocate for doing the right things in the right way. Half-baked solutions aren&amp;rsquo;t my thing. I do what I say and say what I think.&lt;/p>
&lt;p>If you are interested in getting to know what exactly I can help you with: Here are some ideas: &lt;a href="https://www.m365princess.com/work-with-me/">work with me&lt;/a>.&lt;/p>
&lt;p>What are you waiting for?&lt;/p></description></item><item><title>Please don’t “hello” me! Why “hello” in Microsoft Teams isn’t polite!</title><link>https://m365princess.com/blogs/2020-08-04-please-dont-hello-me-why-hello-in-microsoft-teams-isnt-polite/</link><pubDate>Tue, 04 Aug 2020 12:46:14 +0000</pubDate><guid>https://m365princess.com/blogs/2020-08-04-please-dont-hello-me-why-hello-in-microsoft-teams-isnt-polite/</guid><description>&lt;p>I recently stumbled over nohello.com and aka.ms/nohello, which refers to &lt;a href="https://github.com/sbmueller/nohello/blob/master/index.md">https://github.com/sbmueller/nohello/blob/master/index.md#please-dont-say-just-hello-in-chat&lt;/a> by &lt;a href="https://sbmueller.github.io/">https://sbmueller.github.io/&lt;/a>. The author states very clearly, that just saying “hello” in a chat (at work) and then waiting for the other one to reply is not the best way to act.&lt;/p>
&lt;p>&lt;a href="https://twitter.com/LuiseFreese/status/1290278767168860160?s=20">After tweeting about it&lt;/a>, I found myself in a quite controversial discussion and thought it could be helpful to explain my thoughts in a more persistent way-and with more than just 280 characters.&lt;/p>
&lt;h1 id="why-do-you-8220hello8221-people">Why do you “hello” people?&lt;/h1>
&lt;p>Which purpose could it serve? I asked on twitter, got following answers and I would love to dig a bit deeper into them:&lt;/p>
&lt;ul>
&lt;li>Being polite/ first greeting the other one&lt;/li>
&lt;li>Seeing if they are available for synchronous chat&lt;/li>
&lt;li>Control, if their status represents their actual work situation&lt;/li>
&lt;li>Control, if you are still on their “VIP” list of people to be always answered first&lt;/li>
&lt;li>Jump the queue, similar to “did you read my email?&lt;/li>
&lt;li>Control if it’s a good time to share confidential information&lt;/li>
&lt;/ul>
&lt;p>Let’s have a look why all of them are bad and how there are better alternatives&lt;/p>
&lt;h2 id="being-polite">Being polite&lt;/h2>
&lt;p>&lt;span style="font-size: 12pt;">I am sure, you want to be polite. But in chat tools, there is no need to greet the other person like there is no reason to add your signature / contact details. To understand, why we used to do this and why it is not only mot necessary anymore but also harming our colleagues, it’s quite good to understand the roots of this behaviour – as the intention “being polite” seems to be valid and just good.&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">Greeting is a relict of times in which we tried to translate our analogue world (writing letters) to a digital world (writing emails). We all know, that this worked quite good for communication with customers and partners (externals), as email is just a simple way to exchange text and files if you don’t work together in a collaboration solution (like Microsoft Teams). It evolved into a nightmare within companies (internal), as we developed quite unhealthy habits which were the result of an information hiding culture. As everyone made themselves a bottleneck of knowledge and with glorifying busy (and confusing busy with productive), we came into the habit of forwarding the pressure that we got to the next one in the chain of commands by the devilish trinity of bad behaviour in the workplace:&lt;br /> &lt;/span>&lt;/p>
&lt;h3 id="1-8220did-you-read-my-email8221">1. “Did you read my email?”&lt;/h3>
&lt;p>&lt;span style="font-size: 12pt;">Throw a stone, if you never said that to a colleague. “Did you read my email” doesn’t mean “Did you read my email”. It can be translated to:&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">“I gave you a task. And I know that you received it as our mail servers work. But I want you to confirm when you will care about me. So basically, I am jumping the queue and bring you into the position of justifying why you didn’t reply YET.”&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">Pro Tipp: You can even add some extra flavour when hitting the send button WHILE calling a colleague “did you already see? I sent you an email. What? Didn’t arrive yet? Just refresh!”. It’s the ultimate way to put pressure on others and its totally unproductive at the same time as you interrupt others and force them into task switching, which is energy draining for our brains and leads to slower work while more mistakes occur. Please don’t say it. Don’t even try to “did you read my Teams message?”. Thank you.&lt;br /> &lt;/span>&lt;/p>
&lt;h3 id="2-checking-inbox-up-to-100-times-a-day">2. Checking inbox up to 100 times a day&lt;/h3>
&lt;p>&lt;span style="font-size: 12pt;">A lack of purpose in work (we feel disconnected and don’t see the whole picture) leads to a misunderstood sense of duty which results in living in your inbox (doesn’t matter if you call your inbox Outlook or a chat in Teams – if you just put your old ways of working into a new tool, you didn’t win at Digital Transformation!) and checking for new mail in an insane frequency. Of course, this is supported by a default setting in Outlook which gives you a desktop notification for every single mail that you receive.&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">(ICYMI: you can turn that off:&lt;br /> &lt;/span>&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://m365princess.com/wp-content/uploads/2020/08/080420_1211_Pleasedonth1.png" alt="">
&lt;span style="font-size: 12pt;">&lt;br /> &lt;/span>
&lt;p>&lt;span style="font-size: 12pt;">File &lt;span style="font-family: Wingdings;">à&lt;/span> Options &lt;span style="font-family: Wingdings;">à&lt;/span> mail &lt;span style="font-family: Wingdings;">à&lt;/span>uncheck “display desktop alert” – #EnjoyTheSilence ). Constantly interrupting others and expecting fast response times didn’t help with productivity, just with staying busy.&lt;br /> &lt;/span>&lt;/p>
&lt;h3 id="3-being-the-bottleneck--abusing-mail-messages-for-task-management">3. Being the bottleneck / abusing mail /messages for task management&lt;/h3>
&lt;p>&lt;span style="font-size: 12pt;">The “my knowledge is my power” approach doesn’t come at no costs. Not only that sharing knowledge is beneficial for the whole company, it’s just time consuming, if we try to hide information and act like the gatekeeper to – for instance – a project status. If we don’t share information, we will always be busy with individually answering questions. I wouldn’t call this very efficient working behaviour. You can see, that a lot with people abusing mail or messages (again, doesn’t matter if you show this behaviour in Teams!) for task management, because they misunderstand what “polite” means. It’s is not polite, to “ask” your colleagues to accomplish a task that they are responsible for. It gives them – additionally to their task, the task to manage this task accordingly. Assigning the task in a task management system (like Planner) handles responsibilities quite effective. A task has some properties which can easily answer the most common question around this task like&lt;br /> &lt;/span>&lt;/p>
&lt;ul>
&lt;li>&lt;span style="font-size: 12pt;">“whom is it assigned to?”,&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">“which category does it belong to?”&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">“in which bucket will I find it?”&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">“when is the due date?” and&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">“what is the status of this task?”&lt;br /> &lt;/span>&lt;/li>
&lt;/ul>
&lt;p>&lt;span style="font-size: 12pt;">WITHOUT sending mails/messages back and forth. So your so-called politeness resulted in more work for you and others. I would call that a waste of time. If you now say: “But what if we need to exchange our views and really have a conversation?” Then, please consider that a synchronous scheduled conversation (in chat or call) might be the better approach.&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">Polite to means:&lt;br /> &lt;/span>&lt;/p>
&lt;ul>
&lt;li>&lt;span style="font-size: 12pt;">Not causing extra work&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">Not putting pressure on me&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">Sharing your knowledge&lt;br /> &lt;/span>&lt;/li>
&lt;/ul>
&lt;p>&lt;span style="font-size: 12pt;">As this behaviour will be beneficial for me. Sticking to old behaviours and not being willing to transform and evolve how we work is not polite, its just very yesterday and fa sign of a mixed mindset.&lt;br /> &lt;/span>&lt;/p>
&lt;h2 id="looking-if-the-other-one-has-time-for-a-synchronous-chat">Looking, if the other one has time for a synchronous chat&lt;/h2>
&lt;p>&lt;span style="font-size: 12pt;">Asking “Hey, do you have 10 minutes to give me an update regarding Project Deathstar” is at least a bit more specific, but please keep an eye on a small detail: Those closed questions (that can be answered with yes/no) come often with an expectation to be answered immediately (as it is assumed that it doesn’t take a lot of effort to type 2,3 letters), but we all know, that an interruption will steal way more than just the few seconds of seeing the message and answering it with “no”. It will take up to 20 minutes until you are as focused on the thing you wanted to do before you got interrupted. This lack of focus and the quality of your work and your wellbeing are tightly connected&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">Means: The more interruptions, the less stuff you get done and the more tired you feel.&lt;br /> The more time you can spend on deep work, the more you accomplish and the better you feel.&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">You already interrupted the other one and perhaps they know that they now need to time to re-focus and give in, answering: No, I wanted to do something else, but as you already interrupted me, shoot”. And perhaps, you already knew this…&lt;br /> &lt;/span>&lt;/p>
&lt;p>&lt;span style="font-size: 12pt;">If you ask an open question: “when do you have time to talk about Project Deathstar” you will end up with writing back and forth messages about the best time… If there only was a good way to schedule meetings… Oh wait, there is!&lt;br /> &lt;/span>&lt;/p>
&lt;ol style="margin-left: 54pt;">
&lt;li>
&lt;span style="font-size: 12pt;">Outlook Calendar can show the availability of your colleagues, you can also use FindTime and Microsoft Bookings (this is what I do and it works like a charm!)&lt;br /> &lt;/span>
&lt;/li>
&lt;li>
&lt;span style="font-size: 12pt;">You can learn a lot about your co-workers availability by just reading their status. It works like a traffic light, a concept that we are already familiar with.&lt;br /> &lt;/span>
&lt;/li>
&lt;/ol>
&lt;h2 id="control-if-their-status-really-represents-their-work-situation--if-you-are-still-their-vip">Control, if their status really represents their work situation / if you are still their VIP&lt;/h2>
&lt;p>&lt;span style="font-size: 12pt;">If my status is “busy” and you ask me, if I am REALLY busy, this won’t improve our relationship. It reminds me a bit about sleeping and being asked if I am already/still sleeping. Its an expression of not respecting and not trusting, which is both toxic. Even worse, if your colleagues /managers? Just want to control, if you are still on their “VIP” list of people to be always answered immediately. Leadership doesn’t mean micro-management. As we don’t want to repeat mistakes that have their roots in processes from 1910 ( Henry Ford), we also need to evolve leadership and learn to trust our employees. Especially in #wfh times its just disrespectful to check, if your team members are “still” working.&lt;br /> &lt;/span>&lt;/p>
&lt;h2 id="jumping-the-queue">Jumping the queue&lt;/h2>
&lt;p>&lt;span style="font-size: 12pt;">You possible wrote your question – even @ mentioning them in a channel, perhaps you assigned them a task in Planner or wrote them an email – and now you really think that it is a good idea to write “Hello” in a private chat to check if they respond and if so – ask them when they will answer your question? Please mind, that you are part of the problem and not part of the solution. Jumping the queue is not only disrespectful and showing your colleagues that you don’t trust them, its also counterproductive as it leads to the next interruptions, which… nah, I won’t tell you again… You got this! Just break this vicious circle.&lt;br /> &lt;/span>&lt;/p>
&lt;h2 id="control-if-it8217s-a-good-time-to-share-confidential-information">Control if it’s a good time to share confidential information&lt;/h2>
&lt;p>&lt;span style="font-size: 12pt;">This one surprised me the most. If I share or duplicate my screen, I am automagically on DND, which means, that I don’t get notifications and don’t expose information unintentionally to my audience. If it’s very common to me, that someone else shares super high-confidential information with me, I don’t let others take a look over my shoulder. I think it’s the recipients responsibility to secure what happens on their screen like we expect everyone to lock their machines once there are not sitting in front of them.&lt;br /> &lt;/span>&lt;/p>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>Talk with your team members on a regular basis about the right tools to use and how you want to use them. Educate each others and learn about different expectations regarding working styles and response times. Just because I work crazy hours, doesn’t mean you should as well! A good starting point towards excellent teamwork is to have a collaboration contract. If you are not familiar with that – as your Microsoft 365 Consultant about it – or read this &lt;a href="https://sway.office.com/tewankw51rkJ0E2w?ref=Link%2f" target="_blank" rel="noopener noreferrer">article&lt;/a> by my MVP colleagues Loryan Strant and Sue Hanley. Please also read &lt;a href="https://regarding365.com/managing-your-availability-in-microsoft-teams-modern-workplace-scenarios-9a54940bbe5e" target="_blank" rel="noopener noreferrer">Darrell as a Service’s post about availability&lt;/a>– he might have a different approach but I highly recommend it!&lt;/p>
&lt;img src="https://m365princess.com/wp-content/uploads/2020/08/080420_1211_Pleasedonth2.png" alt="" align="left" />
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;p> &lt;/p>
&lt;h1 id="tldr">TL;DR?&lt;/h1>
&lt;p>&lt;span style="font-size: 12pt;">What should you do?&lt;br /> &lt;/span>&lt;/p>
&lt;ol>
&lt;li>&lt;span style="font-size: 12pt;">Don’t “hello” people.&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">Setup your status message in Teams&lt;br /> &lt;/span>&lt;/li>
&lt;li>&lt;span style="font-size: 12pt;">Share this blog post as often as you can &lt;/span>&lt;/li>
&lt;/ol>
&lt;p> &lt;/p></description></item><item><title>Lego &amp; Community – Interviews about building and learning</title><link>https://m365princess.com/blogs/2020-06-10-lego-and-community/</link><pubDate>Wed, 10 Jun 2020 11:24:57 +0000</pubDate><guid>https://m365princess.com/blogs/2020-06-10-lego-and-community/</guid><description>&lt;p>After &lt;a href="https://m365princess.com/category/power-platform/">some blog posts about Power Apps&lt;/a> and my personal &lt;a href="https://m365princess.com/category/microsoft-graph/">Microsoft Graph challenge&lt;/a>, I am excited to announce that I will run a Community interview series about Lego.&lt;/p>
&lt;p>I noticed that there are a lot of Lego addicts in my filter bubble, and I found it interesting to know how they think about Lego.&lt;/p>
&lt;p>&lt;a href="https://twitter.com/LuiseFreese/status/1270631938638872576?s=20">So I asked on twitter&lt;/a> whom I should know – apart from the usual suspects that were already in my mind.&lt;/p>
&lt;p>Meet my interview partners here and answer the questions as well:&lt;/p>
&lt;p>&lt;a href="https://bit.ly/LegoAddicts" target="_blank" rel="noopener noreferrer">&lt;a href="https://bit.ly/LegoAddicts">https://bit.ly/LegoAddicts&lt;/a>&lt;/a>&lt;/p>
&lt;div class="w-100">
&lt;figure class="aligncenter size-large">&lt;img loading="lazy" width="409" height="545" class="w-100" src="https://m365princess.com/wp-content/uploads/2020/06/061020_1040_LegoandComm1.jpg" alt="" srcset="https://m365princess.com/wp-content/uploads/2020/06/061020_1040_LegoandComm1.jpg 409w, https://m365princess.com/wp-content/uploads/2020/06/061020_1040_LegoandComm1-225x300.jpg 225w" sizes="(max-width: 409px) 100vw, 409px" />&lt;/figure>
&lt;/div>
&lt;h2 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomeliostruyf-target_blank-relnoopener-noreferrerelio-struyfaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/ElioStruyf" target="_blank" rel="noopener noreferrer">Elio Struyf&lt;/a>&lt;/span>&lt;/h2>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point? &lt;/strong>&lt;/p>
&lt;p>As a kid, I did not play so much with Lego. I wanted to, but I was not in a fortunate situation where I was able to just choose what I wanted to play with. Luckily, I do not have traumas from this, but I now might have an urge to catch up with things I missed in my childhood. Now that I have kids myself, it is fun to share the same hobby. Although my kids know that the Lego from daddy is different from theirs. They can play/break/recreate with their own Lego, but the sets that are in the office, stay in the office.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How? &lt;/strong>
I always loved Lego, but when Lego started with the Brickheadz series. I just had to collect them all.   During the Corona/COVID-19 crisis, I started to watch Star Wars (I know that I&amp;rsquo;m late to the game). After the first movie, I knew that this was a bad idea. Now I have an office full of unopened Star Wars Lego sets and various minifigs. Every time I make a set from Star Wars if feels like it was created especially for Lego. It all fits so nice together.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with? &lt;/strong>&lt;/p>
&lt;p>Struggle is a big word, but the Bugatti and the new Lamborghini are two sets where you need to keep focused. The engine consists of so many parts that you can easily miss a step or accidentally put something wrong. One thing you do not want to do is break a Lego Technics set apart.&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Most of the time I do it on my own. Sometimes I get help from my kids.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO? &lt;/strong>&lt;/p>
&lt;p>Don&amp;rsquo;t know, Lego is just so cool, and there is no age limit on creativity. Think that brings us all together, finding solutions for problems, and start building them. This process is also what you apply to Lego. I also love it sometimes to open the Bricklink Studio app and start creating Brickheadz from scratch. Always cool to order the bricks and start building it in real afterward.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Coming up with creative solutions for issues you face during your work, but more important is the calm Lego gives me. I can use it to free my head and look at how brick by brick it comes together. Once I a set finished a set, it goes to my office. Sometimes I walk in my office to take a look and smile. It gives me internal joy and happiness. Think that is what matters the most, and a happy person results in making other people in your team happy.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>At the moment, I am building the Lamborghini. The next set will probably be the Star Wars Stormtrooper Helmet (actually a really cool series).&lt;/p>
&lt;hr />
&lt;h2 id="meet-a-hrefhttpstwittercomgirlgemsspan-stylecolor-0563c1-text-decoration-underlinespan-stylecolor-000000-text-decoration-underlinejess-dodsonspanspana">Meet &lt;a href="https://twitter.com/girlgems">&lt;span style="color: #0563c1; text-decoration: underline;">&lt;span style="color: #000000; text-decoration: underline;">Jess Dodson&lt;/span>&lt;/span>&lt;/a>&lt;/h2>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Really young age – I remember having Lego in the house from the time I was about 5. I&amp;rsquo;m a twin and my brother (lucky!!!) got the Black Monarch&amp;rsquo;s Castle (6085) and I remember LOVING playing with it – the drawbridge, the dungeon, the knights on their horses, the winch to pull up the gate. As I got older, I got some of the “girlier” sets – my first memories of sets that were truly “mine” were of the Paradisa series. I was given Poolside Paradise (6416) and loved the new shapes and the smaller details. The parrot!&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I&amp;rsquo;m not sure when I stopped – probably in teenage years? I don&amp;rsquo;t remember there being a specific point, I just don&amp;rsquo;t remember having Lego around.&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I rediscovered Lego in a really unusual way. A fellow geek friend was in hospital in 2010 and I didn&amp;rsquo;t want to do the boring thing and bring him flowers or chocolates. I knew that he&amp;rsquo;d be itching to &lt;em>DO&lt;/em> something. So, knowing he&amp;rsquo;s an engineer and he likes motorcycles, I bought him a Lego Technic motorbike (8051). I got to see it when it was built and thought it looked amazing. So I went and bought the same set. And I was hooked. Started off with Technic, moved into Lego City and then got drawn into Lego Star Wars UCS after I went to a Lego Expo and purchased an Imperial Shuttle (10212) UCS set that I fell in love with building. And I&amp;rsquo;ve been collecting ever since! I collect Lego Star Wars UCS, Expert Creator &amp;amp; Lego Ideas, as well as few limited run series like Monster Fighters &amp;amp; Lego Movie – I&amp;rsquo;m a huge Unikitty fan &lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">The Millennium Falcon UCS (75192) was probably my longest build. My daughter was only 3 months old when I first got it so it took me just over 2 months to complete…with her “helping” of course! A full photo set of the build:&lt;a href="https://photos.app.goo.gl/mhxS8MVpei2BFDu42">&lt;br />&lt;span style="color: #000000;">&lt;a href="https://photos.app.goo.gl/mhxS8MVpei2BFDu42">https://photos.app.goo.gl/mhxS8MVpei2BFDu42&lt;/a>&lt;/span>&lt;/a>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I also had some issues with the R2D2 (10225) set – I put his legs on backwards so I had to completely take him apart and start again, which was incredibly frustrating! Lesson learned!&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I build alone, because I love knolling (&lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://makezine.com/2016/12/07/zen-and-the-art-of-knolling/">&lt;a href="https://makezine.com/2016/12/07/zen-and-the-art-of-knolling/">https://makezine.com/2016/12/07/zen-and-the-art-of-knolling/&lt;/a>&lt;/a>&lt;/span>) and that&amp;rsquo;s half the fun of some of the larger sets! I use it as my escape, as my wind-down. My husband supports me (and spends way too much on me for birthdays and Christmas!) but he&amp;rsquo;s not a Lego addict like I am.&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you think it&amp;rsquo;s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Not at all. It&amp;rsquo;s a physical manifestation of the things that we do – building things! It doesn&amp;rsquo;t surprise me in the slightest that so many tech folk are into Lego. There&amp;rsquo;s also the fact that Lego series tend to be of the “geeky” variety – Star Wars, DC Comics, Marvel, Harry Potter, Minecraft – that we can&amp;rsquo;t help but be drawn to it!&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What did you learn from building LEGO for your job?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Follow the instructions CAREFULLY. Read ahead and don&amp;rsquo;t rush. Plan things out. Break things down into smaller segments and it&amp;rsquo;s easier to manage a big project.&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What will be your next set?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I&amp;rsquo;d like my next set to be the Haunted House (10273) or Pirates of Barracuda Bay (21322), but I think it&amp;rsquo;ll like be Duplo Frozen Ice Castle (10899) for my little one, because of course I&amp;rsquo;m going to foster my love of Lego in her!&lt;/span>&lt;/p>
&lt;hr class="wp-block-separator" />
&lt;h2 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpstwittercomdaggermctimbersspan-styletext-decoration-underlinecaptain-daggers-mctimbersspanaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://twitter.com/DaggerMcTimbers">&lt;span style="text-decoration: underline;">Captain Daggers McTimbers&lt;/span>&lt;/a>&lt;/span>&lt;/h2>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I started playing with Lego Duplo when I was really young. Probably around 2 or 3? I then moved onto the more adult sets when I was about 5. I played with it until I was about 11 and took a break, as I was in my teenage years and had other things to focus on.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I rediscovered my love of Lego around 4 years ago, when my ex partner got me the Lego Yellow Submarine set for Christmas. I had so much fun building it I&amp;rsquo;ve made a point of having at least one set in reserve to build whenever I was to!&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">The ship in a bottle, hands down, is the hardest thing I&amp;rsquo;ve built. I have long and pointy natural nails so building all the tiny little pieces of the ship was a challenge!&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I usually almost always build alone, though my partner does like to lend a hand sometimes.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you think it&amp;rsquo;s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Not at all! I think software engineers love Lego because they&amp;rsquo;re building something up that will be a satisfying final piece. I&amp;rsquo;m a hardware engineer so I love how practical it is, much for the same reason. There&amp;rsquo;s fun in following instructions for a lot of people in our industry.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What did you learn from building LEGO for your job? What will be your next set?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Lego has taught me patience. You can&amp;rsquo;t rush it because if you miss one piece, the whole build is ruined and you have to try and work back or start over. It&amp;rsquo;s a solid lesson to be learnt.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;strong>&lt;span style="color: black; font-family: Arial;">What will be your next set?&lt;/span>&lt;br />&lt;/strong>&lt;/p>
&lt;p>&lt;span style="color: black;">&lt;span style="font-family: Arial;">Well, I&amp;rsquo;m hoping to purchase the Pirate Bay set but I&amp;rsquo;m currently out of work so can&amp;rsquo;t afford it. But I have the Milano from Guardians of the Galaxy already, so that will likely come first&lt;/span>&lt;span style="color: #14171a; font-family: Segoe UI; font-size: 11pt; background-color: #e6ecf0;">!&lt;br />&lt;/span>&lt;/span>&lt;/p>
&lt;hr class="wp-block-separator" />
&lt;h2 id="meet-a-hrefhttpstwittercomwaldekmspan-stylecolor-0563c1-text-decoration-underlinespan-stylecolor-000000-text-decoration-underlinewaldek-mastykarzspanspana">Meet &lt;a href="https://twitter.com/waldekm">&lt;span style="color: #0563c1; text-decoration: underline;">&lt;span style="color: #000000; text-decoration: underline;">Waldek Mastykarz&lt;/span>&lt;/span>&lt;/a>&lt;/h2>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Ugh, tough one. I don&amp;rsquo;t know the actual age but it was early. I recall I had this police station set with a helicopter and an officer on a motorcycle. And I think you could turn the helicopter into a police station. I had so much fun with it!&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Did you re-discover Lego? When and How?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">When we got my son, we gave him duplo and later lego. As he was working on his sets I started to look more into it again and thinking, I want to build something too. Obviously I&amp;rsquo;d need my own set for that 😉&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What is the build you struggled the most with?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Nothing really so far, but I haven&amp;rsquo;t built overly complex sets. I think the biggest struggle was trying to rebuild all sets my son has after he took them them apart. Starting of course with finding the right bricks in all the boxes, drawers and cabinets 😉&lt;strong>&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you build alone or with a partner / in a team?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Me and my wife built together a few houses I have on the shelve in my office. It was a lot of fun especially as we combined building with a nice glass of wine. We liked a lot not only the building itself but also discovering all the different pieces of the house, the rooms, architecture and hidden features. Lego has a great attention to detail!&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you think it&amp;rsquo;s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I love to build something out of nothing. I think that&amp;rsquo;s why I code and it could very much be that it&amp;rsquo;s while I love lego!&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I&amp;rsquo;d say patience and accuracy. Also, with the bigger sets, the idea that it will take a while and I don&amp;rsquo;t need to get it all done in one go.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What will be your next set?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I&amp;rsquo;m on the fence here. There are quite a few things I&amp;rsquo;d love to build that I have on my list, like more houses but also I like a lot the Bugatti and Lamborghini. And I&amp;rsquo;d like to try one of the Technic vehicle sets as well&lt;br />&lt;/span>&lt;/p>
&lt;hr class="wp-block-separator" />
&lt;h2 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpstwittercomjamescallaghanspan-styletext-decoration-underlinejames-callaghanspanaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://twitter.com/jamescallaghan">&lt;span style="text-decoration: underline;">James Callaghan&lt;/span>&lt;/a>&lt;/span>&lt;/h2>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Started with the Duplo blocks when I was probably 3-4. Never stopped although the building and playing stopped in my mid/late teens the appreciation and joy didn&amp;rsquo;t.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Did you re-discover Lego? When and How?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">When my daughter arrived in my mid 20&amp;rsquo;s it was the perfect excuse to unbox all my old models I had saved for such a day. Since then I realised you don&amp;rsquo;t need to be a kid to still enjoy building. The joy and experience my daughter has when we took a trip to Legoland is unforgettable and sparked unbelievable memories from my trip when I was young. I&amp;rsquo;ve since brought myself a number I always admired.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I don&amp;rsquo;t recall struggling. I had a strange ability to translate the instructions with ease and had pretty nibble fingers! My recent challenge has actually been down to a number of lost bags from a really big model.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">All of the above. Building on your own is calming and peaceful but building with my partner and daughter is also lots of fun. There is a kind of Lego language – any one see a flat, 4×2, in blue?!?&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>Do you think it&amp;rsquo;s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">I&amp;rsquo;d be interested to know if this is a tech thing or just a life thing. Lego is so globally accessible and known. This can only have increased as they&amp;rsquo;ve extended their range and their kits which are more inclusive then ever.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">Firstly, everyone sees things differently. Simply put the same pile of blocks in front of different people and ask them to build you a car without them seeing what others have created. Secondly, requirements and agility. That same example could be achieve with flat 8×2 and two sets of wheels so know your requirements and your MVP. Lastly, use your imagination. Take time to step back and consider things in a different perspective. There is always a solution out there.&lt;br />&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">&lt;strong>What will be your next set?&lt;br />&lt;/strong>&lt;/span>&lt;/p>
&lt;p>&lt;span style="color: black; font-family: Arial;">My daughter has a real joy for boats. She has rebuilt some of my kits and even a kit my brother passed down to me. For her birthday I may get her a pirate ship so she has her own memories to cherish. I never got into the Technic kits and would love to tackle something like a crane and teach my daughter about gearing and motors etc.&lt;/span>&lt;/p>
&lt;hr />
&lt;/div>
&lt;/div>
&lt;/div>
&lt;h2 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomlaura_gb-target_blank-relnoopener-noreferrerlaura-graham-brownaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/Laura_GB" target="_blank" rel="noopener noreferrer">Laura Graham-Brown&lt;/a>&lt;/span>&lt;/h2>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>Had Lego as a child, can&amp;rsquo;t remember not having it. When I left home at around 19 my Lego didn&amp;rsquo;t move out with me.. 3&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>Re-discovered Lego as my children got old enough so about 15 years ago&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>I find the mindstorm robotic stuff slow to do, I just need to spend time learning the details.&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>often its a family team effort, we buy sets to celebrate things, happy to build alone though&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Tech people like procedures to do things and getting a finished solution, Lego instructions are just like a good blog post. Although we all claim to be radical and non-conformist we like things that do what they say they will do, and using known items in a slightly different way.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>How step by step instructions work, how to write good to-dos. The Lego manual steps that are unclear drive us nuts.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>possibly &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.amazon.co.uk/LEGO-Ideas-21318-Tree-House/dp/B07W3YZ7SY/">&lt;a href="https://www.amazon.co.uk/LEGO-Ideas-21318-Tree-House/dp/B07W3YZ7SY/">https://www.amazon.co.uk/LEGO-Ideas-21318-Tree-House/dp/B07W3YZ7SY/&lt;/a>&lt;/a>&lt;/span> to celebrate end of university year for the kids&lt;/p>
&lt;hr>
&lt;h2 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwiittercommarcduiker-target_blank-relnoopener-noreferrermarc-duikeraspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twiitter.com/marcduiker" target="_blank" rel="noopener noreferrer">Marc Duiker&lt;/a>&lt;/span>&lt;/h2>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I guess I was 6 years old. Also played with Duplo before that. I stopped when I was a teenager.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I got back into Lego in my 30s. I had the opportunity to go to Lego Land in Denmark for work and bought a Technic Lego set there.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>The Saturn rocket took quite some time and I had to redo some small parts because I didn&amp;rsquo;t read the instructions well enough&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Sometimes I build with my two daughters.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>No, I think developers like to build things. And it&amp;rsquo;s a nice contrast with the virtual world.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>RTFM and patience&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>Technic Dodge Charger&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomandrewconnell-target_blank-relnoopener-noreferrerandrew-connellaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/andrewconnell" target="_blank" rel="noopener noreferrer">Andrew Connell&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I started playing with LEGO at a very young age… so young I can&amp;rsquo;t recall! It was definitely before I was 7 years old because my first set was released when I was 7. In fact, I recently started rebuilding that set after finding the instructions and all the pieces at my parent&amp;rsquo;s house!  I think I “grew out of it” around the time of middle school when I was between 13-16. Looking at the age of my kids (15 &amp;amp; 10), I see that happening to them around the same age.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I think it was about the time my kids started getting into the more complicated sets around 7-10 years old…so that would be about 8 years ago. You made me think about this… this means I took about a 23-year break from LEGO. That makes me sad…  But… seeing them work on sets like Star Wars &amp;amp; the City themes got me interested again. I bought my own set so I could work on one with them. Now as they grow out of them, I&amp;rsquo;m really enjoying building complicated sets as it&amp;rsquo;s a form of therapy… LEGOtherapy I call it.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>Ah… easy answer. It&amp;rsquo;s the one I&amp;rsquo;m working on now: the Technics Bugatti! I&amp;rsquo;ve realized I&amp;rsquo;ve made a mistake a few times and gone back to fix it… like from step 430 to 360!&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Always alone… I did build a set with my son once. It was one I was really looking forward to building – the Saturn V Apollo moon rocket. I didn&amp;rsquo;t realize how much I liked the process of building and watching him build my set drove me crazy.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Not at all… I tend to see makers as the ones do like LEGO more than non-makers. Why? You create something which is what makers love to do. Plus, we get to use our hands and see the results of our output. We don&amp;rsquo;t normally get to do that in our day job.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Patience &amp;amp; to enjoy the process. I love the process of building not just LEGO, but in work, LEGO has helped me with patience and to enjoy the building process.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>Either the Technic Lamborghini Sian or the Porsche 911 RSR&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomryanmaclean365-target_blank-relnoopener-noreferrerryan-macleanaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/ryanmaclean365" target="_blank" rel="noopener noreferrer">Ryan Maclean&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I started with Lego Duplo when I was 2-3 years old and migrated to Lego when I was a bit older and had more dexterity (and I could be trusted not to try and eat the pieces…)  I played with Lego for most of my childhood and adolescent years, but I stopped playing with it when I was a teenager (15-16 years old)&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I rediscovered my love for Lego about 6-7 years ago; whilst I&amp;rsquo;d never really fallen out of love with it, I hadn&amp;rsquo;t engaged with it for a long time. I was browsing eBay and saw a really good deal for a set that looked interesting  (42006 – Excavator/Claw Grabber) so I bought it and that reignited my interest.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>I recently built set 42082 – Rough Terrain Crane, and it was challenging as there are six different functions powered by a single motor, so the integration and orientation of the gears is very important to get right&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>for the Technic builds I generally build them myself. If I am building any of the system brick sets (e.g. Harry Potter, Friends Central Perk, etc.) then I tend to build them with my wife.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>I think Lego is probably appealing to tech folk/developers because it is a logical system that can be used to create complex solutions&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>The creativity in developing complex solutions with simple Lego elements has parallels to my job. I can use some of the simple elements of the Power Platform in creative ways to develop solutions for difficult problems&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>I have two sets I picked up recently to build: 42096 – Porsche 911 RSR, and 9398 – 4×4 Crawler.  After those, I&amp;rsquo;d like to buy set 42100 – Liebherr R 9800 Excavator&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomdchristian19-target_blank-relnoopener-noreferrerdaniel-christianaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/dchristian19" target="_blank" rel="noopener noreferrer">Daniel Christian&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>My first Lego set was a Technic at the age of 5 and I haven&amp;rsquo;t stopped yet.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I was very focused on Technic and MindStorm sets that I completely missed the other series. When my boys were young I discovered a whole new series which I had heard of but never played with. The Mixel and Brickheadz were the two new ones.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>Building something out of your own imagination. We built our very own Easter Egg decoration kit using Lego Technic&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Team. Myself and my two boys. One is 6 and the other 9.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Nope, not a coincidence!&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Do a small POC first before going big. You can substitute one part for another.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>The Star Wars Falcon!&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomangeliquegroot7-target_blank-relnoopener-noreferrerangelique-grootenboeraspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/AngeliqueGroot7" target="_blank" rel="noopener noreferrer">Angelique Grootenboer&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>As a kid i did sometimes at school but i was never in to it. And i started this year to like it much more.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>This year i started because my husband wanted to do some builds. But I&amp;rsquo;m now the main LEGO builder in the house ;P.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>With my last build for the engine. That was the Land Rover Jeep.&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>I build alone and ask sometimes help from my husband.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>No because my husband pulled me in to meet tech folks &amp;amp; developers.  In the group I am actually the outsider, because I don&amp;rsquo;t work in IT at all.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Nothing, i&amp;rsquo;m just doing it for fun. And to build some amazing builds.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>I think from Architects Las Vegas or the new build the Lambogini&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomscautomation-target_blank-relnoopener-noreferrerbilly-yorkaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/scautomation" target="_blank" rel="noopener noreferrer">Billy York&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>5? maybe. sometime in my teenage years&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>my parents brought over all my old Lego in a box and my 5 year old loved them&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>correctly putting the plates together on pirates of barracuda bay&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>5? maybe. sometime in my teenage years&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>no&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>not sure, i already had my job upon rediscovering lego.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>purchase? assembly square, build? birches books&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomtechmike2kx-target_blank-relnoopener-noreferrermike-martinaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/techmike2kx" target="_blank" rel="noopener noreferrer">Mike Martin&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>pretty early if remember correctly. I don&amp;rsquo;t remember the exact year , but … i do remember the exact set the The Lego Classic set 374-1 Fire Station &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://brickset.com/sets/374-1/Fire-Station" target="_blank" rel="noopener noreferrer">&lt;a href="https://brickset.com/sets/374-1/Fire-Station">https://brickset.com/sets/374-1/Fire-Station&lt;/a> &lt;/a>&lt;/span>(originally from 1978!! but i think i got it in 1984 ..)&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>i never stopped loving Lego (i still have every catalogue from each year still somewhere …). But i do admit that i started playin again with thanks to my kids… great excuse uh 😉&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>the Lego Technic car from the 1980&amp;rsquo;s : &lt;a href="https://thelegocarblog.com/2011/11/20/lego-technic-8860-car-chassis-review/">https://thelegocarblog.com/2011/11/20/lego-technic-8860-car-chassis-review/&lt;/a>&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>today alone or with my kids&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>nope, it stimulates creativity 🙂&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>thinking outside of the box&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>dunno, but I&amp;rsquo;m hoping for more UCS series on Batman :-p&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomjeschor-target_blank-relnoopener-noreferrerraineraspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/jeschor" target="_blank" rel="noopener noreferrer">Rainer&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>Started with Duplo in the age of 2-3, Lego at about 7, stopped at about 17, restart with 41, hopefully will never end&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>About four years ago due to my twins&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>The Saturn V last weekend was challenging.&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Started with Duplo in the age of 2-3, Lego at about 7, stopped at about 17, restart with 41, hopefully will never end&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Yes-best time to come down in a useful way.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>You can build a lot with the right pieces – but not every piece fits everywhere&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>To be bought: Lamborghini in August OR a Speedchampion set from the new summer 2020 models OR the Crocodile train. To build: many waiting for me (Aston DB, Harley, Forrest Harvester, Ducati…)&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercommartingeuss-target_blank-relnoopener-noreferrermartin-geussaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/MartinGeuss" target="_blank" rel="noopener noreferrer">Martin Geuss&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I don&amp;rsquo;t remember exactly. Lego was just always there&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>In 2013 I rediscovered my love for Lego Technic at the age of 43. It was pure coincidence, I discovered a discounted set and bought it spontaneously.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>I found the Bugatti Chiron very challenging, because the assembly was sometimes very complicated&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Alone&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>It&amp;rsquo;s obvious that it can&amp;rsquo;t be coincidence ;-). But I don&amp;rsquo;t have an explanation&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>To earn success, sometimes you have to go back a few steps and rearrange a few bricks to make the whole thing work&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>Lamborghini Sian FKP 37&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomlimp15000-target_blank-relnoopener-noreferrerthomasaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/limp15000" target="_blank" rel="noopener noreferrer">Thomas&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>3 years old with duplo.then went to lego and technic. Around 16 as a teenager…&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>After moving to my own place after my first long relationship at 25. I just fell back into technics. And since my daughter is 2 we started again to play with Lego.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>None Thst I remember&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>With my kids&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Never thought about it but a friend also in It had bought a cour sets again so definitely not a coincidence.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>At school around 10 we were doing programming on Apple 2 computers and had these cool lego with sensors and buttons. I loved that class. It teaches you patience and when building without a guide it allows me to be creative.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>Hmmm, another speed champions and possibly the Ducati panigale&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomwill_mi77-target_blank-relnoopener-noreferrerwill-thompsonaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/Will_MI77" target="_blank" rel="noopener noreferrer">Will Thompson&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I don&amp;rsquo;t remember when I started! But I&amp;rsquo;m sure it was a *very* early age, certainly at pre-school I was into Duplo and progressed throughout that. Best moment was when a 70-something year old neighbour came round saying she&amp;rsquo;d found “a little box of Lego” in the attic, and it turned out to be a giant trunk with loads of vintage trains (the blue rails!) and city stuff. I transitioned into Games Workshop&amp;rsquo;s tabletop wargames around 12-13 years and starting spending my spare money on that rather than Lego, and kinda dropped both once I moved out of home.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I remember exactly! I was visiting MSFT HQ in 2008 (I&amp;rsquo;d been working for them in the UK for a year or so) and saw the Space Needle set (21003) in Barnes and Noble. I had to buy it! And that got me back into looking at other sets. I started with a couple of other architecture sets, then some of the one-off franchise models like BTTF (21103) and Tron (21314). But it&amp;rsquo;s jumped again since my kids hit 4 years old and got an interest. I&amp;rsquo;m eternally grateful to my parents for saving all my Lego 🙂 I got a few boxes from them so we have a selection of Fabuland, Pirates, and City stuff to play with now!&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>The Black Seas Barracuda (6285) had some odd steps that I remember struggling with as a kid. The masts &amp;amp; rigging never quite fitted straight either – that&amp;rsquo;s been documented by other builders too!&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Either along or with my kids! They love the Fabuland and city kits I&amp;rsquo;ve rediscovered from my parents&amp;rsquo; attic, and are building their own collections of Frozen stuff.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Not at all. Pre-COVID I always had a kit on the go in my office to have something to occupy my hands on long conf calls or to fiddle with if I was thinking through some complex problems. It&amp;rsquo;s quite zen-like in that way!&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>You could make a ton of analogies between building LEGO and building Power BI – being detail oriented, good aesthetic design, a final output that is delightful to use etc. And of course a *wonderful* community of likeminded people!&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>I&amp;rsquo;ve had my eye on the ISS (7647) since getting the Apollo Lander (10266), but I&amp;rsquo;m also on the lookout for the Saturn V rocket (21309) if I see that again. Pretty sure it&amp;rsquo;s out of print now though…&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomrandyatwork-target_blank-relnoopener-noreferrerrandy-perkins-smartaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/RandyAtWork" target="_blank" rel="noopener noreferrer">Randy Perkins-Smart&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>5, I haven&amp;rsquo;t stopped.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>N/A&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>Lego Technic 8852 Robot&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Alone.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>No.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Things can be fixed.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>Technic Lamborghini Sián FKP 37&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomstacy_cash-target_blank-relnoopener-noreferrerstacy-cashmoreaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/Stacy_Cash" target="_blank" rel="noopener noreferrer">Stacy Cashmore&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I loved Lego growing up! And I was lucky enough that friends of my parents had older children, so I got a lot of “hand me down” sets without having to go through all of my pocket money!  I stopped at some point in my teenage years. Back then, certainly where I grew up, it was considered to be a children&amp;rsquo;s toy.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>Good question… Well, my first set as an adult was the camper, 10220, so it was probably around 2012.  I saw the camper in a shop and was smitten- I&amp;rsquo;ve great memories from VW bashes in the UK from when I was a teenager, so nostalgia hit me hard. And let&amp;rsquo;s face it, it&amp;rsquo;s a beautiful set!  Building it was amazing, the details that were put into the model (the engine, the T-Shirt in the window and the folding bed!). And the finished model is still in the living room on display!  There was maybe a year or two between that and my next model, but from that point on I was trying to figure out what to build next&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>Without a doubt the 2017 USC Millennium Falcon 75192! That took months to complete and there were more than a few times where we had to redo a few steps&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>It depends on the model.  Smaller models, or models that not everyone in the family is interested in, we build alone. We all have some of our own sets, but we the bigger ones we build as a family. Taking it time to search for bits, read instructions or build. That&amp;rsquo;s how we built the Falcon and the modular buildings. My son has been building with us since he was 5 – he actually built a number of complex pieces for the Falcon without waiting for the instructions (he cheated and read the book, cutting his parents out of the loop!)&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Good question… I don&amp;rsquo;t think so. Personally, I love the flow of making a beautiful Lego model out of a pile of bricks, and seeing how some of the really complex structures are actually made. And just sheer the detail on the sets when done. It&amp;rsquo;s not unlike the feeling when I make software, seeing the end result, and figuring out how to join it all together. As well as that I know many people, not in tech, who laugh at me for being a geek, and include my Lego collection in that. Maybe geeks worry about that kind of judgement less and so don&amp;rsquo;t have a hang up about spending hours “playing with childrens toys”…&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Don&amp;rsquo;t rush, step by step and make sure you are paying attention! Building with the family has really taught me about communication, how easy it is to be misunderstood and how when making something you need to have a shared vocabulary.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>We have the Parisian Restaurant (10243) waiting to be opened as a family, and the Batmobile is calling to me for a build on my own…!&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomspdcp-target_blank-relnoopener-noreferrerderek-cash-petersonaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/spdcp" target="_blank" rel="noopener noreferrer">Derek Cash-Peterson&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I have always played with Lego. I stopped at a point when I couldn&amp;rsquo;t afford to buy them and my old ones were stored in my parent basement.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I picked them back up when my son was old enough to play with them and I could give him my old Legos.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>The Parisian Restaurant, was the worst for me. All the little pieces totally stressed me out.&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Building Legos is a family affair in my house, partner, son, and me. We have most of the city collection around the house and Hogwarts.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>No, I think tech folks gravitate to things that engage the mind creatively but are also modular. It&amp;rsquo;s fluid but also structured and you can do anything you want with it.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Patience!&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>We just got the new catalog today. Next set is Haunted House or Book Shop&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomjasehimm-target_blank-relnoopener-noreferrerjason-himmelsteinaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/jasehimm" target="_blank" rel="noopener noreferrer">Jason Himmelstein&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I can&amp;rsquo;t recall a time growing up when I didn&amp;rsquo;t play with LEGO. I am pretty sure that my parents threw caution to the wind and gave them to me earlier than recommended just expecting that I wouldn&amp;rsquo;t choke on them. As a kid I truly PLAYED with LEGO. I never bothered to build what the instructions said, I just built what was in my imagination. I stopped doing that after high school when I had my own place and figured that the women I was attempting to date at the time would turn tail and run if they saw I was a big kid playing with LEGO in my spare time.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I never lost track of it, but the second I had kids I started re-engaging with LEGO. My dudes had sets WAY before they were old enough to understand what was going on. We went to LEGOLand Discovery stores any chance I could get my wife to go.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>the Millennium Falcon… but not for the reasons one might think&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>My youngest son is my build partner. I try to build alone but he always wants to be involved. Occasionally I will build a set on my own after the dudes are in bed.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>I think that LEGO does a good job of capturing the imagination of the creatives &amp;amp; provides an outlet to the highly structured alike. No surprise that techies gravitate toward LEGO for those reasons.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>Just because there is a prescribed way of doing something doesn&amp;rsquo;t mean that it is the only way. Some of the coolest shit happens when you throw away the instruction manual.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>Saturn V&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercompauliehughes-target_blank-relnoopener-noreferrerpaul-hughesaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/PaulieHughes" target="_blank" rel="noopener noreferrer">Paul Hughes&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>My first memory of building “props” with LEGO was around aged 7 watching lots of Sci-fi and trying to re-create the props in LEGO (badly!).  When I hit my teens and started working on games the LEGO went on the back burner for a few years.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>Must&amp;rsquo;ve been the early 2000s when LEGO Interactive appeared on the scene – just seeing games melded with the LEGO franchise kick started me bakc buying sets again for my kids (well, OK, me!)&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>The LEGO Technic Porsche 911 GT3 RS – was a real struggle; fiddly, sometimes confusing, but ultimately a very rewarding build.&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Just on my own – I find it strangely relaxing.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>There&amp;rsquo;s just something fascinating about, quite literally, from small building blocks putting together big, well known objects and franchises – it just looks so darned cool!&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>I used to use LEGO for laying out isometric game ideas – which ironically led me to spending a decade programming LEGO Video Games!&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>I&amp;rsquo;m desperately trying to NOT buy the Millenium Falcon Collectors Edition – really.  I&amp;rsquo;m not going to buy it, no sireee.  (this is quite clearly a lie – I really need to build this, and, err, find somewhere to leave it on display)&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercomharbars-target_blank-relnoopener-noreferrerspenceaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/harbars" target="_blank" rel="noopener noreferrer">Spence&lt;/a>&lt;/span>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>Probably very young with duplo, then donated lego later on.&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>in the 90s when friends started to have children, and later when the Architecture series arrived&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>Taj Mahal – a very long and often times tedious build&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>Sometimes with a partner&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Not really, given the marketing expansion of lego it has become a “thing” across many “technical” disciplines&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>patience&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>whatever the next architecture series model is&lt;/p>
&lt;hr>
&lt;h1 id="meet-span-stylecolor-000000a-stylecolor-000000-hrefhttpswwwtwittercommikaelsvenson-target_blank-relnoopener-noreferrermikael-svensonaspan">Meet &lt;span style="color: #000000;">&lt;a style="color: #000000;" href="https://www.twitter.com/mikaelsvenson" target="_blank" rel="noopener noreferrer">Mikael Svenson&lt;/a>&lt;/span>&lt;/h1>
&lt;img loading="lazy" class="alignleft wp-image-431" src="https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-scaled.jpg" alt="" width="445" height="334" srcset="https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-scaled.jpg 2560w, https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-300x225.jpg 300w, https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-1024x768.jpg 1024w, https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-768x576.jpg 768w, https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-1536x1152.jpg 1536w, https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-2048x1536.jpg 2048w, https://m365princess.com/wp-content/uploads/2020/06/20200610_173201250_iOS-720x540.jpg 720w" sizes="(max-width: 445px) 100vw, 445px" />
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I started when I was 4-5 and kept building until I was around 13, moving mostly to Technic and the Model Team series. My last set then was &lt;a href="https://www.bricklink.com/v2/catalog/catalogitem.page?S=5580-" target="_blank" rel="noopener noreferrer">&lt;a href="https://www.bricklink.com/v2/catalog/catalogitem.page?S=5580-">https://www.bricklink.com/v2/catalog/catalogitem.page?S=5580-&lt;/a>&lt;/a>1 which I modified to put an eagle on the hood – first and only time using scissors on a Lego brick&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>I re-discovered Lego briefly in 1998 with Lego Mindstorm. Next was around 5 years ago buying for my son then 5. He wasn&amp;rsquo;t that interested so I ended up building more and more, and then continued for myself.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>&lt;a href="https://brickset.com/sets/42009-1/Mobile-Crane-MK-II" target="_blank" rel="noopener noreferrer">&lt;a href="https://brickset.com/sets/42009-1/Mobile-Crane-MK-II">https://brickset.com/sets/42009-1/Mobile-Crane-MK-II&lt;/a>&lt;/a> due to an error, and didn&amp;rsquo;t want to redo hours. Managed at the end with careful power bending&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>By myself most of the time, but do get some help from my son at times for Lego he likes. He built most of the Lego Boost and half the Voltron.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>Good question….I think being grown up allows me purchasing power I didn&amp;rsquo;t have as a kid with Lego. And tech people love problem solving and have decent pays.&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>A great conversation icebreaker and people figure you are somewhat creative&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>After the 1989 Batmobile I&amp;rsquo;ll start with the new Lambo &lt;a href="https://www.lego.com/en-us/product/lamborghini-sian-fkp-37-42115" target="_blank" rel="noopener noreferrer">&lt;a href="https://www.lego.com/en-us/product/lamborghini-sian-fkp-37-42115">https://www.lego.com/en-us/product/lamborghini-sian-fkp-37-42115&lt;/a>&lt;/a> which should arrive any day now.&lt;/p>
&lt;hr>
&lt;h1 id="meet-a-hrefhttpswwwtwittercomsbisson-target_blank-relnoopener-noreferrersimon-bissona">Meet &lt;a href="https://www.twitter.com/sbisson" target="_blank" rel="noopener noreferrer">Simon Bisson&lt;/a>&lt;/h1>
&lt;p>&lt;strong>When did you start playing with Lego as a kid? Did you stop at a certain point?&lt;/strong>&lt;/p>
&lt;p>I always remember playing with Lego as a child; I would construct huge moonbases and ships. I guess MOCs before they had a name! I fell out of it when I moved away from my home island to go to university, when I got to play with real steam trains in the Welsh mountains…&lt;/p>
&lt;p>&lt;strong>Did you re-discover Lego? When and How?&lt;/strong>&lt;/p>
&lt;p>It&amp;rsquo;s one of those things I have had waves in an out of, usually when there&amp;rsquo;s been a set that&amp;rsquo;s caught my eye. But now I have a shelf set aside in my office, as I really got into it again when the Saturn V came out. Now I&amp;rsquo;m having fun exploring more complex builds, and have recently started making time lapses of my builds.&lt;/p>
&lt;p>&lt;strong>What is the build you struggled the most with?&lt;/strong>&lt;/p>
&lt;p>I can&amp;rsquo;t think of any that really have been too hard, but I am yet to really get back into the Technic side of things!&lt;/p>
&lt;p>&lt;strong>Do you build alone or with a partner / in a team?&lt;/strong>&lt;/p>
&lt;p>I tend to work alone, especially when I&amp;rsquo;m making a build time lapse.&lt;/p>
&lt;p>&lt;strong>Do you think it’s a coincidence that we know a lot of tech folks &amp;amp; developers who love LEGO?&lt;/strong>&lt;/p>
&lt;p>No, I think they&amp;rsquo;re closely related concepts. I remember using it as a metaphor back in the 00s while trying to explain SOA and interface-first design to colleagues&lt;/p>
&lt;p>&lt;strong>What did you learn from building LEGO for your job?&lt;/strong>&lt;/p>
&lt;p>SOA and design-by-contract! They&amp;rsquo;re great ways of explaining modern cloud-first microservice design.&lt;/p>
&lt;p>&lt;strong>What will be your next set?&lt;/strong>&lt;/p>
&lt;p>I have managed to acquire a V-22 Osprey; it is on its way! I&amp;rsquo;m also fascinated by how the Chinese clone bricks are now starting to make their own sets, and have been building kits from Chinese sci-fi movies. There are some trucks from The Wandering Earth that I might get soon.&lt;/p>
&lt;hr>
&lt;h1 id="feedback-and-whats-next">Feedback and what&amp;rsquo;s next&lt;/h1>
&lt;p>If you like this series, please leave a nice comment 😉 You can submit your answers here: &lt;a href="https://bit.ly/LegoAddicts" target="_blank" rel="noopener noreferrer">&lt;a href="https://bit.ly/LegoAddicts">https://bit.ly/LegoAddicts&lt;/a>&lt;/a>&lt;/p></description></item><item><title>Exploring Microsoft Graph Toolkit as a non Developer – Part 6</title><link>https://m365princess.com/exploring-microsoft-graph-toolkit-as-a-non-developer-part-6/</link><pubDate>Wed, 10 Jun 2020 07:01:19 +0000</pubDate><guid>https://m365princess.com/exploring-microsoft-graph-toolkit-as-a-non-developer-part-6/</guid><description>&lt;p>If you made it with me up to this blog post, it’s very likely, that you read the previous 5 blog posts about my learning journey about Microsoft Graph as well – Thank you. &lt;a href="https://m365princess.com/exploring-microsoft-graph-toolkit-lap-as-non-developer/">If not, this is your chance to catch up here.&lt;/a>&lt;/p>
&lt;p>The Lap around Microsoft Graph Toolkit is a super cool series initiated by the Microsoft Graph team to empower more developers in working with Toolkit.&lt;/p>
&lt;p>I learned so far how to work with Toolkit components, and thanks to the excellent work of our Office development MVPs, I am now able to style and even template components. Never thought this would going to happen, but this super detailed step by step approach made developer tools/toys even accessible and usable for me, a non-developer.&lt;/p>
&lt;p>&lt;img src="https://m365princess.com/wp-content/uploads/2020/06/061020_0700_ExploringMi1-scaled.jpg">&lt;/p>
&lt;h1 id="from-html-application-to-sharepoint-solution">From HTML application to SharePoint Solution&lt;/h1>
&lt;p>In Day 9 of the #MSGraphToolkitLap &lt;a href="https://twitter.com/bernierh">MVP Hugo Bernier&lt;/a> guides us from writing web applications to writing a SharePoint solution. As usually in this series, we break it down to some (supposed to be) easy to follow steps. We already know these steps from Season 1 of our #MSGraphToolKItLap when we worked on creating our first HTML application. Let’s see how this goes.&lt;/p>
&lt;ol>
&lt;li>&lt;span style="text-decoration: line-through;">Register your application&lt;/span>&lt;/li>
&lt;/ol>
&lt;p>We learned in &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/blogs/a-lap-around-microsoft-graph-toolkit-day-2-zero-to-hero/">Day 2&lt;/a>, that we needed to register our application in Azure AD and that we needed that Client ID of this application to get access to our Graph APIs. In SharePoint solutions, we can skip this step, because each solution package automatically creates a unique ID which we use to grant access to Microsoft Graph APIs.&lt;/p>
&lt;ol start="2">
&lt;li>Create your application&lt;/li>
&lt;/ol>
&lt;p>Let’s get our hands dirty! Hugo guides us through creating our web part app with Yeoman SharePoint generator. As I recently dipped my toe into how to make my first Hello World! SPFx web part following these &lt;a href="https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part">docs.microsoft.com&lt;/a>, which Hugo also refers to, it was easy to do that, thanks to super detailed guidance. If you struggle with that, there is also an excellent video about &lt;a href="https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment">how to set up your dev environment and do these first steps provided by Vesa&lt;/a>.&lt;/p>
&lt;p>Adding TypeScript 3.7 is a good idea since we can get rid of some error messages. I am not too fond of error messages, not only because I wonder what they mean, but because I always assume that I did something wrong, and then I question my skills. So if a few additional steps can avoid me getting error messages, this is already a good investment of time and effort. For context: I can write some super basic lines of JavaScript and never experienced doing stuff with TS. So I am a little bit insecure about how this turns out. But as the blog post series seems to be a super beginner-friendly end-to-end tutorial, I probably manage that as well.&lt;/p>
&lt;ol start="3">
&lt;li>Add Microsoft Graph Toolkit to your application&lt;/li>
&lt;/ol>
&lt;p>We did that in our previous HTML application by adding a one-line script, which was just a copy-paste job. (By the way: The more I learn about dev stuff, the more I am convinced that CTRL c and v are the essential keys on my keyboard).&lt;/p>
&lt;p>For our SharePoint Solution, all we need to do is installing an npm package, which again is just a one-line command.&lt;/p>
&lt;ol start="4">
&lt;li>Add the &lt;span style="text-decoration: line-through;">MSAL&lt;/span> SharePoint Provider&lt;/li>
&lt;/ol>
&lt;p>As we now deal with SharePoint and not an HTML application anymore, we do want to add a SharePoint provider. &lt;a href="https://m365princess.com/exploring-microsoft-graph-toolkit-as-a-non-developer-part-5/">Remember part 5 of my series&lt;/a>, where I explained that we do not necessarily as makers need to be able to write our providers? As a developer, you can do that, &lt;a href="https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/samples/examples/simple-provider.html">check out Nikola’s sample on this Github repo&lt;/a> for reference. It’s just great that there is already a SharePoint provider in place, that we can use. You know, I love LEGO, and this seems to be the SharePoint Lego Set. Installing the SharePoint provider is quite easy, as we only need to add three lines of code. Understanding how providers work on a high level (&lt;a href="https://developer.microsoft.com/en-us/graph/blogs/a-lap-around-microsoft-graph-toolkit-day-7-microsoft-graph-toolkit-providers/">thanks again to Simon Agren&lt;/a>) helped me here.&lt;/p>
&lt;p>&lt;img src="https://m365princess.com/wp-content/uploads/2020/06/061020_0700_ExploringMi2-scaled.jpg">&lt;/p>
&lt;p>Wow, the hardest part is already done, and it only took us a couple of minutes!&lt;/p>
&lt;p>&lt;span style="text-decoration: line-through;">5. Add the login component&lt;br /> &lt;/span>&lt;/p>
&lt;p>When we still worked on an HTML app, we used the mgt-login component because we wanted to enable the user to log in securely. In SharePoint, things are a little bit different: Users, who consume a SharePoint web part, already need to be authenticated. They did that login already, so we do not need to log in users twice. Nothing to do in this step – you see, stuff gets easier in SharePoint!&lt;/p>
&lt;ol start="6">
&lt;li>Add additional components&lt;/li>
&lt;/ol>
&lt;p>Again an easy step – we are already familiar with components like mgt-person or mgt-agenda – the only thing we need to do is adding these components in our HTML. Now we can repeat what we learned in the last season, like customizing the components so that they match more our needs or style them like we want it and even use the mgt-get as a placeholder for any data we want to get and even update.&lt;/p>
&lt;p>Now that we translated our previous HTML application knowledge to SharePoint, it’s time for&lt;/p>
&lt;ol start="7">
&lt;li>Build our solution&lt;/li>
&lt;/ol>
&lt;p>First things first, choose the right MSGraph API permissions. When I learned something the hard way in Power Platform and Graph, it’s that permissions comes first. So let’s do this by simply&lt;/p>
&lt;ul>
&lt;li>
&lt;div>
changing &amp;#8220;isDomainIsolated&amp;#8221;: true to &amp;#8220;isDomainIsolated&amp;#8221;: false
&lt;/div>
&lt;p>in a JSON file and&lt;/li>&lt;/p>
&lt;ul>
&lt;li>adding the correct permissions. Don’t you know which permissions you need? I hope that if you follow my blogs that I can’t stress enough, the value that &lt;a href="https://docs.microsoft.com/en-us/graph/toolkit/components/person">docs.microsoft.com&lt;/a> provides. &lt;strong>R&lt;/strong>ead &lt;strong>t&lt;/strong>he &lt;strong>f&lt;/strong>ormidable &lt;strong>m&lt;/strong>anual is always a good advice. Just look up what you need, it’s not rocket science. Those webApiPermissionRequests always consist of a resource, which is “Microsoft Graph” and a scope like user.Read.All”. As always: Don’t add permissions that are not needed.&lt;/ul>&lt;/li>
&lt;/ul>
&lt;p>After we built the solution with&lt;/p>
&lt;ul>
&lt;li>gulp bundle –ship&lt;/li>
&lt;li>gulp package solution –ship&lt;/li>
&lt;/ul>
&lt;p>Note: &lt;a href="https://www.eliostruyf.com/developing-share-point-framework-solutions-outside-the-workbench/">Educated by Elio Struyf that I needed to –ship as otherwise only I could use that.&lt;/a>&lt;/p>
&lt;p>We can find an sppkg package in our solution folder. I mean, how easy was that?&lt;/p>
&lt;p>We now want to upload our sppkg file to SharePoint’s app catalog, and we get asked to deploy our solution. It’s a good idea to have a careful look at the permissions again. We need to approve the pending permissions on the API Management page. What you need to know: By hitting this deploy button, we get a list of all permission requests, which we now need to approve. To access that list, we navigate to the SharePoint Admin Center and select ADVANCED -API ACCESS. Now approve all of these pending requests. This step is mandatory; without approved permissions, our web part can’t work.&lt;/p>
&lt;ol start="8">
&lt;li>Test our web part&lt;/li>
&lt;/ol>
&lt;p>Perhaps you are already familiar with the concept of a local workbench for testing. It runs on your machine. This won’t work here as it is not authenticated, so that we need to switch to a hosted workbench, which runs in our tenant. We now test by adding the new web part in this hosted workbench and see our component for the first time in action&lt;/p>
&lt;p>I tried with mgt-agenda and mgt-tasks, and both worked like a charm.&lt;/p>
&lt;h1 id="feedback-and-what8217s-next">Feedback and what’s next&lt;/h1>
&lt;p>If you liked this blog post, please let me know with a helpful comment. Also, &lt;a href="https://www.twitter.com/LuiseFreese">follow me on twitter.&lt;/a> I will surely blog about the upcoming blogs of Microsoft Graph Team using the hashtag #MSGraphToolKitLap.&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>Community Profile of the June – #MSBuild edition of Microsoft 365 Developer Newsletter</title><link>https://m365princess.com/community-profile-of-the-june-msbuild-edition-of-microsoft-365-developer-newsletter/</link><pubDate>Tue, 09 Jun 2020 09:36:03 +0000</pubDate><guid>https://m365princess.com/community-profile-of-the-june-msbuild-edition-of-microsoft-365-developer-newsletter/</guid><description>&lt;p>Wow, I’ve been featured in the Microsoft 365 Developer newsletter 🙂&lt;figure class="wp-block-image size-large">&lt;/p>
&lt;p>&lt;img loading="lazy" width="529" height="788" src="https://m365princess.com/wp-content/uploads/2020/06/image.png" alt="" class="wp-image-369" srcset="https://m365princess.com/wp-content/uploads/2020/06/image.png 529w, https://m365princess.com/wp-content/uploads/2020/06/image-201x300.png 201w" sizes="(max-width: 529px) 100vw, 529px" /> &lt;/figure> &lt;figure class="wp-block-table is-style-stripes">&lt;/p>
&lt;table>
&lt;tr>
&lt;td>
1. What is the coolest Microsoft 365 developer feature for you? &lt;br />&lt;br />Hard question, I love the core idea of making Microsoft 365 development as easy and accessible for everyone as possible, which leads to great products and documentation and attracts a community which breathes sharing is caring. My personal gamechanger were playgrounds like the Adaptive Cards Designer or the MGT Playground and I love my free DEV tenant.
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
2. What school did you graduate from? &lt;br />&lt;br />I graduated from Humboldt University in Berlin with a Master degree in Business Communications
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
3. What’s your favorite city to live in? &lt;br />&lt;br />I love my hometown Duesseldorf, where I usually live. Currently, I am staying in Belgium on the countryside, which is also lovely.
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
4. What did you want to be when you were a kid? &lt;br />&lt;br />As a kid I had every day 3 new ideas what I wanted to become 🙂
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
5. Which words or phrases do you most over-use? &lt;br />&lt;br />In sessions, I tend to say &amp;#8220;soooo&amp;#8221; :-).
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
6. Which talent would you most like to have? &lt;br />&lt;br />I would love to be able to orientate myself better. I can easily get lost in a grocery store ¯\_(ツ)_/¯
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
7. Where would you most like to live? &lt;br />&lt;br />I want to live with my beloved ones, like to travel (I really miss that) and love coming home again.
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
8. What is your most treasured possession? &lt;br />&lt;br />I don&amp;#8217;t really like having a lot of stuff, because that seeks my time, attention, focus and energy. But I really love my piano, an old box with photos and some LEGO.
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
9.What is your most marked characteristic? &lt;br />&lt;br />Curiosity and empathy
&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>
10. Spaces or tabs? &lt;br />&lt;br />Tabs. Unless I&amp;#8217;d work in team in which they work with Spaces.
&lt;/td>
&lt;/tr>
&lt;/table>&lt;/figure></description></item><item><title>How to make a conference app in less than 30 minutes</title><link>https://m365princess.com/how-to-make-a-conference-app-in-less-than-30-minutes/</link><pubDate>Wed, 27 May 2020 13:07:00 +0000</pubDate><guid>https://m365princess.com/how-to-make-a-conference-app-in-less-than-30-minutes/</guid><description>&lt;p>I made an app for #MSBuild conference so I could more easily overview my sessions, follow relevant tweets and rate the sessions. This is the second app I’ve ever built and I want to share how you can easily rebuilt is as well.&lt;/p>
&lt;p>I learned the very hard way, that the first thing you need to do in a Power App is not only to give it a name, but to save it, so you can then benefit from autosave. I didn’t do that and accidentally closed my browser tab and lost everything when I almost finished the app — Fair to say, that it took me more than 30 minutes when i built it the first time. This is when I came up with the idea of making it a challenge to complete a whole app in under 30 Minutes. I recorded my app making process and the video is about 18 mins. You can watch and share it here:&lt;figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio wp-embed-aspect-4-3">&lt;/p>
&lt;div class="wp-block-embed__wrapper">
&lt;/div>&lt;/figure>
&lt;p>To make this app, we will need&lt;/p>
&lt;ul>
&lt;li>6 Screens&lt;/li>
&lt;li>3 galleries&lt;/li>
&lt;li>2 Connectors&lt;/li>
&lt;li>1 CDS entity&lt;/li>
&lt;/ul>
&lt;p>Let’s start with the&lt;/p>
&lt;h2 id="common-data-service-cds">Common Data Service (CDS):&lt;/h2>
&lt;p>Make sure you understand: This is not an entire CDS tutorial, it’s just the way I started with it and made my app work.&lt;/p>
&lt;p>Open make.powerapps.com, login and click SOLUTIONS → NEW SOLUTION&lt;/p>
&lt;p>Give it a name AND create your own publisher; for reference &lt;a rel="noreferrer noopener" href="https://docs.microsoft.com/en-us/power-platform/alm/solution-concepts-alm#solution-publisher" target="_blank">see this article&lt;/a>. Use this new publisher in your solution, click on CREATE.&lt;/p>
&lt;p>Now select your solution, and click on NEW → ENTITY&lt;/p>
&lt;ol>
&lt;li>Create the fields you want to see and make sure to respect the data type of the field.&lt;/li>
&lt;li>You can add the first record manually on the DATA blade:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Dieses Bild hat ein leeres Alt-Attribut. Der Dateiname ist 1*LXNXE9z89CauG-WV8vEMJw.png" src="https://miro.medium.com/max/1458/1*LXNXE9z89CauG-WV8vEMJw.png"> &lt;/figure>&lt;/p>
&lt;p>You will see that you won’t see all the fields you created before- don’t worry about that too early. Just add your first record “test” (or “#Unicorn”). You may now want to know where your fields are: Just define a new view in the upper right corner:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img alt="Dieses Bild hat ein leeres Alt-Attribut. Der Dateiname ist 1*WqO-sSse8crx06sirGKx6g.png" src="https://miro.medium.com/max/2396/1*WqO-sSse8crx06sirGKx6g.png"> &lt;/figure>&lt;/p>
&lt;p>Now that we have our CDS entity it’s time to connect this entity with our Power App.&lt;/p>
&lt;p>Click on the Data Sources icon on the left rail and connect to your entity.&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img alt="Dieses Bild hat ein leeres Alt-Attribut. Der Dateiname ist 1*XvQBEhKKrjdUVpMyiTrUuQ.png" src="https://miro.medium.com/max/665/1*XvQBEhKKrjdUVpMyiTrUuQ.png"> &lt;/figure>&lt;/p>
&lt;p>As you are already here, you can also connect to Twitter and Office 365 Outlook, we will use that later.&lt;/p>
&lt;h2 id="sessions">Sessions&lt;/h2>
&lt;p>As we want to nicely display all sessions we were interested in, we first need to have the session data. I downloaded the .ics files for all sessions and saved them in an Outlook Calendar called “Events”.&lt;/p>
&lt;p>So this is how it works:&lt;/p>
&lt;ol>
&lt;li>Create a new screen and add a gallery to it- I chose ‘Title, Subtitle, and body’ as Layout&lt;/li>
&lt;li>Create a Dropdown Input with Items= Filter(Office365Outlook.CalendarGetTables().value,DisplayName = &lt;code>Events&lt;/code>)&lt;/li>
&lt;/ol>
&lt;p>without the filter you can actually use it as dropdown, it will show all your Outlook calendars. I set this visibility of this dropdown later to false, because we don’t need to see this in our app.&lt;/p>
&lt;p>I chose that the Subject of my event should be the Title, the Body Preview should be the Subtitle and the Body should be Start / End Time.&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1295/1*R3EJBj35w_1eScRQPcL9ow.png"> &lt;/figure>&lt;/p>
&lt;p>Date /Time looked ugly, I didn’t want to repeat the date for the End Time and so I went for this approach- which is basically concatenating some texts and if formulas- You’d do the same in Excel: Hour(ThisItem.Start) &amp;amp; “:” &amp;amp; If( Minute(ThisItem.Start) = 0, “00”, Minute(ThisItem.Start) )&amp;amp; ” to ” &amp;amp; Hour(ThisItem.End) &amp;amp; “:” &amp;amp; If( Minute(ThisItem.End) = 0, “00”, Minute(ThisItem.End) ) &amp;amp; If( Hour(ThisItem.Start) &amp;lt;10, &amp;ldquo;zzz&amp;rdquo;, &amp;quot;&amp;quot; )&lt;/p>
&lt;h2 id="twitter">twitter&lt;/h2>
&lt;p>We already connected to Twitter, so we can now just repeat what we did for our sessions screen:&lt;/p>
&lt;ol>
&lt;li>Create a new screen, add a gallery. I again chose Title, Subtitle, and Body as the layout.&lt;/li>
&lt;li>Set Items = Twitter.SearchTweet(“#MSBuild”)&lt;/li>
&lt;/ol>
&lt;p>as we want all #MSBuild tweets.&lt;/p>
&lt;ol start="3">
&lt;li>Choose which data you want to display in your fields:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Dieses Bild hat ein leeres Alt-Attribut. Der Dateiname ist 1*ZMZDWRW6Zigo6T8QUxE3NA.png" src="https://miro.medium.com/max/1293/1*ZMZDWRW6Zigo6T8QUxE3NA.png"> &lt;/figure>&lt;/p>
&lt;ol start="4">
&lt;li>I want an icon in the gallery that should open this particular tweet. As a tweet URL has the Syntax:&lt;/li>
&lt;/ol>
&lt;p>&lt;a rel="noreferrer noopener" href="https://www.twitter.com/USERNAME/status/TWEETID" target="_blank">&lt;a href="https://www.twitter.com/USERNAME/status/TWEETID">https://www.twitter.com/USERNAME/status/TWEETID&lt;/a>&lt;/a> we just set the .onSelect property to:&lt;/p>
&lt;h2 id="session-rating">Session Rating&lt;/h2>
&lt;ol>
&lt;li>Create a new screen as a form&lt;/li>
&lt;li>Data Source is your CDS entity. I wanted to have one Dropdown field for the Session Name and 4 Number fields for session evaluation, displayed as stars.&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img alt="Dieses Bild hat ein leeres Alt-Attribut. Der Dateiname ist 1*-OliyUFPkQuTthN2TRmk0A.png" src="https://miro.medium.com/max/1301/1*-OliyUFPkQuTthN2TRmk0A.png"> &lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>
&lt;p>Set this SessionName field to ALLOWED VALUES, and the allowed values property to the same value as the items property of the dropdown field:&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Change the Data Value of the dropdown to SUBJECT (it’s Preview Body by default)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Change the Update property to DataCard.Value.Subject (important!)&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Change the .onSelect property of the SubmitForm Button to&lt;/p>
&lt;h2 id="make-your-app-pretty">Make your app pretty&lt;/h2>
&lt;p>Beautiful apps are more engaging and it’s not as hard as you think to pretty up your app. Here are a few things that I did:&lt;/p>
&lt;ol>
&lt;li>choose a nice background picture. I know, we all have our go to resources for nice pictures, but if you don’r find anything you like on &lt;a rel="noreferrer noopener" href="http://www.unsplash.com/" target="_blank">unsplash.com&lt;/a> you can even open PowerPoint and click on INSERT → STOCK PHOTOS. You will find tons of hi-res photos 🙂&lt;/li>
&lt;li>There are at least three ways to define the color scheme of your app:&lt;/li>
&lt;/ol>
&lt;p>a) Choose a theme for your app that is in harmony with your background picture, so you don’t need to touch any single button to define your colors.&lt;/p>
&lt;p>b) Create a collection with “your” colors on App Start and reference to them later, because you unfortunately can’t just write /upload your own CSS file.&lt;/p>
&lt;p>c) You can also store your colors as variables:&lt;/p>
&lt;p>I just picked a theme and it worked very well for me.&lt;/p>
&lt;ol start="3">
&lt;li>
&lt;p>Choose icons for your navigation, use the ALIGN — DISTRIBUTE HORIZONTICALLY feature&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Use the Navigate() function on the .onSelect property of your icons to navigate to the other screens&lt;/p>
&lt;/li>
&lt;li>
&lt;p>on each screen change the display mode of the icon that represents the screen to “disabled”- wouldn’t make sense to click them.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>That’s it!&lt;/p>
&lt;h2 id="feedback-and-whats-next">Feedback and what’s next?&lt;/h2>
&lt;p>If you like this blogpost / the video / my app, please clap and &lt;a rel="noreferrer noopener" href="https://www.twitter.com/LuiseFreese" target="_blank">follow me on twitter&lt;/a> and &lt;a rel="noreferrer noopener" href="https://youtu.be/T-WU77f83UE" target="_blank">subscribe for my youtube channel&lt;/a>. I think about doing a series called #LessCodeMorePower Community Calls, where I will demo my wins and learning of Power Platform including PowerApps, Power Automate, Power BI, CDS and Power Virtual Agents.If you like to join, just send me a message.&lt;/p></description></item><item><title>Exploring Microsoft Graph Toolkit as a non-developer -Part 5</title><link>https://m365princess.com/exploring-microsoft-graph-toolkit-as-a-non-developer-part-5/</link><pubDate>Tue, 05 May 2020 12:36:00 +0000</pubDate><guid>https://m365princess.com/exploring-microsoft-graph-toolkit-as-a-non-developer-part-5/</guid><description>&lt;p>Wow, we almost reached the end of season 1 of the #MSGraphToolkitLap — in which the Team of Microsoft Graph takes us seriously from zero to hero. If you didn’t read &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-21-exploring-microsoft-graph-toolkit-lap-as-non-developer/">part 1&lt;/a>, &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-23-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-2/">part 2&lt;/a>, &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-27-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-3/">part 3&lt;/a> or &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-30-exploring-microsoft-graph-toolkit-as-a-non-developer-part-4/">part 4&lt;/a> of my series about it, you should catch up first. I promise to stay here and wait until you are ready.&lt;/p>
&lt;p>Read it? Great! So let’s see, what this part is about: &lt;a rel="noreferrer noopener" href="https://twitter.com/agrenpoint" target="_blank">MVP Simon Agren&lt;/a> takes my learning journey in which I play with developer tools, to the next level as his blog post is not about how to make good use of another component, but as he provides an excellent overview about providers and gives an example how to use this as well. My first read of the blog post (and especially the SimpleProvider sample by &lt;a rel="noreferrer noopener" href="https://twitter.com/metulev" target="_blank">Nikola Metulev&lt;/a> in the corresponding &lt;a rel="noreferrer noopener" href="https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/samples/examples/simple-provider.html" target="_blank">GitHub repo&lt;/a> made me feel like this:&lt;/p>
&lt;p>&lt;strong>Time to eat that shark bite by bite&lt;/strong>&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/903/1*XMSR8fsPqLumsJLo2tGlOg.png"> &lt;/figure>&lt;/p>
&lt;p>My blog posts are about which parts of Microsoft Graph Toolkit are useful as well for non-developers. As in many other scenarios, this is a #bettertogether story: Having a dev in your team will be crucial if you want to follow Simon’s approach. Once this is done, citizen developers can contribute by using the LEGO blocks as I already wrote about it.&lt;/p>
&lt;p>&lt;strong>First things first: the architecture of providers&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>the provider needs to be initialized — by a provider component (like we did in the previous posts) or by a developer in the code (this is way more advanced but makes sure that you are flexible!)&lt;/li>
&lt;li>after that, it needs then to be set as a global provider in the provider’s namespace&lt;/li>
&lt;li>provider tells the web component which of the three login state is true: ‘loading’, ‘SignedIn’ or ‘SignedOut’&lt;/li>
&lt;li>on change of this state to ‘SignedIn’ the component can call Microsoft Graph via provider&lt;/li>
&lt;li>provider implements an SDK instance which means, that the provider can call Microsoft Graph APIs&lt;/li>
&lt;li>Microsoft Graph SDK makes the actual Graph call.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Let’s get practical&lt;/strong>&lt;/p>
&lt;p>So if you feel a bit lost here — don’t worry — it’s getting better once we approach this topic more practically and use an example. You surely remember &lt;a href="https://developer.microsoft.com/en-us/graph/blogs/a-lap-around-microsoft-graph-toolkit-day-6-the-power-of-mgt-get/" target="_blank" rel="noreferrer noopener">Cameron Dwyer’s use case of warehouse packers&lt;/a> who needed a lightweight app? Cool. Simon extends this use case so that we can update list items as well and not only get data from a SharePoint list.&lt;/p>
&lt;p>Simon added just a few things to Cameron’s code:&lt;/p>
&lt;ol>
&lt;li>he changed the filter from Status eq ‘ready for packing’ to Status ne ‘delivered’&lt;/li>
&lt;li>he played a bit with colors&lt;/li>
&lt;li>display a ‘pack’ button if the item status is ‘requested’&lt;/li>
&lt;/ol>
&lt;p>All we need to add to our existing code is a little script that checks if the user is already logged in (so if the state changed to “SignedIn”) and then patch the list item with an updateOrder() function which runs on select of the “pack” button. Obviously, we need the right permissions to do that so that we will add sites.readwrite.all as prepScope.&lt;/p>
&lt;p>&lt;strong>My take on providers&lt;/strong>&lt;/p>
&lt;p>To be very honest: I couldn’t write my own provider. But fortunately, this just isn’t my job. Remember: I am one of those self-taught makers citizen developers who are not paid to write code, but just feel the urge to build solutions and making things happen to make people’s work work. I use the so-called low-code on Microsoft Power Platform, and I am ok with writing some lines like in the last parts of this series, but doing the hard work writing a provider would require a lot more coding experience than I have.&lt;/p>
&lt;p>But although this part is not entirely doable for me, I found much value in it. I understood not only what a provider does and in which kind of scenarios we would use our own providers, but also how easy it would be to include a custom provider into a lightweight solution like our warehouse packer application. I got it that predefined blocks make it easier to build faster as we do not always have to reinvent the wheel over and over again.&lt;/p>
&lt;p>Some of the concepts that I learned in this first season were already familiar to me — not because I knew them, but because I spotted similar things in Power Apps (like updating a list with a patch() function) or Power Automate (permissions for Graph calls within a flow). I love to see that I could connect some dots.&lt;/p>
&lt;p>&lt;strong>What’s next &amp;amp; Feedback&lt;/strong>&lt;/p>
&lt;p>Next is #MSBuild — if you haven’t registered yet, do yourself a favor and register as it’s all free and digital. The Graph team will do a live coding session as well, and I will blog about that as well. What do you think about the #MSGraphToolkitLap and how I approach it? Will you stay for the second season after #MSBuild? Please let me know what you want to learn, clap for this blog post, and &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">follow me on twitter&lt;/a> to connect with me.&lt;/p></description></item><item><title>May the force be with me: My first Canvas App in Power Apps</title><link>https://m365princess.com/may-the-force-be-with-me-my-first-canvas-app-in-power-apps/</link><pubDate>Mon, 04 May 2020 12:38:22 +0000</pubDate><guid>https://m365princess.com/may-the-force-be-with-me-my-first-canvas-app-in-power-apps/</guid><description>&lt;p>Challenged and inspired by &lt;a href="https://twitter.com/PieterVeenstra" target="_blank" rel="noreferrer noopener">Pieter Veenstra’s&lt;/a> &lt;a href="https://sharepains.com/2020/04/20/power-apps-for-kids/" target="_blank" rel="noreferrer noopener">Video about an app by and for kids with Power Apps&lt;/a> I decided to finally make my first canvas app. The decision was not attempting to make something: Do or do not- there is no try 🙂&lt;/p>
&lt;h2 id="purpose">Purpose&lt;/h2>
&lt;p>Before I talk about the solution I came up with, I want to introduce to Miss 8- my most important user MIU. Miss 8 struggles a bit with calculating. The fact, that she can’t go to school during these difficult times or attends lessons, doesn’t make it easer. So I wanted to help her.&lt;/p>
&lt;p>So very obviously, this will be an app for kids. I want a pretty simple UI, without fancy elements and a color scheme that is compelling for my MIU. Miss 8 even asked me to take a photo of her favorite #Lego Minifig to make this a background of the app.&lt;/p>
&lt;h2 id="challenges">Challenges&lt;/h2>
&lt;p>There were in total three main issues for me with building this app:&lt;/p>
&lt;ol>
&lt;li>I am not a professional developer and I am truly intimidated by all the tech that is available and impostor a lot about it —&lt;a target="_blank" rel="noreferrer noopener" href="https://regarding365.com/2019-or-how-i-started-to-believe-in-myself-ba06d75dba84"> although I already made progress&lt;/a>&lt;/li>
&lt;li>I have &lt;a href="https://www.understood.org/en/learning-thinking-differences/child-learning-disabilities/dyscalculia/what-is-dyscalculia" target="_blank" rel="noreferrer noopener">dyscalculia&lt;/a>, which means I have difficulties in understanding the meaning behind numbers and that it it’s very hard for me to get the logic in a calculation&lt;/li>
&lt;li>I never built an app before!&lt;/li>
&lt;/ol>
&lt;h2 id="the-list-of-requirements-figure-classwp-block-image">The list of requirements: &lt;figure class="wp-block-image">&lt;/h2>
&lt;p>&lt;img src="https://miro.medium.com/max/2580/1*O_UcQEqZG1Dy-DDA2azJYQ.png"> &lt;/figure>&lt;/p>
&lt;p>&lt;strong>random tasks for exercises&lt;/strong>&lt;/p>
&lt;p>for the four basic calculation operations: +,-, *, / only with natural numbers&lt;/p>
&lt;ul>
&lt;li>addition: x+y = z; z ≥ 100&lt;/li>
&lt;li>subtraction: x-y = z≥ 0&lt;/li>
&lt;li>multiplication: x*y = z; z≤ 100&lt;/li>
&lt;li>division; x/y = z; z≥0&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>show if result is correct or incorrect&lt;/strong>&lt;/p>
&lt;p>so that user gets an instant feedback&lt;/p>
&lt;p>&lt;strong>count correct and incorrect answers and calculate a score&lt;/strong>&lt;/p>
&lt;p>and display this during the game&lt;/p>
&lt;p>&lt;strong>let user submit results only if a score of 30 points is reached&lt;/strong>&lt;/p>
&lt;p>to ensure Miss 8 stays focused and spends a while with doing exercises&lt;/p>
&lt;p>&lt;strong>write results into a list&lt;/strong>&lt;/p>
&lt;p>so that parental account can see results as well&lt;/p>
&lt;p>&lt;strong>display scores for a user and send confirmation to parental email address&lt;/strong>&lt;/p>
&lt;p>so no way to cheat&lt;/p>
&lt;p>&lt;strong>reward user for getting 100, 200, 300 points&lt;/strong>&lt;/p>
&lt;p>with stickers to collect in the app&lt;/p>
&lt;p>&lt;strong>make it compelling for Miss 8!&lt;/strong>&lt;/p>
&lt;p>Not too bad for a first app? This is what I thought as well. So here is how I did that.&lt;/p>
&lt;p>&lt;strong>Preparations:&lt;/strong>&lt;/p>
&lt;p>If you haven’t already, get a &lt;a href="https://powerapps.microsoft.com/en-us/communityplan/" target="_blank" rel="noreferrer noopener">free PowerApps license&lt;/a> and log in. Click on Create, provide a name and here we go:&lt;/p>
&lt;h2 id="working-on-our-app">Working on our app&lt;/h2>
&lt;ol>
&lt;li>as we need four exercises for the different basic calculation operations, I decided to go for four screens. Each screen contains&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>text labels&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>firstNumber&lt;/li>
&lt;li>secondNumber&lt;/li>
&lt;li>operator&lt;/li>
&lt;li>equals&lt;/li>
&lt;li>result&lt;/li>
&lt;li>amount of correct answers&lt;/li>
&lt;li>amount of incorrect answers&lt;/li>
&lt;li>this game’s score&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>buttons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>next (to create the next task)&lt;/li>
&lt;li>navigation (back to Start, submit to SharePoint, show my scores, hall of fame)&lt;/li>
&lt;/ul>
&lt;p>Let’s have a look at this (and also reveal the design):&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2622/1*Pi619xW0bfwSW35fFIsSqg.png"> &lt;/figure>&lt;/p>
&lt;p>&lt;strong>logic&lt;/strong>&lt;/p>
&lt;p>Of course, these text labels and buttons don’t do anything unless we tell them what they need to do.&lt;/p>
&lt;p>START, MY SCORES, HALL OF FAME will be just links to screens we will create later on. To let your user navigate between screens, just select the &lt;em>.onSelect&lt;/em> property and type navigate(- your other screens will now show up and you can just select from this list. Don’t forget to close your parenthesis (round bracket).&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/719/1*sTih5vBi-OSqc63NDKIR2A.png"> &lt;/figure>&lt;/p>
&lt;p>As we want two numbers AND make sure that our user gets an instant feedback, we need do the following in the &lt;em>.onSelect&lt;/em> property of NEXT button:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>This ensures, that if the answer to x+y = z (correctly), we will set a variable named varQuestionCorrect to the value &lt;em>true&lt;/em>, else we will set a variable named varQuestionIncorrect to the value &lt;em>true.&lt;/em>&lt;/p>
&lt;ol start="2">
&lt;li>We will need 3 functions to meet all requirements here:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>rand() — gives us randomized numbers between 0 and 1 with lots of digits after the comma — we will need to multiply this random number with 100 to get numbers up to 100: &lt;em>rand()*100&lt;/em>&lt;/li>
&lt;li>round(&lt;value>,0) — makes sure that we round the value and get 0 digits after the comma. Our value is the randomized number between 1 and 100from our previous step: round(rand()*100,0)&lt;/li>
&lt;li>now it’s getting a bit ugly. The mod() function returns the remainder from a division. So we can have any random number and then use Mod(&lt;Random Number>, 100) to get a random number between 1 and 100&lt;/li>
&lt;/ul>
&lt;p>After we learned the functions, we will use them like this — again &lt;em>.onSelect&lt;/em> of our NEXT button:&lt;/p>
&lt;p>a) First we need to create z- as this is the highest number (≤ 100)&lt;/p>
&lt;p>b) Now we create y and make sure that y is between 0 and z&lt;/p>
&lt;p>c) After that we already know x: it’s just z-y&lt;/p>
&lt;p>After that we just reset our Result text label, to make it more convenient for MIU:&lt;/p>
&lt;p>Reset(‘Add-Result’);&lt;figure>&lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>As we want to add/deduct one point to/from our Counter, we need to do two things:&lt;/li>
&lt;/ol>
&lt;p>a) Set a global variable on app Start&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/755/1*AeH3nM09BdoHr5PhYJU2rA.png"> &lt;/figure>&lt;/p>
&lt;p>b) set a condition, that if the result was correct (we know this already by our varQuestionCorrect), then add 1 to our counter, else subtract 1 from our counter:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>Pretty easy, hm?&lt;/p>
&lt;ol start="4">
&lt;li>Next thing I want is to give Miss 8 an indicator, if her answer was correct or not — show i want to show a 🙂 for a correct answer and a 🙁 for an incorrect answer — but only for 1,5 seconds. Plus: I want the colors of these emojis with my color scheme. Let’s do this!&lt;/li>
&lt;/ol>
&lt;p>a) First, I created a happy face icon — and guess what? An icon has an icon property .&lt;/p>
&lt;p>Again, a simple if statement (now in the in the .icon property of the icon):&lt;figure>&lt;/figure>&lt;/p>
&lt;p>b) Next small feature: colors of the Emoji. If it’s the happy face, it shall be the light blue I use”, else it shall be pink. So on the &lt;em>.color&lt;/em> property of our icon we do:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>c) Now I want to make sure that the Emoji will only be visible after Miss 8 clicked on the NEXT button:&lt;/p>
&lt;p>So I need to set a variable in the &lt;em>.onSelect&lt;/em> property of my NEXT button:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>(we will need the varNextclicked in step c)&lt;/p>
&lt;p>so whenever the NEXT button is clicked, the varShowface variable is set to true.&lt;/p>
&lt;p>Now we will work on the &lt;em>.visibility&lt;/em> property of our icon:&lt;/p>
&lt;p>It should only show up, if the NEXT button is clicked — we know this from the value of the variables of the previous step — so we just type:&lt;/p>
&lt;p>varShowFace&lt;/p>
&lt;p>d) Now let’s work on this 1 1/2 seconds:&lt;/p>
&lt;p>I added a timer and set its .duration property on 1500 (which means 1500 milliseconds or 1,5 seconds).&lt;/p>
&lt;p>The Start property is varAddNextclicked = true (we did that in step b) and the .onTimerEnd property is set to:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>to make sure, that the icon will be visible on next NEXT click again.&lt;/p>
&lt;p>e) I wanted to show the amount of correct and incorrect given answers, not only the earned points, so I set two variables in the &lt;em>.onSelect&lt;/em> property of my NEXT button in an if statement, depending on if varQuestionCorrect equals true or false:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>All I need to do now is to set three additional text labels, that display my variables. I found it nice to do it like this in the &lt;em>.text&lt;/em> property:&lt;figure>&lt;/figure> &lt;figure>&lt;/figure> &lt;figure>&lt;/figure>&lt;/p>
&lt;ol start="5">
&lt;li>Now the easy part — at least for me. We want to write results into a SharePoint list, display the list in a nice way in the app, notify parental account by email and make sure that Miss 8 needs to have at least 30 points to be able to submit her records. Here we go:&lt;/li>
&lt;/ol>
&lt;p>Preparation for this step: Create a SharePoint list&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2022/1*zwU7YpuhzdemQ8AYOH4zpQ.png"> &lt;/figure>&lt;/p>
&lt;p>a) Create a button which is only visible, if the gloCounter is ≥ 30. We will do the following in the &lt;em>.visibility&lt;/em> property of this SUBMIT button:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>b) in the &lt;em>.onSelect&lt;/em> property, we want to run a flow, so we select Power Automate in the menu and create a flow — there is even a template to do this:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1089/1*SEu3rGTKdfhMifx4VKcyxA.png"> &lt;/figure>&lt;/p>
&lt;p>Back in Power Apps, we need to specify the arguments of our run function:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1781/1*NxTfJevTH4mOim6vlfqu-w.png"> &lt;/figure>&lt;/p>
&lt;p>The contextual help will guide you through this like in an excel formula. Basically, all your fields in Power Automate need to now be defined in this step.&lt;/p>
&lt;p>Now that we got our records in the list, I want to display it to my MIU. To make this work, I created a new screen and in this screen new form. I connected in the DATA tab of Power Apps to my SharePoint list and now wanted to show the values of my SharePoint list sorted by highest points. A couple of questions, answered by her Queen of Canvas Apps &lt;a href="https://twitter.com/ClarissaGillin2" target="_blank" rel="noreferrer noopener">Clarissa Gillingham&lt;/a>, google search and &lt;a href="https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-clear-collect-clearcollect" target="_blank" rel="noreferrer noopener">RTFM&lt;/a> I found out how to do this: We first need to create a new collection (we call it colSortedData) and define the order in which we want to display it:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>We do the same thing for our other screens in which our user can practice the other operations.&lt;/p>
&lt;p>&lt;strong>Additional Screens and logic&lt;/strong>&lt;/p>
&lt;p>As we already made a button for the hall of fame, we should make a screen as well 🙂 In this hall of fame, Miss 8 gets stickers /images if she reaches a defined amount of points overall her games. To get this, we need another global variable, I called it gloOverallPoints — so what we need to do is:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>and display the variable gloOverallPoints in our HALL OF FAME and/or MY SCORE screen in a text label&lt;/p>
&lt;p>A Start Screen to navigate through the app and a Success Screen to let Miss 8 know, that she submitted results make the app complete. Let’s see this app in action:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2880/1*X4k68IhEaC7QSH6QWZCsPA.gif"> &lt;/figure>&lt;/p>
&lt;h2 id="learnings">Learnings:&lt;/h2>
&lt;p>I learned: a ton! As this was my very first app and I didn’t touch Power Apps before, everything that described in this post was new to me. But I’d like to share, what helped me the most:&lt;/p>
&lt;ol>
&lt;li>View on variables- You can not only see here all variables that you used globally or in context of a screen, but also have a look at their current value and where you used them. Invalueable while debugging!&lt;/li>
&lt;li>Train your eyes! We often oversee things because we are too fast- rubberduck debugging works in Power Apps as well. Talk something or someone (thanks again to Clarissa) through your code- this slows down your thinking and likelihood that you understand what you did wrong will be so much higher.&lt;/li>
&lt;li>The importance of &lt;a rel="noreferrer noopener" href="https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/formula-reference" target="_blank">docs.microsoft.com&lt;/a> — especially when it comes to all functions- some of them are perhaps familiar to you, but it’s a good idea to read that list of functions to know what’s possible and then lookup syntax when you need it.&lt;/li>
&lt;li>Curiosity helps you to find the little gems like: you can rename everything in your tree view with F2 (like in Excel) or make a new screen like with CTRL+m (like in PowerPoint).&lt;/li>
&lt;/ol>
&lt;h2 id="my-two-cents-on-power-apps-after-this-experience">My two Cents on Power Apps after this experience:&lt;/h2>
&lt;p>Microsoft introduced Power Apps (and the whole Power Platform) as something no-code / low-code -ish environment — and left some people behind with that. Actually, there is lot of code in an app made with Power Apps, but you can’t see it in one view. There are a lot of tiny boxes and panes, which you need to open to see all the code that you have written. So it’s more a trojan horse: it hides the code very well 🙂&lt;/p>
&lt;p>Also, there is no easy way to document your solution (this is one reason why I wrote this blog… quite sure that I will forget at least one thing that I learned by making this app and I will google that in a few months and will be overly excited to find my own blog post ).&lt;/p>
&lt;h2 id="whats-next-and-feedback">What’s next and Feedback&lt;/h2>
&lt;p>If you liked the post, learned something new, reactivated knowledge or just like the #Lego Minifig, then please clap for this blog post. Also make sure that you &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">follow me on twitter&lt;/a>. I already have ideas for next apps to make, but I am open for suggestions as well. May the force be with you.&lt;/p></description></item><item><title>Exploring Microsoft Graph Toolkit as a non-Developer — Part 4</title><link>https://m365princess.com/blogs/2020-04-30-exploring-microsoft-graph-toolkit-as-a-non-developer-part-4/</link><pubDate>Thu, 30 Apr 2020 11:55:03 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-30-exploring-microsoft-graph-toolkit-as-a-non-developer-part-4/</guid><description>&lt;p>Continuing my learning journey with #MSGraphToolkitLap to make good use of the Graph Toolkit I read &lt;a href="https://twitter.com/CameronDwyer" target="_blank" rel="noreferrer noopener">Cameron Dwyer’s&lt;/a> blog about the power of the mgt-get. You can catch up here to read &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-21-exploring-microsoft-graph-toolkit-lap-as-non-developer/">part 1&lt;/a>, &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-23-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-2/">part 2 &lt;/a>and &lt;a href="https://medium.com/@LuiseFreese/exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-3-85b95c9c9d8b" target="_blank" rel="noreferrer noopener">part 3&lt;/a> of my series, if you haven’t done this before.&lt;/p>
&lt;p>I took a look at the &lt;a href="https://github.com/microsoftgraph/mgtLap-TryItOut/blob/master/06%20-%20Power%20of%20mgt-get/index.html" target="_blank" rel="noreferrer noopener">GitHub Repo&lt;/a>, but instead of copy-paste everything and only replacing the IDs, I just copied the CSS code- I am still very slow at writing CSS and I wanted to focus on the web component itself. So I talked myself 3 times through Cameron’s code to really know what it needed to do. As you know: I am (still) not a developer, but I am getting familiar to writing code -step by step.&lt;/p>
&lt;h1 id="mgt-and-sharepoint-lists">MGT and SharePoint lists&lt;/h1>
&lt;p>I work a lot with SharePoint and of course, lists are an amazing feature. So in Cameron’s example of warehouse workers, who need a lightweight app to know which orders to pack, he uses the mgt-get component and “some CSS magic” to nicely display only the orders, that are ready to pack by using a filter.&lt;/p>
&lt;p>Easy to understand, but what took the longest time was to find out where I get this list ID, that I needed to provide in the code. For everyone who doesn’t know as well (or is that long in the business to have already forgotten this):&lt;/p>
&lt;h2 id="get-list-id">get list ID&lt;/h2>
&lt;ul>
&lt;li>Navigate to your list, go to your list settings&lt;/li>
&lt;li>The list ID is now in the URL of your browser and should look like this:&lt;br>
…list=%7B4895e009–4478–4153–94ef-05b172541d38%7D&lt;/li>
&lt;li>Delete everything before “list=” and replace “%7B” by “{“ and “%7D” by “}”. Just in case your list name contains spaces, you need to replace all “%2D” by “-”. That’s it. You can now insert this ID (in my example it’s&lt;br>
{4895e009–4478–4153–94ef-05b172541d38}) into your code.&lt;/li>
&lt;/ul>
&lt;h2 id="index-the-column-you-want-to-filter-by">index the column you want to filter by&lt;/h2>
&lt;p>Next thing that needed my attention was that I obviously first needed to index my “status” column .
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/3384/1*Ke3UvXdIJwloLPMhYMjz7A.png
" alt="">&lt;/p>
&lt;p>So I did this as well:&lt;/p>
&lt;ul>
&lt;li>navigate to List settings, Indexed columns&lt;/li>
&lt;li>then create a new index selecting “status” column as we want to filter by this&lt;/li>
&lt;/ul>
&lt;p>and tried again.&lt;/p>
&lt;h2 id="make-your-data-ready">Make your data ready&lt;/h2>
&lt;p>So what happened when I set two items of my list to the status “ready” and played a bit with the colors and font size in CSS:&lt;/p>
&lt;figure class="wp-block-image">
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1292/1*hfYqYkqqnYUoCWSflWfpCA.png" alt="">
&lt;figcaption>w00t! it works!&lt;/figcaption>&lt;/figure>
&lt;p>Boom — first trial and it already worked the way I wanted it to work! Again, this is seriously LEGO and you can’t do anything wrong as everything is already perfectly shaped and aligned with each other.&lt;/p>
&lt;p>When I first read the blog title “Power of mgt-get”, I thought, “wow, what a click baiting title”. But then I realized how powerful this is web component is, because it is the most flexible one of all components. Instead of just having a very defined purpose like the components I learned about before, it can be used to get literally any data set from the Graph API.&lt;/p>
&lt;p>Playing around with the Microsoft Graph Explorer let me realize, which amount of data regarding people, tools, services can be accessed there, possibilities seem to be endless!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/909/1*yEURnBc6YAwHdCQxrLhiVQ.png" alt="">
&lt;h1 id="conclusion-feedback-and-whats-next">Conclusion, Feedback and what’s next&lt;/h1>
&lt;p>Short and super concise blog post- short recap: mgt-get is the placeholder component in the Graph toolkit, which basically means that you can literally access ANY information in Graph API and make secure, lightweight and easy to build apps to display information that fit your business case. I am curious: What can you imagine? How do you want to use this power for?&lt;/p>
&lt;p>If this post brought you some insights, joy or even food for thought, please clap for it and also &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">follow me on twitter&lt;/a>. Feel free to connect with me and share your thoughts. Can’t wait for next part of the #MSGraphToolkitLap, will blog about that soon.&lt;/p></description></item><item><title>Exploring Microsoft Graph Toolkit Lap as a non-developer -Part 3</title><link>https://m365princess.com/blogs/2020-04-27-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-3/</link><pubDate>Mon, 27 Apr 2020 11:52:00 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-27-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-3/</guid><description>&lt;p>This is part 3 of my blog series about the Microsoft Graph ToolKit Lap — a very hands-on approach by the Microsoft Graph Team to engage the developer community to start using the Toolkit. If you haven’t read the first parts, please &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-21-exploring-microsoft-graph-toolkit-lap-as-non-developer/">catch up here&lt;/a>. So you know that I am not a developer, but I just began to learn about how this set of web components works and how easily even I could change the look and feel of them by using some CSS. As a code newbie, this was not the most natural thing — but as with the provided resources, I was able to achieve the desired outcome — and thanks to the author &lt;a href="https://twitter.com/bernierh" target="_blank" rel="noreferrer noopener">MVP Hugo Bernier&lt;/a> I learned much new stuff as well.&lt;/p>
&lt;p>&lt;strong>#MSGraphToolkitLap Day 5&lt;/strong>&lt;/p>
&lt;p>In this part, &lt;a href="https://twitter.com/franzinifabio" target="_blank" rel="noreferrer noopener">MVP Fabio Franzini&lt;/a> will continue, and I need to confess, when I first skimmed the post, I felt seriously intimidated. But Fabio explained everything in a very concise and well-structured way so I could follow.&lt;/p>
&lt;p>&lt;strong>templates&lt;/strong>&lt;/p>
&lt;p>Wow, my learning curve is steep. I can tell you! A few days ago, I didn’t even have a clue that something like the Toolkit exists or what is a component or the fact that you style it using CSS custom properties — and today, I will learn how to use templates to change the UI. Learned that I could just add a &lt;template> element inside of a web component. I tried out the mgt-agenda component both in the &lt;a href="https://mgt.dev/" target="_blank" rel="noreferrer noopener">MGT Playground&lt;/a> and on my site. But perhaps, the way it renders is not how we want it to behave. So Fabio suggests to&lt;/p>
&lt;ul>
&lt;li>display date and time of the event&lt;/li>
&lt;li>to link to the calendar on click of the title&lt;/li>
&lt;li>to show all participants&lt;/li>
&lt;li>show the description of the event&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>data-type&lt;/strong>&lt;/p>
&lt;p>You can change a lot in components- to tell the component which part of the template you want to modify, you need to use data-type. We will use event view, no data view, and loading view as an example, whereas event view will teach us the most (and will be the hardest to do as well), which is why we focus on it.&lt;/p>
&lt;p>We will need to be able to call functions directly from the template. Whoa, what? Let’s read that again. Calling a function? I knew that already from a JavaScript online course that I started to do a while ago. So I read &lt;a href="https://docs.microsoft.com/en-us/graph/toolkit/templates" target="_blank" rel="noreferrer noopener">docs.microsoft.com&lt;/a> and had a look at the &lt;a href="https://github.com/microsoftgraph/mgtLap-TryItOut/tree/master/05%20-%20Templating" target="_blank" rel="noreferrer noopener">GitHub Repo&lt;/a> and then eventually understood how I could extend the context of the template with the stuff I needed like variables and functions. To me, it sounded like shopping&lt;/p>
&lt;p>¯_(ツ)_/¯&lt;/p>
&lt;p>So let’s go shopping in the mall of functions 🙂 I take three functions to format dates properly:&lt;/p>
&lt;ul>
&lt;li>getDate&lt;/li>
&lt;li>getTime&lt;/li>
&lt;li>formatDate&lt;/li>
&lt;/ul>
&lt;p>Additionally, I want a function to open the calendar:&lt;/p>
&lt;ul>
&lt;li>openWebLink&lt;/li>
&lt;/ul>
&lt;p>and one function to get me the text from within the body of the event for the description:&lt;/p>
&lt;ul>
&lt;li>getTextFromHTML&lt;/li>
&lt;/ul>
&lt;p>I loved how structured Fabio guided me through the lesson, so I understood what he wrote while reading it.&lt;/p>
&lt;p>&lt;strong>Parallels to my maker life&lt;/strong>&lt;/p>
&lt;p>The remarkable thing for me is that I am so used to build stuff in Power Automate / Power Virtual Agent, that my mind tried to “translate” what Fabio teaches me about code and functions to expressions and actions in a flow . In essence, I need to say: it’s pretty similar. Not because of the same or similar syntax or code, but just the way how I would think about a solution.&lt;/p>
&lt;ol>
&lt;li>First divide the monster into easily digestible chunks&lt;/li>
&lt;li>Get the all the small pieces together&lt;/li>
&lt;li>Then put everything together&lt;/li>
&lt;li>Try to make it more pretty /add some styling&lt;/li>
&lt;li>Explain it to someone, who has no clue about it&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>open calendar link&lt;/strong>&lt;/p>
&lt;p>Think this was the hardest part, but again, I learned something very new, as Fabio introduced me to doubled braces like {{event.subject}} — which is just like dynamic content from a variable.&lt;/p>
&lt;p>As we want to do different things in our event card we will need two div. To make this even more elegant, we can use conditional expressions. They work just like an if formula in Excel ¯_(ツ)_/¯: We want to display the date for the end of the event, if it is not the same as the start of the event. Otherwise, it will show the second div. Not that hard to understand, as it makes sense, but the syntax of dates caused me some headache. Need to confess, that I couldn’t write these lines without mistakes on my own for the first two times, but succeeded at the third attempt without needing to look up the code in the blog post / GitHub Repo.&lt;/p>
&lt;p>&lt;strong>show attendees&lt;/strong>&lt;/p>
&lt;p>To show attendees, we can use the mgt-person control and the data-for attribute. I wish, I had a bit more context here, as I didn’t fully understand how this worked, but I was happy enough, that it worked 🙂&lt;/p>
&lt;p>&lt;strong>Putting everything together&lt;/strong>&lt;/p>
&lt;p>All our puzzle tiles fit perfectly together — thank you for designing them with so much care! The biggest issue for me was that I needed to look up how the CSS part worked. I should spend some time learning that as well.&lt;/p>
&lt;p>So I put together everything that I first explored in the MGT Playground into my HTML file, and I realized that my code seriously did what it should do! You can’t beat that feeling, or better: I can’t beat it. Very similar to “Your flow ran successfully 🙂 I would have never guessed that I would be able to do something like this. I Don’t know where this will lead to, but I like what it does with my mind. I love building those bricks and think about how I can modify something pre-built to make it like I want to be.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1605/1*b9k7zT2d5CAsWb-R1pNeZg.png" alt="">
&lt;p>&lt;strong>What’s next &amp;amp; Feedback&lt;/strong>&lt;/p>
&lt;p>If this blog helped you to get started with MGT, please clap and &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">follow me on twitter&lt;/a>. Also, reach out to me and share your views. I appreciate any kind of feedback. And please stay tuned, as I will publish the next part of my learning journey soon.&lt;/p></description></item><item><title>Exploring Microsoft Graph Toolkit Lap as a non-developer — Part 2</title><link>https://m365princess.com/blogs/2020-04-23-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-2/</link><pubDate>Thu, 23 Apr 2020 11:50:00 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-23-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-2/</guid><description>&lt;p>This is the second part of my learning story with #MSGraphToolkitLap — a series by the Microsoft Graph Team to drive community adoption and developer satisfaction. My goal is to learn something new — way outside of my comfort zone and share my journey. If you haven’t &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-21-exploring-microsoft-graph-toolkit-lap-as-non-developer/">read the first part, please keep up here&lt;/a>.&lt;/p>
&lt;p>&lt;strong>MSGraphToolkitLap Day 4&lt;/strong>&lt;/p>
&lt;p>written by &lt;a href="https://twitter.com/bernierh" target="_blank" rel="noreferrer noopener">MVP Hugo Bernier&lt;/a>, who refers to himself as worlds laziest developer. This already sounds promising to me, as I try to avoid work, which just keeps me busy but not productive.&lt;/p>
&lt;p>&lt;strong>web components are easy to customize&lt;/strong>&lt;/p>
&lt;p>TIL, that web components are not only super easy to use (I figured that already out in Day #3), but also easy to customize. I a little bit skeptical about that, but lets jus see how this goes. The first attempt is using the &lt;a href="https://mgt.dev/" target="_blank" rel="noreferrer noopener">Microsoft Graph Toolkit Playground&lt;/a>, which is just an excellent way to try out everything without putting your project at risk or even having the requirement of having your own site. So I tried the people component here first but immediately felt familiar enough to do the same on my site and even used not only HTML but also JavaScript to play around with some attributes. And guess what? It worked!&lt;/p>
&lt;p>The surprise was not that it just worked as described in the blog post, but that I _expected _it to work like that for the first time. To me, this was a lightbulb moment , because usually I try and fail and expect things to fail and make self-fulfilling-prophecy mistakes which then prove me right, that this whole thing is just not made for me. But this time, everything worked smoothly because I was confident enough to believe that it would work.&lt;/p>
&lt;p>&lt;strong>empathy &amp;amp; documentation ♥&lt;/strong>&lt;/p>
&lt;p>What helped me the most to overcome those impostor syndrome attacks? The fact that all authors of this series just describe it the way it is without making me feel less skilled. Super helpful again, that the documentation is linked directly in the blog post and the playground, so you really can’t miss that.&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.mdium.com/max/1598/1*AZgh95okNj1URxH1Za--HQ.png
" alt="">&lt;/p>
&lt;p>&lt;strong>a little bit more code with the mgt-task component&lt;/strong>&lt;/p>
&lt;p>To be able to do some filtering, for example, in the tasks component, you will need to do that in Javascript. So cool, that there is a &lt;a rel="noreferrer noopener" href="https://github.com/microsoftgraph/mgtLap-TryItOut" target="_blank">Github Repo&lt;/a> that comes along with the blog series. So next thing to try was filtering tasks by a string in the title of the task with just three lines — easy-peasy&lt;/p>
&lt;p>Hugo advises not to try this (at home) with HTML — I do not even know how to do that ¯_(ツ)_/¯, even IF I wanted to. I am surely not a coding pro, but if I can manage to make it work — you can do as well. Everything is so well documented that you can copy-paste most of the stuff and then figure out how it works on your own.&lt;/p>
&lt;p>&lt;strong>and even more with mgt-get (wow, this one seems to be powerful)&lt;/strong>&lt;/p>
&lt;p>I also played around with some styling using the CSS for the mgt-get component. I didn’t want this odd greyish color for hovering over my emails but some pink. Thanks to &lt;a href="https://twitter.com/sebastienlevert" target="_blank" rel="noreferrer noopener">Seb&lt;/a> for &lt;a href="https://twitter.com/sebastienlevert/status/1253009138696716288?s=20" target="_blank" rel="noreferrer noopener">the right pink :-&lt;/a>)&lt;/p>
&lt;figure class="wp-block-image">
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/638/1*B_YH6carFcKE46v_FNttLw.png" alt="">
![][2] &lt;figcaption>with #ValoLove &lt;/figcaption>&lt;/figure>
&lt;p>You see, I find this whole process of trying out this box of cool tools not that hard to understand. So I think if you can read carefully and can copy-paste plus show some persistence to try and test what you build, you will find this achievable even if you are not a developer or only have some super basic coding skills like me. Changing attributes and properties to make the component work like you intend it to work seems to be an easy win. But Hugo takes us a level further:&lt;/p>
&lt;p>&lt;strong>last topic for today: mgt-person&lt;/strong>&lt;/p>
&lt;p>Using the mgt-person component, he introduces me to CSS custom properties like &lt;em>— person-card-display-name-font-size&lt;/em> to change the look of a component. I had no idea before that something like this existed. I couldn’t even know because these properties usually don’t exist in CSS — only in the web component in Microsoft Graph Toolkit. Lucky me, that the &lt;a href="https://docs.microsoft.com/en-us/graph/toolkit/components/person-card#css-custom-properties" target="_blank" rel="noreferrer noopener">documentation got me covered&lt;/a> so I could learn about all custom properties of the different components — this was very helpful again.&lt;/p>
&lt;p>I need to admit, that it cost me some minutes and caused me at least me six new grey hairs to make this CSS selector work to give each person a different font color — but just because I didn’t want to copy-paste anymore but write it on my own ¯_(ツ)_/¯.&lt;/p>
&lt;p>The last exercise was about rotating and offsetting the person cards. It was enjoyable to play around with, although I couldn’t imagine a business case for that — but love the fun approach to learn something new- ideal for me!&lt;/p>
&lt;p>To wrap it up: To be able to use the MGT, you will need to know/learn at least some basic HTML / CSS, otherwise it won’t work. But this is what you need as a Low-Code App maker in Power Platform as well — so will already be familiar with that thought. What I like is that the #MSGraphToolkitLap provides me with precise documentation, guided exercises in blog posts by awesome Tech Community speakers and authors, a very helpful GitHub repo plus tools and services that enable me to try out now like MGT Playground and the Dev Tenant.&lt;/p>
&lt;p>&lt;strong>Call to action &amp;amp; Feedback&lt;/strong>&lt;/p>
&lt;p>So if you think: yeah — all nice, but I am not a professional developer / I don’t know enough to start now, hear me out:&lt;/p>
&lt;ul>
&lt;li>READ &lt;a href="https://docs.microsoft.com/en-us/graph/toolkit/overview" target="_blank" rel="noreferrer noopener">docs.microsoft.com&lt;/a>&lt;/li>
&lt;li>try out &lt;a href="https://mgt.dev/" target="_blank" rel="noreferrer noopener">MGT Playground&lt;/a>&lt;/li>
&lt;li>get yourself a developer tenant by joining the &lt;a href="https://developer.microsoft.com/en-us/microsoft-365/dev-program" target="_blank" rel="noreferrer noopener">Microsoft Developer Progra&lt;/a>m (even if you think you are not a dev) and have a sandboxed tenant with admin access to securely try out anything without having to fear to break things.&lt;/li>
&lt;/ul>
&lt;p>If this post was helpful for you and you would like to see more content like this, please clap and &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">follow me on twitter&lt;/a>. I use the official &lt;a href="https://twitter.com/search?q=%23MSGraphToolkitlap&amp;src=typed_query&amp;f=live" target="_blank" rel="noreferrer noopener">#MSGraphToolkitLap&lt;/a> hashtag for tweets around this learning journey to encourage others to step outside and extend their comfort zones as well. Please share your thoughts, as well! You can &lt;a href="https://medium.com/@LuiseFreese/exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-3-85b95c9c9d8b" target="_blank" rel="noreferrer noopener">continue reading here in part 3&lt;/a> of my series&lt;/p></description></item><item><title>Exploring Microsoft Graph Toolkit Lap as non Developer</title><link>https://m365princess.com/blogs/2020-04-21-exploring-microsoft-graph-toolkit-lap-as-non-developer/</link><pubDate>Tue, 21 Apr 2020 11:47:20 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-21-exploring-microsoft-graph-toolkit-lap-as-non-developer/</guid><description>&lt;p>Purpose of this blog post is to let you be part of my personal experiences with the blog post series &lt;a href="https://developer.microsoft.com/en-us/graph/blogs/announcing-a-lap-around-microsoft-graph-toolkit-blog-series/" target="_blank" rel="noreferrer noopener">A Lap Around Microsoft Graph Toolkit&lt;/a> written by The Microsoft Graph team and several MVPs.&lt;/p>
&lt;p>I refer to myself as a not deep technical person. &lt;mark>My job is to make people’s work work&lt;/mark>, which I do as an Microsoft 365 Business Consultant and Power Platform Evangelist. I take away all things that prevent employees from actually adding value to their company in a meaningful way but just keeps them busy with being busy due to odd and old processes, pointless procedures and “But we’ve always did it like that” — mindset.&lt;/p>
&lt;p>As I did the #30dayswithMSGraph &lt;a href="https://developer.microsoft.com/en-us/graph/blogs/announcing-30-days-of-microsoft-graph-blog-series/#" target="_blank" rel="noreferrer noopener">blog post challenge&lt;/a> last year which introduced me into the amazing world of working with Microsoft Graph in Power Automate, I was very happy to see the #MSGraphToolkitLap announcement. I hope that I can share some experiences which might be helpful for&lt;/p>
&lt;ul>
&lt;li>developers, to understand how makers work&lt;/li>
&lt;li>makers, to understand how developers work&lt;/li>
&lt;li>the product team to take into account all developers, regardless if the write code, low-code or seriously no code at all.&lt;/li>
&lt;/ul>
&lt;h1 id="so-lets-get-started-with-day-1-of-msgraphtoolkitlap">So let’s get started with Day 1 of #MSGraphToolkitLap&lt;/h1>
&lt;p>Similar to #30DaysMSGraph the team shares Level 0–200 content, learn- and doable in 5–15 minutes each day, twice a week. Their audience is described as developers, in which I include myself as a maker as well. We will see, how fine this will work.&lt;/p>
&lt;p>Microsoft Graph Toolkit is described as “ a collection of reusable, framework-agnostic web components and helpers for accessing and working with Microsoft Graph”. As far as I understand this, this means basically pre defined LEGO bricks which perfectly fit into each other to be able to use the Graph.&lt;/p>
&lt;p>If you are not familiar with what the Microsoft Graph is, please make sure that you read &lt;a href="https://developer.microsoft.com/en-us/graph/blogs/30daysmsgraph-day-1-why-you-should-learn-the-microsoft-graph/" target="_blank" rel="noreferrer noopener">this article of #30DaysMSGraph&lt;/a> first.&lt;/p>
&lt;p>Recommendation to prepare for the next days of this series is to have Visual Studio Code installed and to already have joined the Microsoft Developer program — which gives you a FREE tenant and admin access — plus: you can’t destroy anything in your own production tenant, which gives you more confidence when trying out everything.&lt;/p>
&lt;h1 id="from-zero-to-hero--day-2-of-msgraphtoolkitlap">From Zero to Hero — Day 2 of #MSGraphToolkitLap&lt;/h1>
&lt;p>I LOVE this from zero to hero approach, as I seriously start at zero when it comes to MGT. But it sounds really tempting to be able to do the first steps with just copy-pasting two lines of code into your editor in a very basic html file.&lt;/p>
&lt;p>I for myself could not believe, that it should be that easy and did, what I always do when I have NO CLUE AT ALL about something. I go to docs.microsoft.com, search for this something and start reading. Can’t tell you how much I love it that Microsoft documents everything and that this gives me such a good overview. Also, when I read first to gather more information and then dig into some detail, it already sounds a little bit more familiar to me. So I started to read &lt;a href="https://docs.microsoft.com/en-us/graph/toolkit/overview" target="_blank" rel="noreferrer noopener">here&lt;/a> and realized, that the way everything is explained is not deeply tech jargon or super sophisticated nerd speak but just extraordinary understandable, which makes me feel very optimistic, that I as a maker could achieve that as well.&lt;/p>
&lt;p>So I learned about components and providers, and although I am far away from understanding every single detail, I found it very helpful to see, where this will lead to. I highly recommend for everyone to start reading the documentation as you will feel more familiar with the content and as you will want to look up details soon.&lt;/p>
&lt;h2 id="register-app-in-aad">Register app in AAD&lt;/h2>
&lt;p>So, Blog post #2 advised me to register an application in Azure AD — as I was already familiar with this due to my previous experiences and #30daysMSGraph (&lt;a href="https://developer.microsoft.com/en-us/graph/blogs/30daysmsgraph-day-10-azure-ad-applications-on-v1-endpoint/" target="_blank" rel="noreferrer noopener">if you don’t know how it works, here is the blog post where I learned it&lt;/a>), this was an easy part. Simply&lt;/p>
&lt;ul>
&lt;li>log in to your shiny new dev tenant on portal.azure.com&lt;/li>
&lt;li>register a new application with a fancy name like “MGT Zero to Hero”&lt;/li>
&lt;li>copy your App ID as you will use it soon&lt;/li>
&lt;li>under authentication set the redirect URI to &lt;a rel="noreferrer noopener" href="http://localhost:3000/" target="_blank">http://localhost:3000&lt;/a> AND  &lt;a rel="noreferrer noopener" href="http://localhost:3000/index.html" target="_blank">http://localhost:3000/index.html&lt;/a> (see below)&lt;/li>
&lt;li>select both checkboxes for implicit grant: Access tokens and ID tokens&lt;/li>
&lt;/ul>
&lt;p>Click the magic save button — that’s it!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2258/1*MBhezDricvmWKyumF_Oxrw.png" alt="">
## Install Live Server
&lt;p>This seemed to be an easy one as well, but as Live Server uses 127.0.0.1 and port 5500 by default, but my application expected these values to be localhost and port 3000. So I set my redirect URI in my app to these values, but still ran into the same odd error message:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1481/1*IektO77JsKomc4EzN31Kqw.png" alt="">
When I changed the port of Live Server in the JSON file that you can find in Live Server settings to localhost and port 3000, and waited a bit, it worked.
&lt;p>I also added &lt;a href="http://localhost:3000/index.html" target="_blank" rel="noreferrer noopener">http://localhost:3000/index.html&lt;/a> as a second redirect URI in my Azure AD app, otherwise I ran into the same error message.&lt;/p>
&lt;p>Note: this BIT of time can be a couple of minutes, in my first attempt it took more than an hour ¯_(ツ)_/¯ — These are challenging times and some services don’t seem to work that fast these days — so please don’t be afraid or give up, just try again after a few minutes (I suggest to just take a break )&lt;/p>
&lt;p>So this took me a while as I was already doubting my skills again -Please, if you read this: don’t doubt your skills. It will work.&lt;/p>
&lt;h2 id="copy-pasting">Copy-pasting&lt;/h2>
&lt;p>Yip, seriously. The Graph Team just made it super easy by providing you not only the html but also the two lines of code (and the mgt loader as well.) All you have to do is&lt;/p>
&lt;ul>
&lt;li>copy-paste the lines into a new file in Visual Studio Code and save it as index.html&lt;/li>
&lt;li>replace the ID placeholder with the App ID you got from your app registration in AAD&lt;/li>
&lt;li>hit the save button / CTRL + s&lt;/li>
&lt;li>hit the GO LIVE button&lt;/li>
&lt;/ul>
&lt;p>Pro Tip: Please do yourself a favor and create a new profile for your dev tenant in your browser and make sure that you have this open BEFORE you hit GO LIVE — only then your website will open in this window (and with no other user on whatsoever tenant is logged in).&lt;/p>
&lt;p>And then you can just celebrate — because it works.&lt;/p>
&lt;p>If you want to try out the &lt;a href="https://docs.microsoft.com/en-us/graph/toolkit/components/agenda" target="_blank" rel="noreferrer noopener">mgt-agenda web component&lt;/a> make sure that you HAVE already at least one meeting in your user’s calendar, otherwise you won’t see anything. I know, this sounds almost logical, but I thought at first, that there would be just a message “no meetings” or something like that. (I checked with F12 in my browser to investigate, if my code worked and I just and no meetings in this test users account).&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1241/1*UFnQrUFzjpWpk2YWaPdVog.png" alt="">
As I mentioned, I already read the documentation of Microsoft Graph Toolkit and I remembered, that there were more web components available — so I tried out the task component as well and noticed something very nice: This can get you both To Do as Planner tasks, which are assigned to that user — but default is planner.
&lt;p>Cool thing: you can not only read the tasks that are already assigned to this user in planner, but also add tasks to planner from within your application!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3384/1*wwNf8YcIR3ceY2spW9k2mQ.png" alt="">
# Day 3 of #MSGraphToolKitLap — Components
&lt;p>In this post Office Development &lt;a href="https://twitter.com/franzinifabio" target="_blank" rel="noreferrer noopener">MVP Fabio Franzini&lt;/a> guides us through two most amazing topics. First thing:&lt;/p>
&lt;h2 id="the-mgt-playground">The MGT Playground&lt;/h2>
&lt;p>You can find this under &lt;a href="https://mgt.dev/" target="_blank" rel="noreferrer noopener">&lt;a href="https://mgt.dev/">https://mgt.dev/&lt;/a>&lt;/a> and you can there try out and learn about components without needing to create your own project. I still recommend to get your own dev tenant — but this Playground is just awesome. It reminds me a bit (or more than just a bit) about both &lt;a href="http://adaptivecards.io/designer" target="_blank" rel="noreferrer noopener">the Adaptive Cards Designer&lt;/a>, in which you can build your own adaptive cards in a visual editor, which will auto generate JSON code for you and the Graph Explorer in which you can try out queries with both sample data and in the data of the tenant of your choice.&lt;/p>
&lt;p>So in this playground I just tried out the web components in a canvas, and whenever I was not sure about what I was actually doing, right next to the Canvas I found the documentation which explained pretty detailed what I needed to understand.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3384/1*fF3Ync2_F21YVUHqELe-Og.png" alt="">
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3384/1*qmpbXYO0xS5yu9NmBqlTxg.png" alt="">
&lt;p>Again, it is super helpful when you just don’t need to search for information, but when it is directly connected to where you actually work /play. Getting content in the right context makes is so much less frustrating when you first need to catch up with some knowledge. After I understood the component, I tried it out in my application by just copy-pasting the lines into my html file, pressed CTRL +s and didn’t even need to refresh my browser to see it working.&lt;/p>
&lt;p>Second thing covered in this Day #3:&lt;/p>
&lt;h2 id="web-components">Web Components&lt;/h2>
&lt;p>I already said it before as I was discovering the web component in docs and the playground before: These web components are super easy to use but seriously powerful! The Graph Team promises to make app development faster and easier as we can work with pre-built bricks, that perfectly fit into each other and no need to reinvent the wheel every single time.&lt;/p>
&lt;p>From my perspective as a maker I need to say that I really like this approach as it matches my experience which I have on Power Platform. It’s a good idea to be already familiar (or make yourself at least now familiar) with permissions, as different actions require different permissions.&lt;/p>
&lt;p>So now excuse me while I try out the different components 🙂&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/4436/1*GRdGcpnAxFuXgAZb68eSFA.jpeg" alt="">
&lt;h1 id="whats-next--feedback">What’s next &amp;amp; Feedback&lt;/h1>
&lt;p>This is the first part of a blog series about my personal experiences learning about the MGT — once the team released new parts I will catch up and write about my experiences.&lt;/p>
&lt;p>You can continue reading about my journey &lt;a target="_blank" rel="noreferrer noopener" href="https://www.m365princess.com/blogs/2020-04-23-exploring-microsoft-graph-toolkit-lap-as-a-non-developer-part-2/">here in part 2 of this series&lt;/a>&lt;/p>
&lt;p>So stay tuned, &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">connect with me on twitter&lt;/a> and please share your feedback. If this article was helpful for you, please clap so I know what you are interested in. If you have any other thoughts or recommendations or cool learning resources related to MGT please tell me 🙂&lt;/p></description></item><item><title>With great power comes great responsibility: Ensure that Microsoft Teams Owners are digitally literate</title><link>https://m365princess.com/blogs/2020-04-17-with-great-power-comes-great-responsibility-ensure-that-microsoft-teams-owners-are-digitally-literate/</link><pubDate>Fri, 17 Apr 2020 12:37:00 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-17-with-great-power-comes-great-responsibility-ensure-that-microsoft-teams-owners-are-digitally-literate/</guid><description>&lt;p>Uncle Ben was right — and if we translate this famous quote into our Microsoft 365 universe we know: &lt;/p>
&lt;p>If we give users great tools with great power, we also need to make sure to properly skill them up. We also need a lean process to deal with common asks.&lt;/p>
&lt;h1 id="work-from-home-is-the-new-normal">Work from home is the new normal&lt;/h1>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/918/1*XKi0__39uTZTTUgT8MVTNg.png
" alt="">
Perhaps, this image relates at least a little bit to you. I for myself can admit, that — although I was already used to work SOMETIMES from home, this crisis is a game changer.&lt;/p>
&lt;p>In my work as a Consultant, I see a lot of companies that weren’t ready for this step — and they now need fast solutions:&lt;/p>
&lt;ul>
&lt;li>Solutions to deploy software at scale&lt;/li>
&lt;li>Skill up their employees&lt;/li>
&lt;li>Simplify IT processes&lt;/li>
&lt;/ul>
&lt;h1 id="so-very-obviously-its-the-time-for-microsoft-teams">So very obviously, it’s the time for Microsoft Teams!&lt;/h1>
&lt;p>Everyone now wants to work with Teams as it provides us the workplace we need to be able to work from anywhere. And with Teams comes the relatively new concept (at least for an enduser) concept of ownership. Owning a Team gives you some very nice powers, but we need to use them wisely and ensure that users know about that. Users nowadays keep asking for new Teams like that:&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2255/1*BcHNN7FyaD1gRwhKXdKFwA.png
" alt="">
But in my daily life as a consultant I see two extremes: On the one hand, there are companies with very restrictive IT departments: They block, sanction and control as most as they can. This leads to users who will become very creative finding a way to get their stuff done. They will use shadow IT solutions which no workers council approved, no data security officer has seen and no one of IT has tested.&lt;/p>
&lt;p>On the other side I see Teams Overadoption. Users, who can just click the magic “Create a new team button” without any restriction, approval, training or whatsoever process, will create a lot of teams, which should be channels, they will create channels that should be a chat. In short: they overadopt.&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2259/1*KvX4XRSHkuLfufvAiGmVng.png
" alt="">
Key therefore is, to balance these extremes. I want the right balance to fulfil both needs of users to just collaborate like it’s intended within Teams and of IT who has the job to ensure not only smooth but also secure operations. Plus, I want to improve the level of digital literacy in the company, which is so important not only on the short term view.&lt;/p>
&lt;p>Perhaps you read &lt;a target="_blank" rel="noreferrer noopener" href="https://regarding365.com/how-to-use-power-virtual-agents-to-simplify-it-and-unf-ck-user-experience-739cdac694b7">my last blog post about Unf*cking User Experience and simplifying IT processes with Power Virtual Agent and Power Automate&lt;/a> -I want to use this solution and take it to the next level with a few tweaks to make sure, that digital literacy is taken into account as well.&lt;/p>
&lt;h1 id="solution-overview">Solution Overview&lt;/h1>
&lt;p>Instead of trying to take over the world I decided to go with this approach:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1239/1*rgby0EJzKwdd2C6xFj7Xgg.png" alt="">
User asks in natural language chatbot for a new team, our first flow picks up this information and checks if the user is already in an Azure AD security group called Educated Users. If the owner to be is already a member in this Educated Users security group, a second flow gets manager’s approval and provisions the team. If the user is not a member of this group, user will be invited for training and test.
&lt;p>If user passes test, he /she will be added to the group of Educated Users ( which means that for the next team request, he/she doesn’t need to pass a test again) and the second flow gets manager’s approval and provisions the team.&lt;/p>
&lt;p>If user doesn’t pass the test OR if manager doesn’t approve, notifications will be send and the process ends.&lt;/p>
&lt;p>To achieve this, I actually thought about Lego — and built it brick by brick:&lt;/p>
&lt;h1 id="lets-first-build-the-basics">Let‘s first build The Basics:&lt;/h1>
&lt;ul>
&lt;li>2 Security Groups in Azure AD for Educated Users and Uneducated Users&lt;/li>
&lt;li>Events for training in an Outlook calendar&lt;/li>
&lt;li>Form for training session invitations in Microsoft Forms&lt;/li>
&lt;li>Flow to send session invitations&lt;/li>
&lt;li>Form to test users&lt;/li>
&lt;li>Flow to log tests in a SharePoint list&lt;/li>
&lt;li>SharePoint list to calculate the result with a few calculated columns&lt;/li>
&lt;li>SharePoint list to log all teams requests&lt;/li>
&lt;/ul>
&lt;h2 id="2-security-groups-in-azure-ad">2 Security Groups in Azure AD&lt;/h2>
&lt;p>Just go to portal.azure.com, click on GROUPS and then on NEW GROUP. Give the groups names like “Educated” or “Uneducated”. Assign your users to the groups — If I were you, all users were by default in the Uneducated Group.&lt;/p>
&lt;h2 id="form-for-training-session-invitations-in-microsoft-forms">Form for training session invitations in Microsoft Forms&lt;/h2>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1163/1*0zm-_qPoOEV6sSRT3xwnYw.png" alt="">
## Flow to send session invitations
&lt;p>This one is quite easy, just get the response details of the form ad use a simple filter query to get the right event from the calendar, then update the event by adding the user to it.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1158/1*s4JQgxH_G6zcq5QMUFAEKw.png" alt="">
## Form to test users
&lt;p>I thought, this would be a super easy one. Forms is able to do surveys (there are no correct answers) and quizzes (there ARE correct answers). I just assumed that I could use the score of a quiz in a flow to see if a user has passed the test – but it appeared, there is no way to do that – so I created a normal survey and used the SharePoint list to calculate the outcome of the test.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2135/1*58GBg1mpK93kILNOYXDwZg.png" alt="">
## SharePoint list to calculate the result with a few calculated columns
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2783/1*PwYQmLgkQ5is6NYVvV9zpQ.png" alt="">
I used the following calculated columns:&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2192/1*MQifaPYJv1lA6JDyuJnQSA.png
" alt="">
## Flow to log tests in a SharePoint list
&lt;p>This flow just creates items in our SharePoint list&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1212/1*M3kgqcJB1recq0ubPW06kA.png
" alt="">
After we build the basics (and even the first two tiny flows ) lets make our hands dirty! We need&lt;/p>
&lt;ul>
&lt;li>a Chat Bot in Power Virtual Agent&lt;/li>
&lt;li>a flow that‘s called from PVA&lt;/li>
&lt;li>a 2nd flow to provision a Team based on the information we got out of the first flow&lt;/li>
&lt;li>to Publish our Bot &amp;amp; add it as an App in Teams&lt;/li>
&lt;/ul>
&lt;p>Here we go — I described part of this process — as already mentioned in one of my last blog posts — but as we deal wit evergreen software and there are now dedicated triggers and actions for Power Virtual Agent, I will give it another round.&lt;/p>
&lt;h2 id="create-a-bot-in-power-virtual-agent">Create a Bot in Power Virtual Agent&lt;/h2>
&lt;p>– very obviously! But don‘t fear! I never build a chat bot before and was able to do that and so will you! We will start very easy.&lt;/p>
&lt;p>Just go to powerva.Microsoft.com, register or log in — there is a free trial. (I will write later some words about licensing)&lt;/p>
&lt;p>Create a new topic and enter some trigger phrases. Don’t try to be too formal, chat bot supports natural language understanding.&lt;/p>
&lt;p>Now you can outline the conversation in the Authoring Canvas. It’s a bit like Visio — very visual interface — no coding required. Only thing is, when you want to do loops or add some more logic, it gets a bit chaotic — so try to keep it simple and let the flow do the logic for you.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3378/1*YIYF5KjrXytfwEiE9q8zuA.png" alt="">
&lt;figure class="wp-block-image">![][12]&lt;/figure>
&lt;p>I just asked all the questions I need to get answered to provision a team like team name, description, owner and visibility. Additionally, I asked questions about the first three members and a channel. I saved all inputs as Variables and gave them easily recognizable names like VarOwner or VarTeamName.&lt;/p>
&lt;p>Now e need to create a flow from within the PVA and then call this flow from the bot.&lt;/p>
&lt;h2 id="a-flow-thats-called-from-pva">&lt;strong>a flow that‘s called from PVA&lt;/strong>&lt;/h2>
&lt;p>It’s very easy — just click on the + sign to create the next node after your last question / message in PVA and click on CALL AN ACTION and then CREATE A FLOW&lt;/p>
&lt;p>The PVA template will open up in a new browser tab. Save this template with a new name. Now it’s time to understand what this flow needs to do:&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2430/1*TIbwYEB3MXhXg875qLxECA.gif
" alt="">
New: no JSON code required — just type in what you want the flow to pick up:&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1116/1*TzAfdta2NaByLzk7ndSyIA.png
" alt="">
Now we need to initialize a ton of variables for all the information the user gives us so we can provision the team like team name, description, privacy, owner, members and first channel. Not everything of that is required for your minimal viable product.&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1140/1*YeF8wtoFWZGQgjxK9xCRfw.png" alt="">
You might have noticed, that I already got the manager of the user as well — I will need the manager later in a notification.&lt;/p>
&lt;p>After we took care of all variables we need to check the group membership of our owner.&lt;/p>
&lt;p>The CHECK GROUP MEMBERSHIP returns the string of the Group ID if a user is a member of the group and will return NULL if the user isn’t member of that group.&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1142/1*vNPaAOaxghumT_cxynpgtg.png
" alt="">
Expression: empty(null)&lt;/p>
&lt;p>If he/ she is in the educated group, we can just log the request in the SharePoint list we already prepared.&lt;/p>
&lt;p>&amp;lt;img loading=&amp;ldquo;lazy&amp;rdquo; class=&amp;ldquo;w-100&amp;rdquo; src=&amp;ldquo;&lt;a href="https://miro.medium.com/max/1170/1">https://miro.medium.com/max/1170/1&lt;/a>*7B58_X0DVis8U9lHXia4Qw.png&lt;/p>
&lt;p>&amp;quot; alt=&amp;rdquo;&amp;quot;&amp;gt;
If the user is still in the Uneducated Group, we need to invite him to a training and test him — (and wait a bit so he / she can complete this).&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/948/1*iAARPY0LMeu5ZAvRKxTpgw.png" alt="">
To invite the user to the training and link him / her to the test, I used an Adaptive Cards. If you never used Adaptive Cards before, just go to&amp;nbsp;&lt;a href="https://adaptivecards.io/designer" target="_blank" rel="noreferrer noopener">https://adaptivecards.io/designer&lt;/a>, select MICROSOFT TEAMS as host applications and replace the text of one of the samples with your text in the visual editor. Below, the Designer autogenerates some JSON for you — copy-paste this into a POST YOUR OWN ADAPTIVE CARD AS A FLOW BOT TO A USER action.
&lt;p>This is how our Card look like then:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1274/1*jsnSUlXSVcnjGNxXlO-2IQ.png" alt="">
The clickable buttons link directly to the forms for training sessions (remember, we already built a flow to invite users automatically!) and the quiz (yet again, our flow logs the answers and SharePoint calculates the result for us!)
&lt;p>Now need to know if user passed the test:&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/936/1*IKXhwmq_q-paCOLVK7sLFA.png
" alt="">
If user passes the test, he / she will be added to the Educated Group and we log the request in SharePoint. If user doesn’t pass, we will just send notifications and end the process.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1517/1*qf2kMH44OFOc55k6bHvlJA.png" alt="">
Small hint: If you are testing your flow, do yourself a favor and create an additional flow to undo the actions of group membership 🙂
&lt;p>Now the core of our work:&lt;/p>
&lt;h2 id="create-a-2nd-flow-to-provision-a-team-based-on-the-information-we-got-out-of-the-first-flow">Create a 2nd flow to provision a Team based on the information we got out of the first flow&lt;/h2>
&lt;p>Before we are going to create this flow, we need an overview about what this flow will be doing:&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2430/1*GqQrgt4LJccb0of2Gxx9VQ.gif" alt="">
So when I first wanted to automate the provisioning of teams with Power Automate, I thought — how hard can this be? But then, I was like, Bummer — there is no “Create a team” action in flow! So what could I do?&lt;/p>
&lt;h2 id="microsoft-graph-to-the-rescue">Microsoft Graph to the rescue!&lt;/h2>
&lt;p>So, actually, we can use the Microsoft Graph to create Teams, add members, create channels and a lot more, but we first need to authenticate to make this magic happen.&lt;/p>
&lt;p>To do so, we need to&lt;/p>
&lt;h2 id="register-an-app-in-azure-ad">register an app in Azure AD&lt;/h2>
&lt;p>For everyone, who is not super familiar with that concept, please go to portal.azure.com and click on APP REGISTRATIONS, and click NEW REGISTRATION. Give it a nice name and save the ID of your tenant and the ID of our App (Client) After that, click on API PERMISSIONS (use APPLICATION) and select MICROSOFT GRAPH. We need to add the Group.Read.Write.All permission and grant admin consent for that as well. If you are your own Global Admin — congrats! If not, you need to wait until this is granted by admin.&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/2346/1*QJtFM_kuHZgd4hJ3XEwOXw.png"> &lt;/figure>&lt;/p>
&lt;p>To make it work, we also need an App Secret. Please, save this little dude when you see it — because you won’t see it anymore again. And funny thing is: It will expire the day you want to demo it! — It’s like when your machine wants to update 10 minutes before your session starts. # BeenThereDoneThat&lt;/p>
&lt;h2 id="this-is-what-you-need-to-do-in-the-2nd-flow-in-power-automate">This is what you need to do in the 2nd flow in Power Automate:&lt;/h2>
&lt;p>Your trigger is WHEN A NEW ITEM IS CREATED (remember, the PVA flow will end with this action, so basically, the PVA flow kicks off our second flow )&lt;/p>
&lt;p>Now we need to initialize the following variables:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/722/1*g1XfdhI2erA_ND2FSgv0tQ.png"> &lt;/figure>&lt;/p>
&lt;ul>
&lt;li>Tenant ID, App ID, App Secret are strings and we get all these IDs out of the app registration of the previous step&lt;/li>
&lt;li>Group ID is a string as well but is empty for now&lt;/li>
&lt;li>I was so tired of typing the Graph URL over and over that i put it into a var as well — this is optional — but bonus points as you are now on the same level of laziness as I am 🙂&lt;/li>
&lt;li>We will later need the MailNickname to provision the Team.Mailnickname is the Displayname of the Team WITHOUT spaces. So I just used a super simple replace expression: replace(triggerBody()?[‘Title’],’ ’,’’) which just means, replace all spaces with nothing&lt;/li>
&lt;/ul>
&lt;h2 id="managers-approval">Manager’s Approval&lt;/h2>
&lt;p>I’m quite sure you already saw this before, but in case you haven’t, this is how it works as I want again a nice and fancy Adaptive Card in Teams for the Approval:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/729/1*B7E-lg13v_JPeDR7g7yHCA.png"> &lt;/figure>&lt;/p>
&lt;p>Depending on the outcome we let the Microsoft Graph create first a group an then update it to a team or we will end the process if manager doesn’t approve. Here is what happens if the Outcome is not Approved:&lt;/p>
&lt;p>We update our SharePoint list (status is now rejected) and we post an other Adaptive Card to our user to inform him / her and terminate the process.&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/761/1*RtJuv1r-6vCeEu_fKFKMVg.png"> &lt;/figure>&lt;/p>
&lt;p>If the Outcome of the Approval is Approved, we need to update our List as well and add an HTTP Call to first create a Group:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/780/1*8uvetrM2ILYccIPYgi8rjA.png"> &lt;/figure>&lt;/p>
&lt;p>You can find all required JSON on &lt;a href="https://docs.microsoft.com/en-us/graph/api/resources/teams-api-overview?view=graph-rest-1.0" target="_blank" rel="noreferrer noopener">docs.microsoft.com&lt;/a> — Plus it is highly recommended to first test everything in &lt;a href="https://developer.microsoft.com/en-us/graph/graph-explorer" target="_blank" rel="noreferrer noopener">Graph Explorer&lt;/a>&lt;/p>
&lt;p>As we do not only want an Office 365 Group (Caution, Microsoft is renaming to Microsoft 365 groups!) but also a Team based on that group, we need the Group ID. To get this ID (remember, we initialized an empty var for that already!) , we need the parse JSON action and set our Group ID var to that value:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/713/1*WEP5I-7bMuCUOfF-mxnz4g.png"> &lt;/figure>&lt;/p>
&lt;p>Now it’s time to use another two HTTP calls for creating the Team and adding the channel:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/723/1*WjYT2mpWm4yyBj9Pe1JZlQ.png"> &lt;/figure>&lt;/p>
&lt;p>Please keep in mind to expand the SHOW ADVANCED OPTIONS and enter all authentication information as shown in the Create a group step. Now update your SharePoint list (status is no created) and inform your user with another Adaptive Card in Teams&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/711/1*bE8Qa9U83s637cxW4q25MQ.png"> &lt;/figure>&lt;/p>
&lt;p>&lt;strong>Publish our Bot &amp;amp; add it as an App in Teams&lt;/strong>&lt;/p>
&lt;p>To publish your Bot, just click on Publish in PVA and choose Microsoft Teams as Channel. Copy the APP ID and open App Studio in Teams, where you can create apps. Paste in this App ID and fill in Name, Description and some links for your privacy statement and terms of use. As valid Domain use token.botframework.com. Download your app as a package and then install it from Teams App Catalogue — There you go!&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/3384/1*lgD1uCrD6OIqBViEVGucSQ.png"> &lt;/figure>&lt;/p>
&lt;p>This is our result as a Gif:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/2430/1*YfOLB5id_J9rhTlR5jWHyw.gif"> &lt;/figure>&lt;/p>
&lt;h2 id="1f50">Coming back to the purpose of solutions like this:&lt;/h2>
&lt;p>The goal is to enable users and to give them great powers! We now have an easy maintainable solution for IT and a very lean process for the business side of a company to request common asks. We are more efficient as we only need to involve human working time if needed. We don’t need to spend lots of time to make users adopt this system as the interface is easy to understand even for users who are not that tech savvy.&lt;/p>
&lt;p>And we have a good chance to narrow the historical gap between business and IT. I mean, by background I am not an IT person myself, so I can really understand users, who feel uncomfortable with smart ass IT people who know everything better. And I can also relate to my IT colleagues, who are just tired of blocking everything instead of being able to enable users. So again, this is really a nice move towards each other. It’s a #BetterTogether story.&lt;/p>
&lt;h1 id="licensing">Licensing&lt;/h1>
&lt;p>A few words about Licensing. Both Power Automate and Power Virtual Agents are not free — so I will show you everything I know about licensing: It’s next to godliness.&lt;/p>
&lt;p>Microsoft provides somehow a gazillion pages of &lt;a rel="noreferrer noopener" href="https://docs.microsoft.com/en-us/power-platform/admin/pricing-billing-skus" target="_blank">licensing guide&lt;/a> and I hope that at some point, it will be as easy to understand that like it is to use a chat bot&lt;/p>
&lt;p>Good thing: there is a free Community plan to learn and share!&lt;/p>
&lt;h1 id="feedback-questions">Feedback, Questions?&lt;/h1>
&lt;p>Please clap if you liked this post and give me feedback if you want to add your thoughts on that. Looking forward to connect with you on &lt;a href="https://www.twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">twitter&lt;/a> as well.&lt;/p></description></item><item><title>How to learn sketchnoting even if you can’t draw and what sketchnotes taught me</title><link>https://m365princess.com/blogs/2020-04-15-how-to-learn-sketchnoting-even-if-you-cant-draw-and-what-sketchnotes-taught-me/</link><pubDate>Wed, 15 Apr 2020 09:42:22 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-15-how-to-learn-sketchnoting-even-if-you-cant-draw-and-what-sketchnotes-taught-me/</guid><description>&lt;p>in this blog post I will show you how to start sketchnoting and how I overcame a lot of false beliefs. I often hear the following reasons why someone believes that he/she can’t begin to sketchnote:&lt;/p>
&lt;h3 id="1-but-i-can8217t-draw">1. “But I can’t draw!”&lt;/h3>
&lt;p>You might think, “I cannot draw” or “I don’t have talent”, and maybe you are right. However, the good news is: sketchnoting is not about art. It’s not about performing as an artist or comparing yourself to famous artists like Picasso or Monet. It’s not about perfection, it’s about communication.&lt;/p>
&lt;p>Sketchnotes help people all over the world to better remember what they heard, regardless of their drawing skills. Instead of focusing on whether my drawings look pretty or otherwise, I try to see which stories I can tell and the insights I can come up with. It is more important to me to provide visual summaries that everyone can understand than to create something that looks picture-perfect. I try not to judge my quickly drawn stick figures or compare myself to professional graphic recorders.&lt;/p>
&lt;p>Whenever I used “I can’t draw” as an excuse to just not draw, this was a perfect self-excuse not to practice. Referring to my lack of talent was just about avoiding responsibility for my progress. If you practice, you will get better. This applies to almost everything.&lt;br>
 &lt;br>
&lt;strong>Learning: Be kind to yourself.&lt;/strong>&lt;/p>
&lt;h3 id="2nbspbut-i-can8217t-work-under-pressure">2. But I can’t work under pressure!&lt;/h3>
&lt;p>Although I started sketchnotes just for myself, it somehow happened that I became kind of famous in the community for it, and it soon became part of my job at conferences where people asked me, “Hey Luise, will you sketchnote the session in room A? Because if so, I will be happy to join the session in room B. Both interest me so much, but if you sketch one of them, I am sure I won’t miss a thing as your sketchnotes provide a compelling summary”. This is, of course, nice and I am flattered to hear that, but it also puts some pressure on me – as I get more exposure, people often expect me to deliver -and deliver good quality. I can’t ignore the fact that I don’t do this anymore just for myself – so I try to find the right balance between documenting content and my own thoughts sinceI know that I sketchnote with the intention to publish my work. I am just trusting my own process of listening by drawing. I am very aware of the fact that I can’t deliver a perfect recording of a session, because I will miss some things. I am busy with drawing/writing/correcting /reshaping/coloring, or I am just not fast enough to capture the essence of a slide. During some demos, I am not able to note down all single steps, but I need to filter and condense what’s behind the demo, the overall concept. I’ve discovered that the more I let go, learn to trust my process and stop pushing myself into “you missed this part / you need to draw faster,” the more I can find good images and inspiration. This helps prevents me from writing too much stuff that is not needed to later understand my sketchnote. My most surprising, touching, and significant learning at all: &lt;strong>Although I am not a professional artist, although I don’t produce perfect notes – community welcomed me and my skills, and showed appreciation for what I do.&lt;/strong> So, despite me being insecure and imperfect, the community encouraged me to continue – which made me better – with both drawing and knowing that what I do is right. I imposter so often about my skills, and am pleased to be surrounded by awesome people who help me through this!&lt;br>
 &lt;br>
&lt;strong>Learning: Letting go is a skill that helps me to focus &lt;/strong>&lt;/p>
&lt;h3 id="3nbspbut-i-can8217t-do-it-like-you">3. “But I can’t do it like you!”&lt;/h3>
&lt;p>In our tech community, there aren’t many known sketchnoters, and I seem to be one of the firsts. But if we broaden our view beyond tech community, we can see a lot of visual thinkers, and we can learn from them. Some sketchnoters work more visually, and some do a lot of lettering, some give an overview, some are more detailed. There are visualizers that sketch in real-time as I do and those who prepare their drawings in advance. But instead of feeling intimidated by others one’s skills, I just appreciate the diversity of sketchnotes and focus on what I can learn from that. &lt;strong>I would love to see more visual thinkers in our community.&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Learning: Let’s celebrate uniqueness&lt;/strong>&lt;/p>
&lt;h3 id="4nbspi-can8217t-do-two-things-at-once">4. “I can’t do two things at once!”&lt;/h3>
&lt;p>Sketchnoting is fun, and the experience of drawing can help you relax as you are just following a flow. Sketchnoting forces you to stick to this one task without interrupting yourself and without switching to other tasks. This can calm your mind and will improve your learning as multitasking is one of the biggest showstoppers for education. Once you commit to doing the job and don’t distract yourself, you will realize that this is an intense experience – and when you get used to that, chances are high that you want to extend this feeling to other workloads as well.&lt;/p>
&lt;p>To me, it’s not listening AND drawing, but listening by drawing. Drawing is just the way I learnt to process information, like for some of you writing blogposts or preparing sessions is just the way you can guide yourself through your thoughts.&lt;br>
 &lt;br>
&lt;strong>Learning: Listen to understand&lt;/strong>&lt;br>
 &lt;br>
Let’s dig a bit more into the practical side of sketchnoting: &lt;/p>
&lt;p>&lt;strong>Preparation&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Know necessary session information: exact title, conference hashtag, speaker’s twitter handle&lt;/li>
&lt;li>Set up your app: choosing pens, show gridlines&lt;/li>
&lt;li>Pre-draw session information if needed  &lt;/li>
&lt;/ul>
&lt;p>Start listening!&lt;/p>
&lt;ul>
&lt;li>A good speaker will give you some intro (sometimes also known as “sign-posting”), with verbal cues like “take these five steps to…” or “three different approaches to…” The better a speaker is prepared in providing a concise and well-structured talk, the more likely it is that I will produce a sketchnote that captures the ideas. So if you see a compelling and easy to understand sketchnote, kudos go in the first place to the fantastic speaker.&lt;/li>
&lt;li>Keep in mind to let go of any approach of perfection and just trust the process. If you can just write down a single idea, you already won! And with practice comes progress.&lt;/li>
&lt;/ul>
&lt;p>To get you up and running, you can follow my sway presentation right here:&lt;/p>
&lt;p>&lt;a rel="noreferrer noopener" href="https://sway.office.com/KedDggXCAClkjUll?ref=Link%20" target="_blank">&lt;a href="https://sway.office.com/KedDggXCAClkjUll?ref=Link">https://sway.office.com/KedDggXCAClkjUll?ref=Link&lt;/a>&lt;/a>&lt;/p>
&lt;p>originally published on &lt;a href="https://techcommunity.microsoft.com/t5/humans-of-it-blog/the-uptake-podcast-guest-speaker-spotlight-luise-freese/ba-p/1304006">https://techcommunity.microsoft.com/t5/humans-of-it-blog/the-uptake-podcast-guest-speaker-spotlight-luise-freese/ba-p/1304006&lt;/a>&lt;/p></description></item><item><title>TimeTracking Tool with #PowerPlatform and Adaptive Cards</title><link>https://m365princess.com/blogs/2020-04-02-timetracking-tool-with-powerplatform-and-adaptive-cards/</link><pubDate>Thu, 02 Apr 2020 12:34:00 +0000</pubDate><guid>https://m365princess.com/blogs/2020-04-02-timetracking-tool-with-powerplatform-and-adaptive-cards/</guid><description>&lt;p>In this blogpost I would like to show you some easy steps with which you can not only track the time you spend on a project and it’s different tasks, but also share this with team members or even your customer.&lt;/p>
&lt;p>A few months ago, I posted my very basic timetracking tool, but as I evolved in my journey and improved my skills, I modified my flows.&lt;/p>
&lt;h1 id="what-i-want">What I want:&lt;/h1>
&lt;ol>
&lt;li>Data shall be SharePoint list&lt;/li>
&lt;li>Visualization in Power BI&lt;/li>
&lt;li>two mobile buttons to define start and end of my work&lt;/li>
&lt;li>summing up all the hours I worked on&lt;/li>
&lt;li>inform my customer what I did&lt;/li>
&lt;/ol>
&lt;h2 id="sharepoint">SharePoint&lt;/h2>
&lt;p>Create a list with the following columns&lt;/p>
&lt;ul>
&lt;li>Title&lt;/li>
&lt;li>Description (single line of text)&lt;/li>
&lt;li>Status (single line of text, default value = open)&lt;/li>
&lt;li>Start (Date and Time)&lt;/li>
&lt;li>End (Date and Time)&lt;/li>
&lt;li>Duration (calculated column, = 24*(End-Start), display as number with 2 decimal places)&lt;/li>
&lt;li>Totals (number)&lt;/li>
&lt;/ul>
&lt;h2 id="power-bi">Power Bi&lt;/h2>
&lt;p>Create a Streaming Dataset on &lt;a href="https://powerbi.microsoft.com/" target="_blank" rel="noreferrer noopener">&lt;a href="https://powerbi.microsoft.com">https://powerbi.microsoft.com&lt;/a>&lt;/a> (Click on the workspace you want to work in and then click on CREATE in the upper right corner, then Streaming dataset). Now click on API, NEXT.&lt;/p>
&lt;p>You will need to give your dataset a meaningful name and define the values. I went for&lt;/p>
&lt;p>&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/1235/1*lTDEdfUr42Yd9h8PAxcQFg.png
" alt="">&lt;/p>
&lt;ul>
&lt;li>workload (text)&lt;/li>
&lt;li>task (text)&lt;/li>
&lt;li>hours (number)&lt;/li>
&lt;/ul>
&lt;p>Please set the toggle of historic data analysis to ON and click CREATE.&lt;/p>
&lt;p>Now click on DONE.&lt;/p>
&lt;p>You can now create a report from this dataset, just click on the little chart icon:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2741/1*tkLW39Piw0SPGblmyXHQXw.png" alt="">
## Power Automate
&lt;p>We will need to flows for START and END.&lt;/p>
&lt;p>&lt;strong>Start flow:&lt;/strong>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1029/1*9YUix3cSNfyEA7_lYtMdDg.png" alt="">
With a manual flow trigger button and 2 fields I kick off my first flow — asking myself on which workload I am currently working on and if I have any notes. I defined a dropdown menu for that so it is most convenient to fill out on mobile 🙂&lt;/p>
&lt;p>I just pass this to my SharePoint list and make sure using the timestamp of the button to be in the START column.&lt;/p>
&lt;p>But the real magic comes with the next steps 🙂&lt;/p>
&lt;p>As I want to give my team colleagues / customer / fellow unicorn a notification, what I do, I thought about a fancy adaptive Card, which gets the input from my SharePoint list and will be posted in Teams 🙂&lt;/p>
&lt;h2 id="adaptive-card">Adaptive Card&lt;/h2>
&lt;p>To create your own Adaptive Card, just go to h&lt;a rel="noreferrer noopener" href="https://adaptivecards.io/designer/" target="_blank">ttps://adaptivecards.io/designer/&lt;/a>, select Microsoft Teams as your host app and make yourself a bit familiar with the card elements. You can open samples to see what is possible or start creating one from scratch. While you are busy with draggy droppy (learned this term at #SS2020 from &lt;a rel="noreferrer noopener" href="https://twitter.com/ScottDurow" target="_blank">Scott Durow&lt;/a> ) the adaptive Card designer creates nice JSON for you. Don’t worry, if you are not a dev — same here! I learned from &lt;a rel="noreferrer noopener" href="https://twitter.com/KeithWhatling" target="_blank">Mr. Power Apps in person Keith Whatling&lt;/a>, that JSON is just text in curly brackets {}( although I don’t call them anymore ‘curly brackets’ but ‘braces’).&lt;/p>
&lt;p>Just understand, that the ACs are just a design schema. You only define the structure, but not the design itself. You can insert text, images, tables in your card and the card will later have the look &amp;amp; feel of the application you post it in — therefore we call it ADAPTIVE.&lt;/p>
&lt;p>So just copy /paste the code to your clipboard.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3384/1*zl5Sl8MJfc97vvV9j9cORQ.png" alt="">
Insert another action to your flow: Post your own Adaptive Card as the flow bot to a user and replace some of your placeholders with dynamic content from previous steps of your flows. You can also then copy/paste this code back to your AC designer — I really like the experience there is I have a preview how my card looks like. I also tried out the AC extension in Visual Studio Code, it’s pretty neat as well- (Just open Extensions Marketplace and search for Adaptive Card Viewer).
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1850/1*W05aCs9OowoMevm0d_euGg.png" alt="">
If you want to learn more about Adaptive cards, here is a link to last year’s #MSIgnite session of&amp;nbsp;&lt;a href="https://twitter.com/MattHidinger" target="_blank" rel="noreferrer noopener">Matt Hidinger&lt;/a>&amp;nbsp;and&amp;nbsp;&lt;a href="https://twitter.com/TimCadenbach" target="_blank" rel="noreferrer noopener">Tim Caldenbach&lt;/a>&amp;nbsp;— also including a sketchnote I did from this session:
&lt;p>&lt;a href="https://developer.microsoft.com/en-us/sharepoint/blogs/adaptive-cards-community-call-november-14-2019/" target="_blank" rel="noreferrer noopener">&lt;a href="https://developer.microsoft.com/en-us/sharepoint/blogs/adaptive-cards-community-call-november-14-2019/">https://developer.microsoft.com/en-us/sharepoint/blogs/adaptive-cards-community-call-november-14-2019/&lt;/a>&lt;/a>&lt;/p>
&lt;p>Also an awesome ressource of incredible in-depth knowledge on that is &lt;a href="https://twitter.com/TomaszPoszytek" target="_blank" rel="noreferrer noopener">Tomasz Poszytek&lt;/a>.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/489/1*W24ODqJYzcfisgEJ2VFjAw.png" alt="">
Ok, after we made a short tour to Adaptive Cards, we need to take care of our
&lt;h2 id="end-flow">End Flow&lt;/h2>
&lt;p>We again start with a mobile button: and insert an action to get the item of our SharePoint list that is still in status OPEN. Remember? Open was our default value 🙂&lt;/p>
&lt;p>Now we want to UPDATE ITEM with the timestamp of this button — and we want to change the Status to DONE.&lt;/p>
&lt;p>The flow will *automagically* add an APPLY TO EACH.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/831/1*kYwXbym5Kb3AcsCtdIFFlQ.png" alt="">
**Sums**
&lt;p>As I wanted the sums of my working hours, we need to put in some work on that. Shoutout to &lt;a rel="noreferrer noopener" href="https://twitter.com/mattbdevaney" target="_blank">Matthew Devaney &lt;/a>who introduced me to the float type. (Knew that one in JavaScript, but didn’t think about using it in a flow)&lt;/p>
&lt;p>The Problem is: we can’t easily get totals for our calculated column in SharePoint as this is just not a supported feature 🙁&lt;/p>
&lt;p>So what we will do is, that we will use the get items action (this time, to get ALL items) and initialize two variables. The First variable CurrentTotal shall be type Float and the value needs to be 0. Without this, it doesn’t work!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/710/1*_60klxoHarMxzrm6rVlUtg.png" alt="">
The second variable tempNumber is type String. We will use both variables in the next step:
&lt;p>First, we set the variable tempNumber with our Duration column, then we increment our CurrentTotal variable. For the value just use this easy expression:&lt;/p>
&lt;p>&lt;em>float(variables(‘tempNumber’))&lt;/em>&lt;/p>
&lt;p>Now just update TOTALS column of the list with the CurrentTotal:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/738/1*2tU7ltIewkID2XnOorVP4Q.png" alt="">
**Visualisation**
&lt;p>Remember our Streaming dataset in Power Bi? We can now easily connect this to our flow. Just insert the Power BI action INSERT ROWS TO A DATASET, choose your workspace, your dataset and fill in TITLE, DESCRIPTION and DURATION from dynamic content.&lt;/p>
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/1530/1*UQ5gTQ9HGX-ockRznYSghg.png" alt="">
## your mobile phone
&lt;p>If you don’t have the Power Automate app yet on your mobile phone, please install it, log in and refresh the BUTTONS (just swipe down). You should now see your two new buttons — Start and End.&lt;/p>
&lt;h1 id="summary">Summary&lt;/h1>
&lt;p>The Start flow creates a new row in your SharePoint list and sends an adaptive Card to your teammates to inform them what you are currently doing. The End one updates your list, sums up your working hours and passes your data to Power Bi where you can create a nice report from that.&lt;/p>
&lt;h2 id="feedback">Feedback?!&lt;/h2>
&lt;p>Did you like this post? Then please let me know and clap for it! Do you have thoughts on that? Or questions? Or do you know other ways to do this?&lt;/p>
&lt;p>&lt;a href="https://twitter.com/LuiseFreese" target="_blank" rel="noreferrer noopener">Find me on twitter&lt;/a>&lt;/p></description></item><item><title>How to use Power Virtual Agents to simplify IT and unf*ck User Experience</title><link>https://m365princess.com/blogs/2020-02-12-how-to-use-power-virtual-agents-to-simplify-it-and-unfck-user-experience/</link><pubDate>Wed, 12 Feb 2020 12:24:57 +0000</pubDate><guid>https://m365princess.com/blogs/2020-02-12-how-to-use-power-virtual-agents-to-simplify-it-and-unfck-user-experience/</guid><description>&lt;p>In this blog post I want to show you how you can use the whole Power of the Power Platform to improve user experience, narrow the traditional gap between IT and Business side of a company and simplify IT processes.&lt;/p>
&lt;p>It won’t be my shortest blog post so you’d better grab some wine 🙂&lt;/p>
&lt;p>Ready? Here we go!&lt;/p>
&lt;h1 id="lets-build-a-thing">Let’s build a thing!&lt;/h1>
&lt;h2 id="story">Story&lt;/h2>
&lt;p>Users, who want to request new team in Microsoft Teams can easily interact with a Chat Bot in Microsoft Teams. They will be guided through the whole process.&lt;/p>
&lt;p>After an approval by a manager the team will be created automatically and the owners will be informed.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2990/1*-DEPExRlx_d_ef9UQGvxzQ.png" alt="">
## Process
&lt;ol>
&lt;li>Power Virtual Agent asks questions about:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Team Name&lt;/li>
&lt;li>Team Description&lt;/li>
&lt;li>Guest Status&lt;/li>
&lt;li>First and second owner&lt;/li>
&lt;li>Visibility&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>
&lt;p>Power Automate will pick up the inputs in our FIRST FLOW and create items for each team request in a SharePoint list&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Our SECOND FLOW in Power Automate will be triggered by a new list item. It will run an approval and provision the Team after that by making use of Microsoft Graph. Owners and Approvers will be informed in Teams.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3000/1*2nE5rl-Jy0mXAQ0_AtG35w.png" alt="">
# Power Virtual Agent — part 1
&lt;p>&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2879/1*NZg-c3Ohp6oBEMkoF7zn-A.png
" alt="">&lt;/p>
&lt;ol>
&lt;li>Go to &lt;a href="https://powerva.microsoft.com/" target="_blank" rel="noreferrer noopener">&lt;a href="https://powerva.microsoft.com">https://powerva.microsoft.com&lt;/a>&lt;/a> and Sign up or sign in, if you already signed up&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/4059/1*7Cx-U9tc96_YrMAtcBK81w.png" alt="">
2. Click on the Bot Panel which you will find in the upper right corner
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1361/1*TPXE9TSuthpYOSSPWaEXtg.png" alt="">
(don’t judge me for the rainbow design) and create a new Bot, type in a name and click CREATE. It is normal that this can take up a minute or so — don’t worry!
&lt;ol start="3">
&lt;li>Your screen should look like this now:&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/4092/1*2mjSOSsAWTsUwMT1r7KGnw.png" alt="">
You can now just click on TOPICS in your navigation.
&lt;ol start="4">
&lt;li>You will see some pre-defined topics and you can actually try them out with the TEST BOT feature:&lt;/li>
&lt;/ol>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/4098/1*V1EsVIPq7_JxhOl-9hoiRA.png"> &lt;/figure>&lt;/p>
&lt;p>When you are done with tat, you may reset the Test Bot by clicking RESET.&lt;/p>
&lt;ol start="5">
&lt;li>Now that you played around with that, it’s time to create your own topic. Click + NEW TOPIC, give your topic a name and enter some trigger phrases. These trigger phrases will wake up the bot.&lt;/li>
&lt;/ol>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*QfcPGfZAK4CQoEl5FoZtBQ.png"> &lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>Click GO TO AUTHORING CANVAS where you will outline the process:&lt;/li>
&lt;/ol>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*n8GBamrUSh3riDKap5hkOw.png"> &lt;/figure>&lt;/p>
&lt;p>You can now add and edit actions like you may know this from Power Automate.&lt;/p>
&lt;p>So just edit the first MESSAGE and create a new QUESTION.&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/2765/1*Y4PlSrHZl1LcNz8q8Rbwpg.png"> &lt;/figure>&lt;/p>
&lt;p>It is very important to check if the Chat Bot and the user are on the same page, therefore we want the user to confirm that he wants to request a new team in Microsoft Teams. As the answer to that question should be YES or NO its a good idea to identify this as BOOLEAN:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/2763/1*vsTPCLuN75_W-u_P-XfmVQ.png"> &lt;/figure>&lt;/p>
&lt;p>and actually name the variable.&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/2768/1*VO5co3aebF92oIbqfPOgog.png"> &lt;/figure>&lt;/p>
&lt;p>Now we want to add a condition and some messages depending on the outcome:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2759/1*ZI_5zg1XlqJjDHiIpOD_Xg.png"> &lt;/figure>&lt;/p>
&lt;p>From now on, we will continue in the IF TRUE branch.&lt;/p>
&lt;p>Start with asking for the Team Name, identify the answer as User’s entire response (which is a string) and name your variable.&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/764/1*_IfhrQh-FBspztEh12GuGA.png"> &lt;/figure>&lt;/p>
&lt;p>Ask the other questions as stated above as well,&lt;/p>
&lt;p>please keep in mind,&lt;/p>
&lt;ul>
&lt;li>that questions, which can be answered with a YES or a NO are always BOOLEAN,&lt;/li>
&lt;li>questions which can be answered with predefined responses are CHOICE (we don’t use them in this scenario) and&lt;/li>
&lt;li>only if we want the user just to type a text it’s USER’S ENTIRE RESPONSE&lt;/li>
&lt;/ul>
&lt;p>So finally, this should look like this:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/2762/1*m-V_tvhOdPyp0pXdJjSJ-Q.png"> &lt;/figure>&lt;/p>
&lt;figure class="wp-block-image">![][16]&lt;/figure>
&lt;p>you can actually test the bot right now. To do that, please SAVE (upper right corner)&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/3657/1*yA_e63XZHtUdH_Ojnkv97w.png"> &lt;/figure>&lt;/p>
&lt;p>As you can see, this works pretty fine. Please now add an additional message to your topic, summarizing the chat:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/726/1*brZKT0kNJnpQYWA4mMKqcQ.png"> &lt;/figure>&lt;/p>
&lt;p>After that, end the conversation with a survey if you like to — but it’s not required.&lt;/p>
&lt;p>Now add a new node (those cards!) between the last question and the summarizing message by clicking the + Icon:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/779/1*X1Y6h-KMa9A_MIdjj9Hl2A.png"> &lt;/figure>&lt;/p>
&lt;p>Click on CREATE A FLOW. We want to take action on the inputs that our users made in the chat. Luckily, Microsoft already made a nice template for us.&lt;/p>
&lt;h1 id="power-automate-first-flow">Power Automate, First Flow&lt;/h1>
&lt;p>A new tab in your browser opens with the POWER VIRTUAL AGENTS TEMPLATE:&lt;/p>
&lt;figure class="wp-block-image">
&lt;p>&lt;img src="https://miro.medium.com/max/4098/1*0rNwmKF0ZkSypnLVrkZM6Q.png"> &lt;/figure>&lt;/p>
&lt;p>We need to make some changes to the JSON objects and to the variables — but I will guide you through!&lt;/p>
&lt;ol>
&lt;li>In the Trigger WHEN A HTTP REQUEST IS RECEIVED we need to add all variables from the chatbot so we can work with that.&lt;/li>
&lt;/ol>
&lt;p>The variables for TeamName, Description, First and Second Owner need to be type string, the variables for Visibility and Guest Status need to be boolean.&lt;/p>
&lt;p>So here is the JSON:&lt;figure>&lt;/figure>&lt;/p>
&lt;ol start="2">
&lt;li>Now we need to edit the first INITIALIZE VARIABLE action and insert the right Value from the Dynamic Content:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/3657/1*Ncz2w_n3doaerVr0zHp0UA.png"> &lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>
&lt;p>As we have no numbers in our JSON code, we can just delete the next action: INITIALIZE VARIABLE — Number&lt;/p>
&lt;/li>
&lt;li>
&lt;p>We need to add for each variable (which represents a question that the chatbot asked) an INITIALIZE VARIABLE action. You can simplify this by just using the COPY TO MY CLIPBOARD feature:&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>(Click the … Icon on the action you want to cope, click COPY to my Clipboard, Click the + icon to insert another action, select MY CLIPBOARD,select the action from the clipboard).&lt;/p>
&lt;p>Please note, that the yes/no questions in the PVA should be reflected as type boolean in flow.&lt;/p>
&lt;ol start="5">
&lt;li>Please do yourself a favor and rename your actions.&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1403/1*b77cFpYf1jCmDh7YJQjHIQ.png"> &lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>
&lt;p>Seriously. Do this now.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>We need to edit the RESPOND TO PVA:&lt;figure class="wp-block-image">&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1404/1*F_GJSxt_GuCsejoJJ0SdfQ.png"> &lt;/figure>&lt;/p>
&lt;p>Please make sure you also click on SHOW ADVANCED options and insert the following JSON:&lt;/p>
&lt;div class="wp-block-group">
&lt;div class="wp-block-group__inner-container">
&lt;figure>&lt;/figure>
&lt;/div>
&lt;/div>
&lt;ol start="8">
&lt;li>To log all request, we use a SharePoint list. Create a new list and add columns which represent our variables plus columns for STATUS and COMMENTS&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/3107/1*WDsDfG3t4nDXyujbJvSDsA.png"> &lt;/figure>&lt;/p>
&lt;ol start="9">
&lt;li>Add a CREATE ITEM action to the flow — simply insert our variables into the fields.&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1400/1*mWuyluGUpw3mL1YrkOpN0A.png"> &lt;/figure>&lt;/p>
&lt;ol start="10">
&lt;li>
&lt;p>Save your changes and give your flow a better name.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Now we need to add the flow we just created in the PVA Autoring Canvas. To make it appear in the CALL AN ACTION list we need to&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h1 id="move-our-flow-to-the-solutions-tab-in-power-automate">move our flow to the SOLUTIONS tab in Power Automate:&lt;/h1>
&lt;ol>
&lt;li>Click SOLUTIONS, then NEW SOLUTION&lt;/li>
&lt;li>give your solution a name, select CDS DEFAULT PUBLISHER and click CREATE:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/762/1*eqy8zaVp8IMzCQp2bS0Xwg.png"> &lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>Now click on the name of your new solution and then on ADD EXISTING, then select FLOW&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*vxUdlTlNRDQpA2Z6OIyhmg.png"> &lt;/figure>&lt;/p>
&lt;ol start="4">
&lt;li>Select the flow from OUTSIDE SOLUTIONS and click ADD.&lt;/li>
&lt;/ol>
&lt;p>Special Thanks here to &lt;a href="https://www.poszytek.eu/" target="_blank" rel="noreferrer noopener">Tomasz Poszytek&lt;/a> who made a more detailed tutorial on that.&lt;/p>
&lt;h1 id="power-virtual-agents--part-2">Power Virtual Agents — part 2&lt;/h1>
&lt;ol>
&lt;li>Now go back to the Authoring Canvas of PVA , insert the call an action node between the last question and the final message. Your flow should now appear here:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1415/1*RQI4vn_DMjyO41aMoiboVA.png"> &lt;/figure>&lt;/p>
&lt;ol start="2">
&lt;li>Select your flow this will create another node:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/711/1*_P5m-dLBpqOq4FeHhv12Dw.png"> &lt;/figure>&lt;/p>
&lt;p>Again, fill out the fields.&lt;/p>
&lt;p>You can test your Bot/flow experience using the test bot, which will trigger the flow and create SP list items. Once everything works here, its time to continue with the second flow.&lt;/p>
&lt;h1 id="power-automate-second-flow">Power Automate, Second Flow&lt;/h1>
&lt;p>Purpose of this second flow is to provision the team after an approval, notifying owners and managers about what’s going on and updating our SP list. As there is no action “Create a Team” in Power Automate, we need to provision the team by an http request to Microsoft Graph. Challenge: To be able to authenticate, wen need to first&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2859/1*XRQnl9kK-qXMXyHgRuJnlA.png"> &lt;/figure>&lt;/p>
&lt;h2 id="register-an-application-in-azure-active-directory">register an application in Azure Active Directory&lt;/h2>
&lt;ol>
&lt;li>go to &lt;a href="https://portal.azure.com/" target="_blank" rel="noreferrer noopener">&lt;a href="https://portal.azure.com">https://portal.azure.com&lt;/a>&lt;/a>, click on Azure Active Directory, then App Registrations. It helps, if you are your own global admin or at least know her very well 🙂&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/870/1*GHpSWjBEm_dPVPeCJKmbgg.png"> &lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>Next task is to create a new application. Give it a name and notice, that you can already see&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>your app ID&lt;/li>
&lt;li>your tenant ID&lt;/li>
&lt;/ul>
&lt;p>We will use that later. First we need to setup some permissions and an App Secret.&lt;/p>
&lt;ol start="4">
&lt;li>Click on API PERMISSIONS, then ADD PERMISSIONS, then MICROSOFT GRAPH and select APPLICATION PERMISSION&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*twLaTrGCF8rpP1vXJIiNcA.png"> &lt;/figure>&lt;/p>
&lt;ol start="5">
&lt;li>Add permissions for Directory, User and Groups (ReadWriteAll)&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*769wCSaQrYgn8R9JSxf6LA.png"> &lt;/figure>&lt;/p>
&lt;p>and grand admin consent:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*E_ll0NXjfwmbzX1bmjfmFQ.png"> &lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>Now click on CERTIFICATES &amp;amp; SECRETS and add a Client Secret&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*syowLVC7gl4Gu-aB-1dNqg.png"> &lt;/figure>&lt;/p>
&lt;p>Copy the Client secret IMMEDIATELY! You don’t have any chance later to access this!&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2523/1*O2wqQbTL929p5ewxDMA83Q.png"> &lt;/figure>&lt;/p>
&lt;p>Congrats! Now that you registered the app in Azure AD, we go back to Power Automate:&lt;/p>
&lt;ol>
&lt;li>Create a new flow, whose trigger is the a new list item in SharePoint. Select our list we created before.&lt;/li>
&lt;li>initialize string-variables for Tenant ID, App ID, App Secret with the values we already have (Remember when registering the app in AAD?).&lt;/li>
&lt;/ol>
&lt;p>I even initialized one for the URL of Microsoft Graph because while testing I was too lazy to type 🙂 Also initialize another empty variable (string) for Group ID.&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1364/1*3pPJrogWtR8YRdA0TNBUSQ.png"> &lt;/figure>&lt;/p>
&lt;ol start="3">
&lt;li>We will need a variable for MailNickname as well — here we just use a little expression to get rid of spaces (we will need that later!):&lt;/li>
&lt;/ol>
&lt;p>replace(triggerBody()?[‘Title’],’ ’,’’)&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1341/1*NA0L0kZjxdjU-yKYTCD6lg.png"> &lt;/figure>&lt;/p>
&lt;ol start="4">
&lt;li>Now we need to get the Owner ID this will be our first HTTP request:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1368/1*qX3gJynaK9sAEspyCHSTAQ.png"> &lt;/figure>&lt;/p>
&lt;p>Please make sure to expand advanced options! These are mandatory because we need to authenticate. Put the variables into the fields as shown above.&lt;/p>
&lt;ol start="5">
&lt;li>To ensure a nice approval, we want to get the user’s manager from Active Directory. We use the OFFICE PROFILE — GET MANAGER action for that:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1374/1*zRM34tNbxoZNALfVqNmQaQ.png"> &lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>Now we create an approval, that shall post a fancy Adaptive Card to the manager&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1361/1*xhaxr_hScLgVMCac0LrcKQ.png"> &lt;/figure>&lt;/p>
&lt;p>Enable Notifications needs to be a NO, otherwise we just create emails and we want the adaptive card!&lt;/p>
&lt;ol start="6">
&lt;li>Now choose the TEAMS action POST YOUR OWN ADAPTIVE CARD AS THE FLOW BOT TO A USER&lt;/li>
&lt;/ol>
&lt;p>Use the manager’s email address for the recipient field and the ADAPTIVE CARD itself for the message.&lt;/p>
&lt;p>Now add an action called WAIT FOR AN APPROVAL and just insert the APPROVAL ID.&lt;/p>
&lt;p>Depending on the Outcome of the Approval (Approved or rejected) we want different actions to follow. Therefore we ad a CONDITION.&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1428/1*9M6-GlxbGjCgncHXlwmt1Q.png"> &lt;/figure>&lt;/p>
&lt;ol start="7">
&lt;li>We will now focus on the IF YES branch (so if the Team is approved. Our SharePoint list shall be updated with the Values for Approval Outcome and Approval Comments&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1319/1*WljA34OIeVi6g1LssJsYuw.png"> &lt;/figure>&lt;/p>
&lt;p>Once we added the Responses Comments, an APPLY TO EACH will appear. Don’t worry about that.&lt;/p>
&lt;ol start="8">
&lt;li>Now it’s time to create an Office 365 Group with an HTTP request. Add an action and make sure you authenticate properly in the advanced settings:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1347/1*LgOtCKWF8n8F8lEU3jxZdg.png"> &lt;/figure>&lt;/p>
&lt;ol start="9">
&lt;li>Now we need to update the Office 365 Group to a Team. We will need the Group ID for that. Remember? We already initialized a Group ID. We now just set the value. We do this with a simple Parse JSON action:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1364/1*1lciBhngkdVLIcswlbccTg.png"> &lt;/figure>&lt;/p>
&lt;ol start="10">
&lt;li>Use a PUT method in the next HTTP Request as the Group already exists and authenticate again in advanced settings.&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1352/1*6PXs5MtwLRYR_rxRjnl7-A.png"> &lt;/figure>&lt;/p>
&lt;ol start="11">
&lt;li>As the next step update the SP list:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1286/1*cSYSCv-bTzKnXOR1pebcWA.png"> &lt;/figure>&lt;/p>
&lt;ol start="12">
&lt;li>Now we want to send some notifications in Teams to the new proud owners and we can do this again by an adaptive card. To create one, simply go to &lt;a href="https://adaptivecards.io/designer" target="_blank" rel="noreferrer noopener">&lt;a href="https://adaptivecards.io/designer">https://adaptivecards.io/designer&lt;/a>&lt;/a> and use the Visual Designer to get valid JSON which you can use in the flow:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*JCc7E66TtM5IJDud9gvaOQ.png"> &lt;/figure>&lt;/p>
&lt;p>I explained this in more detail in my blog post about &lt;a target="_blank" rel="noreferrer noopener" href="https://regarding365.com/adaptive-cards-for-beginners-monitor-a-hashtag-on-twitter-1b9c1ea6e56f">Adaptive Cards for Beginners&lt;/a>. Simply copy the Code, insert it into a POST YOUR OWN ADAPTIVE CARD AS A BOT TO A USER and replace Placeholders with variables:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1368/1*zkWH-uZaUCYbqpVQbOnK2A.png"> &lt;/figure> &lt;figure class="wp-block-image">&lt;img src="https://miro.medium.com/max/1361/1*AzAP_8j02wBSG0YvPZJjMA.png">&lt;/figure>&lt;/p>
&lt;ol start="13">
&lt;li>Do this for both owners of the Team.&lt;/li>
&lt;/ol>
&lt;p>Now we want to focus on the IF NO branch of our flow (so when the Team was rejected)&lt;/p>
&lt;ol start="14">
&lt;li>Add a new action UPDATE ITEM in SharePoint:&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/2700/1*AUMbcdhmvO5n5fe6HuPLfg.png"> &lt;/figure> &lt;figure class="wp-block-image">&lt;img src="https://miro.medium.com/max/1347/1*dJjGgqZKdXqLEQPA1axDcg.png">&lt;/figure>&lt;/p>
&lt;ol start="15">
&lt;li>Add another Adaptive Card with a rejection text here.&lt;/li>
&lt;/ol>
&lt;p>Perhaps you want to add some other actions as well, like informing the manager or adding members to the Team. You can even think about provisioning the channels and content. For now, I will leave it by that.&lt;/p>
&lt;h2 id="publish-your-bot-and-add-it-as-an-app-to-teams">Publish your Bot and add it as an app to Teams&lt;/h2>
&lt;p>To get your bot working not only as the test bot, you need to publish it and follow the next steps:&lt;/p>
&lt;ol>
&lt;li>go back to PVA and click the PUBLISH Button&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/2700/1*AUMbcdhmvO5n5fe6HuPLfg.png"> &lt;/figure>&lt;/p>
&lt;ol start="2">
&lt;li>Now click on MANAGE CHANNELS and select MICROSOFT TEAMS&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/2768/1*nLSBnrtvjW5ou7pJkHVTSA.png"> &lt;/figure>&lt;/p>
&lt;p>3 Click on ADD ( lower right corner), after that copy the APP ID (we will need that later).&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2049/1*t6pQwkS4aVXnrs5HPnuoog.png"> &lt;/figure>&lt;/p>
&lt;p>You may close this window now.&lt;/p>
&lt;ol start="4">
&lt;li>
&lt;p>Open Microsoft Teams, open or install (if you haven’t already) TEAMS APP STUDIO&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Click the MANIFEST EDITOR and fill in some details. Paste the APP ID you copied before from the PVA into the field:&lt;figure class="wp-block-image">&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/4098/1*LSpVKglASXMAcs5eU4JNgg.png"> &lt;/figure> &lt;figure class="wp-block-image">&lt;img src="https://miro.medium.com/max/3729/1*rG7TbbhxydESl2q-fbog5w.png">&lt;/figure>&lt;/p>
&lt;p>You need to fill in at least SOME information for privacy statement and terms of use… and you may brand you app by giving it a nice icon&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2484/1*VAzqxF1-4sd1KMeFuSuKew.png"> &lt;/figure>&lt;/p>
&lt;ol start="6">
&lt;li>Now click on BOTS and then on EXISTING BOT (because we already have one!) Use again our App ID and select PERSONAL as scope&lt;figure class="wp-block-image">&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/2025/1*jfHztJs0Xv3q-9xK_3s7AA.png"> &lt;/figure>&lt;/p>
&lt;ol start="7">
&lt;li>Now click on DOMAINS AND PERMISSIONS&lt;/li>
&lt;/ol>
&lt;p>Add token.botframework.com as a valid domain (wait 2–5 seconds, it will show up like this:&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/3666/1*FQDTk9VSL12WOTcii1uZWw.png"> &lt;/figure>&lt;/p>
&lt;ol start="8">
&lt;li>
&lt;p>Click TEST AND DISTRIBUTE, then DOWNLOAD&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Now click on the App catalogue in Teams and Upload a custom app. Select your downloaded zip file from step 8&lt;figure class="wp-block-image">&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/2651/1*0TE7PVtp9q4Oaja-imFrbQ.png"> &lt;/figure>&lt;/p>
&lt;p>Click add — and CELEBRATE!&lt;/p>
&lt;p>Special thanks to &lt;a href="https://www.twitter.com/TomaszPoszytek" target="_blank" rel="noreferrer noopener">Tomasz Poszytek&lt;/a>, who already vlogged and blogged about how to add a Bot to Teams — learned so much from that!&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/4103/1*fzGsOBj1aPTlGaQRNP40fg.png"> &lt;/figure> &lt;figure class="wp-block-image">&lt;img src="https://miro.medium.com/max/3830/1*n1dM3r83c75j5-AuN9MHGg.png">&lt;/figure> &lt;figure class="wp-block-image">&lt;img src="https://miro.medium.com/max/3830/1*LCIaVWiKF_89ycgc4k0EWA.png">&lt;/figure>&lt;/p>
&lt;h1 id="business-impact">Business Impact&lt;/h1>
&lt;p>So after we successfully built this or understood how we could build this — why should we care about a ChatBot? And why should we implement a chatbot solution with Power Platform?&lt;/p>
&lt;h2 id="1-more-productive-internal-client-support">1. more productive internal client support&lt;/h2>
&lt;p>If internal clients can request not only Teams but also whatever they need in a chat experience, this will make our support more efficient, as we will free up human agent time for more complex issues.&lt;/p>
&lt;h2 id="2-fast-adoption">2. fast adoption&lt;/h2>
&lt;p>Built in a familiar experience like teams (or on a familiar website like an Intranet Site), adoption we be more easy. A simple and lean process will satisfy users needs, we can ensure that users will like to use the Chat Bot intsead of writing emails back and forth, filling out forms, which sometimes leads to confusion and frustration&lt;figure class="wp-block-image">&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/750/1*Yjvba_Lx44_CRvFxSmg0Wg.gif"> &lt;/figure>&lt;/p>
&lt;h2 id="3-easy-governance">3. easy governance&lt;/h2>
&lt;p>As we now built a solution in Office 365 / Azure AD / Power Platform, we can apply all policies and rules we like to have in place. Right now, this solution is a mvp. If we want to ensure, that for example guests shall already be added to the team after a second approval (or after signing an NDA) we can manage that as well with Power Platform.&lt;/p>
&lt;h2 id="4-human-touch-and-feel-of-an-interactive-interface">4. human touch and feel of an interactive interface&lt;/h2>
&lt;p>I think this is one of the most important aspects which will lead to my conclusion of the business impacts on solutions LIKE this. Although clients know, that they interact with a bot, it gives IT a more human face and we not only enhance the user experience but seriously unf*ck it. I know it’s sometimes hard to imagine from an IT point of view, but most IT projects and processes frustrate users before they give them any value. And this is not because all of our colleagues are just belonging to that group of dumbest assumeable users. So understanding what the business needs (for example: a lean process with a human touch to request what they need to get their work done) plus satisfying what IT needs (for example: a lean process which ensures that we stay in control but don’t need to do the same stupid job ever and ever again) seems to be the key to finally&lt;/p>
&lt;h2 id="5-narrow-the-historical-gap-between-business--it">5. narrow the historical gap between Business &amp;amp; IT&lt;/h2>
&lt;p>This is a long, long story, and I think, that this blogpost is already long enough. If you want to read more about that, please continue here:&lt;a target="_blank" rel="noreferrer noopener" href="https://regarding365.com/office-365-as-an-it-only-project-what-could-possibly-go-wrong-d26b77df7e35">Office 365 as an IT only Project — what could possibly go wrong?Note: I did a session with Michael Roth on based on this blog post at #Office365UGHH — please connect with and follow…regarding365.com&lt;/a>&lt;/p>
&lt;h1 id="feedback-thoughts">Feedback, Thoughts?&lt;/h1>
&lt;p>I would love ton hear what you think about this solution. Any feedback is welcome 🙂 If you liked it, please don’t forget to clap 🙂 If you didn’t like it, please tell me what you would improve.&lt;/p></description></item><item><title>2019 or How I started to believe in myself</title><link>https://m365princess.com/blogs/2019-12-28-2019-or-how-i-started-to-believe-in-myself/</link><pubDate>Sat, 28 Dec 2019 12:19:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-12-28-2019-or-how-i-started-to-believe-in-myself/</guid><description>&lt;h1 id="purpose-of-this-post">Purpose of this post&lt;/h1>
&lt;p>Someone on twitter asked, what was the one word to describe 2019. As I can’t name just one word, I will need to write an entire blog post to do that. Originally, I didn’t know if I will publish this recap, but I needed to clarify my thoughts to get a better vision of what next year will be about. With clarity comes freedom and peace in mind. Right now, there is a thunder inside of me and I can only calm myself by taking away anything that distracts me. This also means, that I need to write what holds me back, puts me down, shrinks me into places I have already grown out and blurs my vision. I don’t want to write a list of accomplishments to let others applaud me, but I think that sharing my experiences is a good way to beat my insecurities, overcome my impostor syndrome and thank people that helped me on my way.&lt;/p>
&lt;h1 id="last-year">Last year&lt;/h1>
&lt;p>In 2018, a big part of our community got to know me because of my sketchnotes at various conferences. Whenever someone introduced me, I was the “Sketchnote Girl”. Sure, I am very thankful about the visibility and my exposure within the community, but I felt the urge of showing that I am not only able to draw (in fact, I taught myself how to draw in 2018, but this is another story ), but that I am actually an Office 365 Consultant, who is passionate about learning &amp;amp; sharing in first place. I wanted others to benefit not only from my skills to visually recap what other’s session were about, but also from my own insights and learnings. I have so many ideas about the art of teamwork, how to really increase productivity and how to explain complex structures that really anybody is able to understand, so I wanted to open discussions and yes — also be recognized as an expert.&lt;/p>
&lt;h1 id="this-year">This year&lt;/h1>
&lt;p>Therefore I realized, that I needed to sharpen my profile and work on how others could see me, so that I wasn’t only remembered as the one that does those drawings at conferences but also knows her stuff as a Consultant and can share her technical expertise by speaking.&lt;/p>
&lt;h2 id="state-of-mind">State of mind&lt;/h2>
&lt;p>Although I am a non-technical Consultant focusing on User Adoption &amp;amp; Communication. I felt the need to challenge myself with new things like finding solutions for problems. As I have no technical background at all, I was afraid that I could fail, and that I wasn’t smart enough to achieve my goals. I suffer from impostor syndrome and had these voices inside, telling me that at some point everyone will notice that I don’t really provide value, or that everything I did was just a mistake. But although I was scared as hell, I set some goals and just continued learning and asked for help. I can’t list everyone who helped me, gave me hints, encouraged me whenever I needed support, but just let me say THANK YOU DEAR COMMUNITY. Thank you for including me after I excluded myself. Very special thanks to &lt;a href="https://twitter.com/donasarkar" target="_blank" rel="noreferrer noopener">Dona Sarkar&lt;/a>, for giving me more courage. My old beliefs, false assumptions and low self esteem when it comes to my technical skills held me back for so long time from actively making progress in my learning journey. It was just so easy to refuse instead of breaking my barriers and work for it. This year I finally broke with this mindset. How I managed that? I realized that there was a parallel between how I became a Sketchnote artist and everything I wanted to be in other areas. I realized that it was about me, how I perceive myself, and the world.&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/4200/1*QxBF7ORzrIzBIeANby8PnQ.jpeg" alt="">&lt;/p>
&lt;p>So obviously, I need to tell that story — didn’t plan that for this blog post, but here we go:&lt;/p>
&lt;h2 id="how-i-became-a-sketchnote-artist">How I became a Sketchnote artist&lt;/h2>
&lt;p>Discouraged at the age of 6 by my teacher in arts at school, who told me that I should just stop to try drawing because I really had “no talent at all”. I just didn’t draw for my whole life. I suffered from it for a long time, because I wanted to express myself also visually. As I am a visual learner and felt that there was creativity inside of me. These words of having “no talent at all” stuck to my mind. This held me back from trying. At some point, last spring,. I realized that I wasn’t that poor girl. It became very clear to me, that I’m the one who is responsible for her life and what she makes from it. The grass is greener where you water it.&lt;figure class="wp-block-image">&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/4536/1*U5JY8qnYonlY9pwyEyAcQg.jpeg" alt="">
&lt;figcaption>writing “The grass is greener where you water it ” by Neil Baringham, photo taken in Antwerp&lt;/figcaption>&lt;/figure>
&lt;p>Sure, the arts teacher was mean and should have encouraged me. He should have helped me instead of discouraging me. He should have been inclusive, but I played his game and excluded myself from being, thinking and acting visually. I used the “no talent at all” as an excuse, as a perfect reason, so that I didn’t need to just sit down and practice.&lt;/p>
&lt;p>When I understood, that I was in my own drivers’ seat and that I couldn’t allow someone else to determine if I was successful or not, I felt very scared, but then let that feeling go. This was more than relieving!&lt;/p>
&lt;p>First I started by setting up a little visual vocabulary of around 20 icons which I wanted to draw. I also worked on improving my handwriting.&lt;/p>
&lt;p>Listening is also a good skill of myself. This together with practicing drawing and writing, I was able to take my very first sketchnotes at Re:Publica 2018 in Berlin. Three weeks later I also did this at Collabsummit 18 in Mainz. The guy, which sat next to me, asked me to post my notes to Twitter and tag the speaker of the session — rest is history. Thank you again, &lt;a rel="noreferrer noopener" href="https://twitter.com/danholme" target="_blank">Dan Holme&lt;/a>.&lt;/p>
&lt;h2 id="learning-journey-in-2019">Learning Journey in 2019&lt;/h2>
&lt;p>&lt;strong>Power Automate / JSON /Microsoft Graph&lt;/strong>&lt;/p>
&lt;p>I started to learn to use Power Automate and wanted to add some complexity in my flows. This complexity triggered me to learn JSON, which helped me with using Adaptive Cards and connecting to Microsoft Graph. This was seriously a game changer for me. Already typing &lt;a href="http://developer.microsoft.com/" target="_blank" rel="noreferrer noopener">&lt;em>developer.microsoft.com&lt;/em>&lt;/a> as the URL in my browser was a bit scary for me.&lt;/p>
&lt;p>Thanks to &lt;a href="https://twitter.com/jthake" target="_blank" rel="noreferrer noopener">Jeremy&lt;/a>, who not only pointed me to an amazing blog post series &lt;a href="https://developer.microsoft.com/en-us/graph/blogs/announcing-30-days-of-microsoft-graph-blog-series/" target="_blank" rel="noreferrer noopener">#30daysMSGraph&lt;/a>, but also helped me understand how I as non-Developer could find my niche. I used the Graph in a lot of flows and found out that I seriously love its power. Registering my first Azure AD app got me very exciting. These days I show others how to use and do this and it feels amazing.&lt;/p>
&lt;p>Thank you &lt;a href="https://twitter.com/sharepointainia" target="_blank" rel="noreferrer noopener">Andre&lt;/a> for being my partner in crime for that at BASS. I binge-watched sessions that were far outside of my comfort zone. Each session, I documented in a learning diary. Whenever I struggle, I read in this diary to know what I asked know. Thank you as well &lt;a href="https://twitter.com/KeithWhatling" target="_blank" rel="noreferrer noopener">Keith&lt;/a> for pointing me to some resources and continuing to encourage me.&lt;/p>
&lt;p>&lt;strong>JavaScript&lt;/strong>&lt;/p>
&lt;p>Besides that, I took the next step. I started to &lt;a href="https://www.udemy.com/share/101WfeCUIacFpbQ3o=/" target="_blank" rel="noreferrer noopener">learn JavaScript&lt;/a>. Yes, I am &lt;a href="https://twitter.com/search?q=%23NotADev&amp;src=typed_query&amp;f=live" target="_blank" rel="noreferrer noopener">#NotADev&lt;/a>, but felt that it was a good idea to understand how to write code and how a developer thinks. More and more I get involved in intranet projects. Having these coding skills/mindset, it helps me see things from a different angle. This gives me an additional perspective on the work I do in order to have a more holistic approach how to provide value to my customers. Plus: I wanted to know if I can really learn anything. I shared a lot of this on Twitter. Some of the comments weren’t nice. Others really helped me finding my way — thanks also to my 300 new developer-followers who saw me struggle and offered help — I was deeply touched by that!&lt;figure>&lt;/figure>&lt;/p>
&lt;p>&lt;strong>Adaptive Cards&lt;/strong>&lt;/p>
&lt;p>Since I became more familiar with writing and understanding JSON, it became easier to use Adaptive Cards. I really like these Adaptive Cards and my customers find them fancy as hell.&lt;/p>
&lt;p>At #MSIgnite, I not only attended the session about Adaptive Cards, but also did a sketchnote from &lt;a href="https://twitter.com/MattHidinger" target="_blank" rel="noreferrer noopener">Matt Hidinger&lt;/a> s session. This sketchnote was published on the &lt;a href="https://developer.microsoft.com/en-us/sharepoint/blogs/adaptive-cards-community-call-november-14-2019/" target="_blank" rel="noreferrer noopener">official Adaptive Cards blog&lt;/a>. Thank you, Matt, for recognizing my work!&lt;/p>
&lt;h1 id="events">Events&lt;/h1>
&lt;p>I worked on blogposts about my learnings and shared my experiences openly on Twitter in order to not only be “the artist”. I realized that I had to start submitting sessions to conferences in 2019. This was a success and I spoke at the following events:&lt;/p>
&lt;h2 id="6-meetups">6 Meetups&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Modern Workplace Meetup Münster&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Thank you &lt;a rel="noreferrer noopener" href="https://www.twitter.com/nyn3x" target="_blank">Henning&lt;/a> for having me – I had a great time presenting my very ironic session: 5 ways how to avoid collaboration in your organization&lt;/p>
&lt;ul>
&lt;li>&lt;strong>BASS in Bremen&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>thank you, &lt;a rel="noreferrer noopener" href="https://twitter.com/sharepointainia" target="_blank">André&lt;/a>, for presenting together with me about how to provision a Microsoft Teams team with Power Automate! Was the first time I presented not alone – Thank you as well for our very professional preparation session in the ball pitt!&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/4632/1*zs8ZVOu_BIIxuBryVZH0Bw.jpeg" alt="">&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Office 365 Cologne&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>where I showcased how drawing helps me in consultancy by facilitating teamwork&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Office 365 UG Hamburg&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>where I talked with &lt;a href="https://twithttps//twitter.com/Gezeitenbrandter.com/Gezeitenbrand" target="_blank" rel="noreferrer noopener">Michael Roth&lt;/a> about what could possibly go wrong in an Office 365 project if you treat it like an IT only project. Read &lt;a target="_blank" rel="noreferrer noopener" href="https://regarding365.com/office-365-as-an-it-only-project-what-could-possibly-go-wrong-d26b77df7e35">our blog post&lt;/a> in case you missed it.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Wirtschaftsförderung Paderborn&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>again, thanks Michael who helped me to convince Cloud Newbies to consider Azure &amp;amp; Office 365 as part of their digital transformation journey&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MSCCCH Hannover&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>big thanks to &lt;a href="https://twitter.com/TKarafilov" target="_blank" rel="noreferrer noopener">Tomislav Karafilov&lt;/a> and &lt;a href="https://twitter.com/PapaRiedel" target="_blank" rel="noreferrer noopener">Stefan Riedel&lt;/a> who hosted not just me but also my 9 year old daughter Clara who became a Power Automate fan this day:&lt;figure>&lt;/figure>&lt;/p>
&lt;h2 id="7-sharepoint-saturdays">7 SharePoint Saturdays&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>SPS Bremen&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>My very first speaker slot at an SPS event — about sketchnoting and how this technique could make you a better consultant — Lucky that I had a great audience who were not only very keen on learning how to draw, but also gave me amazing feedback — thank you! Also thank you &lt;a href="https://twitter.com/mosslive" target="_blank" rel="noreferrer noopener">Daniel Wessels&lt;/a> for giving me this opportunity.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SPS Warsaw&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Bad session. Nothing worked. Wi-Fi, Demo, my slide deck, my poll, the game I wanted to play. Worst 50 minutes for me.&lt;/p>
&lt;p>Obviously, I didn’t sacrifice chickens to the demo gods, or I didn’t bath in blood at midnight. You need to learn from your failures. This session taught me I needed to have at least a plan B.&lt;/p>
&lt;p>Thank you to any of the speakers who hugged me, told that shit just happens, and that you just need to embrace the lessons life gives you.&lt;/p>
&lt;p>Thanks you &lt;a href="https://twitter.com/EdytaGorzon" target="_blank" rel="noreferrer noopener">Edyta Gorzon&lt;/a>, &lt;a href="https://twitter.com/TomaszPoszytek" target="_blank" rel="noreferrer noopener">Tomasz Poszytek&lt;/a> and &lt;a href="https://twitter.com/MarcinSiewnicki" target="_blank" rel="noreferrer noopener">Martin Siewnicki&lt;/a> for an awesome event!&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SPS London&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Taking my two chief fangirls &amp;amp; daughters (Marie, 14 and Clara, 9) with me to London, it was an absolute pleasure for me to deliver my very first session about Power Automate and what you can do with it if you think you lack of technical skills. This was a trojan horse session. It looked like a #Maker- session, but was in fact about empowerment.&lt;/p>
&lt;p>The session was packed and I really had a good time presenting.&lt;/p>
&lt;p>Thanks, &lt;a href="https://twitter.com/sebmatthews" target="_blank" rel="noreferrer noopener">Seb Matthews&lt;/a> for selecting me!&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SPS Cologne&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Cologne is somehow my local SPS Event, I seriously enjoyed being there and amazed my audience with some drawing skills — thanks to &lt;a href="https://twitter.com/Ra_Koellner" target="_blank" rel="noreferrer noopener">Raphael Köllner&lt;/a>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Power Saturday Paris&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Once again, sharing how to take visual notes was fun, need to come back to Paris soon — thanks to &lt;a href="https://twitter.com/thesqlgrrrl" target="_blank" rel="noreferrer noopener">Isabelle Van Campenhoudt&lt;/a> and &lt;a href="https://twitter.com/patricg" target="_blank" rel="noreferrer noopener">Patrick Guimonet&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SPS Brussels&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Funny story. Although I was originally not selected as a speaker at this event, I actually delivered a session about productivity — thanks to my daughter Clara, who not only took pictures of me, but was my fangirl for the whole day, and fell in love with Community.&lt;/p>
&lt;p>By the way: very proud of receiving such good speaker rates!&lt;/p>
&lt;p>Thank you &lt;a href="https://twitter.com/ThomasVochten" target="_blank" rel="noreferrer noopener">Thomas Vochten&lt;/a>, &lt;a href="https://twitter.com/RickVanRousselt" target="_blank" rel="noreferrer noopener">Rick van Rousselt&lt;/a> and &lt;a href="https://twitter.com/eliostruyf" target="_blank" rel="noreferrer noopener">Elio Struyf&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SPS Munich&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Right after #MSIgnite I was part of an expert’s panel discussion about the purpose and use cases of private Channels in Microsoft Teams together with &lt;a href="https://twitter.com/EdytaGorzon" target="_blank" rel="noreferrer noopener">Edyta Gorzon&lt;/a>, &lt;a href="https://twitter.com/Rob_The_Ninja" target="_blank" rel="noreferrer noopener">Robert Mulsow&lt;/a>, &lt;a href="https://twitter.com/mosslive" target="_blank" rel="noreferrer noopener">Daniel Wessels&lt;/a> and &lt;a href="https://twitter.com/OmarShahine" target="_blank" rel="noreferrer noopener">Omar Shahine&lt;/a>. I also delivered a session on my own about task management with Planner, To-Do, SharePoint and Power Automate — audience was very happy about how I connected libraries with tasks and how everyone can achieve zero inbox with ease.&lt;/p>
&lt;p>Thanks &lt;a href="https://twitter.com/AntjeLamartine" target="_blank" rel="noreferrer noopener">Antje Lamartine&lt;/a>, &lt;a href="https://twitter.com/corinna_lins" target="_blank" rel="noreferrer noopener">Corinna Lins&lt;/a> and &lt;a href="https://twitter.com/maxmelcher" target="_blank" rel="noreferrer noopener">Max Melcher&lt;/a> for organizing an awesome event!&lt;figure class="wp-block-image">
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1287/1*89449vXQFd1KI8RjAyfy_A.png" alt="">&lt;/p>
&lt;/figure>
&lt;h2 id="3-non-microsoft-organized-conferences">3 non-Microsoft-organized Conferences&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>aOS Aachen&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>as an aOS ambassador it was my pleasure to deliver a session about task management — I learned a lot about being a woman in tech; here is my advice to all fellow #WIT: don’t wear long earrings (they come in conflict with your headset and will make awful noise) and if you wear a dress, choose one with a belt so the sound engineer has a fair chance to attach the sender.&lt;/p>
&lt;p>Thank you &lt;a href="https://twitter.com/patricg" target="_blank" rel="noreferrer noopener">Patrick Guimonet&lt;/a> and &lt;a href="https://twitter.com/SaschaF80" target="_blank" rel="noreferrer noopener">Sascha Fredrich&lt;/a> for having me!&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Office 365 Connect Haarlem&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Delivered two sessions at the very last Office 365 Connect Conference which made me laugh and cry. I was happy to present in front of great audiences who were so open and interactive, asked so many questions and were very talkative. I cried because it would be the last time to have a conference at this awesome venue.&lt;/p>
&lt;p>Thank you &lt;a href="https://twitter.com/nigel_himself" target="_blank" rel="noreferrer noopener">Nigel Clapham&lt;/a>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Modern Workplace Conference Paris&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>For the first time, I didn’t just show how I thought everyone &lt;em>could&lt;/em> learn how to draw, but I showcased how somebody learned it in fact. This somebody was none other than &lt;a href="https://twitter.com/eliostruyf" target="_blank" rel="noreferrer noopener">Elio Struyf&lt;/a>. I want to say big thanks for trusting me in this process. Although for him as a developer, drawing is far outside of his comfort zone. He was very passionate in learning and practiced very focused.&lt;/p>
&lt;p>We presented on stage what needed to happen so anybody can learn to draw and how developers and other IT-people can benefit from this skill.&lt;/p>
&lt;p>Thank you &lt;a href="https://twitter.com/patricg" target="_blank" rel="noreferrer noopener">Patrick Guimonet&lt;/a> and &lt;a href="https://twitter.com/xGokan" target="_blank" rel="noreferrer noopener">Gokan Ozcifci&lt;/a> for having us!&lt;/p>
&lt;h2 id="3-microsoft-conferences">3 Microsoft Conferences&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>MVP Summit&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>My airline left my luggage in my hometown, so when I arrived at Summit, I had nothing to wear. Not like my cupboard is full of clothes I don’t want to wear but just NOTHING to wear, so the struggle was real. 20 Minutes after I posted this on Twitter, &lt;a href="https://twitter.com/8bitclassroom" target="_blank" rel="noreferrer noopener">Brian Dang&lt;/a> came to my hotel (even before I arrived from the airport) to bring Power Apps Shirts and other swag. Thank you, Brian!&lt;/p>
&lt;p>&lt;a href="https://twitter.com/mkashman" target="_blank" rel="noreferrer noopener">Mark Kashman&lt;/a> packed a whole SharePoint Bag for me with shirts and my dear roommate &lt;a href="https://twitter.com/IdaBergum" target="_blank" rel="noreferrer noopener">Ida Bergum&lt;/a> allowed me to just use her make-up and wear her clothes —&lt;/p>
&lt;p>Damn, all this gave me such a warm feeling of being welcome and that community got me covered.&lt;/p>
&lt;p>After shopping, I had the pleasure of delivering a 3-hour session at Pre-Day. During this session I showed the basics of drawing and how you could overcome those inner barriers which limit you.&lt;/p>
&lt;p>Thank you, &lt;a href="https://twitter.com/betsyweber" target="_blank" rel="noreferrer noopener">Betsy Weber&lt;/a>, for making this happen. Audience was deeply touched. It could have been I had something in my eyes as well.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Microsoft Business Application Summit&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Another step outside of my comfort zone was this event, that focused on Dynamics and Power Platform — I shared my learnings in a Citizen Developer Panel and would like to say a big thank you, &lt;a href="https://twitter.com/sameerbhangarhttps:/twitter.com/sameerbhangar" target="_blank" rel="noreferrer noopener">Sameer Banghar&lt;/a>. Showing what’s achievable for makers and how Microsoft is really fulfilling their mission statement gave me goosebumps.&lt;figure class="wp-block-image">
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2411/1*J6UwBepMOugEh4Dn7_edNw.png" alt="">&lt;/p>
&lt;figcaption>My “About me” slide of my Citizen Developer presentation&lt;/figcaption>&lt;/figure>
&lt;ul>
&lt;li>&lt;strong>Microsoft Ignite&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Biggest Conference of the year! Together with &lt;a href="https://twitter.com/LoryanStrant" target="_blank" rel="noreferrer noopener">Loryan Strant&lt;/a> I was part of an unconference session of &lt;a href="https://twitter.com/JoanneCKlein" target="_blank" rel="noreferrer noopener">Joanne Klein&lt;/a> about survival tips for the independent Consultant — we shared insights and learnings and were all impressed how openly our participants shared their thoughts. Thankful of being part of this experience.&lt;/p>
&lt;p>Another great thing happened — after I did a sketchnote on Satya Nadellas vision keynote, he replied to my tweet. This made my day!&lt;figure>&lt;/figure>&lt;/p>
&lt;p>Thank you to everyone who celebrated this with me &lt;a href="https://twitter.com/_achu" target="_blank" rel="noreferrer noopener">Anna Chu&lt;/a>, &lt;a href="https://twitter.com/heddanewman" target="_blank" rel="noreferrer noopener">Heather Newman&lt;/a>, &lt;a href="https://twitter.com/betsyweber" target="_blank" rel="noreferrer noopener">Betsy Weber&lt;/a>, &lt;a href="https://twitter.com/donasarkar" target="_blank" rel="noreferrer noopener">Dona Sarkar&lt;/a>, &lt;a href="https://twitter.com/resing" target="_blank" rel="noreferrer noopener">Tom Resing&lt;/a>, &lt;a href="https://twitter.com/shonadelie" target="_blank" rel="noreferrer noopener">Shona Bang&lt;/a>, &lt;a href="https://twitter.com/lauriepottmeyer" target="_blank" rel="noreferrer noopener">Laurie Pottmeyer&lt;/a>, &lt;a href="https://twitter.com/Karuana" target="_blank" rel="noreferrer noopener">Karuana Gatimu&lt;/a> and so many others!&lt;/p>
&lt;p>AS if all these event weren’t enough, I was also official Sketchnote artist at&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Collabsummit19 in Wiesbaden&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>I did not only come back “home” after a year, but also got the opportunity to be on stage. My sketchnotes of both keynotes were projected on huge screens so the 1600 people could see them in real time. Can’t say that I didn’t feel pressure, but I so enjoyed it. I also wrote an article for the &lt;a href="http://www.collaborationsummit.com/documents/Collabmagazine_2019.pdf" target="_blank" rel="noreferrer noopener">#Collabmagazine&lt;/a> and designed its front page — thank you &lt;a href="https://twitter.com/adisjugo" target="_blank" rel="noreferrer noopener">Adis Jugo&lt;/a> and &lt;a href="https://twitter.com/harbars" target="_blank" rel="noreferrer noopener">Spencer Harbar&lt;/a> for making that happen. Meant the world to me, #communityrocks&lt;figure>&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>SPC19 Las Vega&lt;/strong>s&lt;/li>
&lt;/ul>
&lt;p>Something special is going on when your family jokes about you going last-minute to Vegas — Thanks to &lt;a href="https://twitter.com/williambaer" target="_blank" rel="noreferrer noopener">Bill Baer&lt;/a>, &lt;a href="https://twitter.com/danholme" target="_blank" rel="noreferrer noopener">Dan Holme&lt;/a> and the &lt;a href="https://twitter.com/SPConf" target="_blank" rel="noreferrer noopener">whole team&lt;/a> for bringing me to SPC19 — I loved being there. Nice touch, that my sketchnote tweets were the most retweeted ones of the whole conference. Thanks as well to &lt;a href="https://twitter.com/thatmattwade" target="_blank" rel="noreferrer noopener">Matt Wade&lt;/a> who really challenged me with giving the most speedy talk I ever experienced.&lt;figure>&lt;/figure>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ESPC19 in Prague&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>After being the Sketchnote Artist last year, Tracy O’Connell invited me again to facilitate speakers and amplify content on social media. If I look at the awesome report of &lt;a rel="noreferrer noopener" href="https://twitter.com/diverdown1964" target="_blank">John White of TyGraph&lt;/a> I need to say: It worked out quite well&lt;/p>
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/4092/1*7HYQ5YeHO4DdkkflDXIoaQ.png" alt="">
&lt;h1 id="people">People&lt;/h1>
&lt;p>Nothing of this would be possible without the people that support me. So although I already thanked a lot of people in this post, I want to continue to do this.&lt;/p>
&lt;h2 id="family">Family&lt;/h2>
&lt;p>First of all, I want and need to thank my family. My two daughters Marie, 14 and Clara, 9 support me in an outstanding way and I can’t say how much I appreciate that. Having a mom that is so often far away from home and spends a lot time after work on community work can seriously be a challenge. But both girls ensure me quite often, that they see me as their role model and that they want me to see happy and independent and that I need to do what I want to do. Sometimes, I am able to bring the kids with me to events and I would love to see more kids around community events. We aren’t alone in this world and I think this would also be a step towards more diversity if we really included all of us.&lt;/p>
&lt;h2 id="regarding-365">REgarding 365&lt;/h2>
&lt;p>I’m part of the team of &lt;a href="https://twitter.com/regarding365" target="_blank" rel="noreferrer noopener">#Regarding365&lt;/a>, if you don’t know us by now, please make sure to visit our website. I’m so incredibly thankful to have friends around me that work with me on the same mission, and love the connectiveness of this group. Thank you &lt;a href="https://twitter.com/DarrellaaS" target="_blank" rel="noreferrer noopener">Darrell as a Service&lt;/a> for including me, thank you &lt;a href="https://twitter.com/Worrelpa" target="_blank" rel="noreferrer noopener">Phil Worrell&lt;/a> for amplifying my work, my learnings, my journey. Thanks to everyone in the team who helped me out when I struggled and gave me advice, so I didn’t feel awkward anymore. You all are great.&lt;/p>
&lt;h2 id="poweraddicts">#PowerAddicts&lt;/h2>
&lt;p>Another very special group of people my heart feels so close to are the &lt;a href="https://twitter.com/search?q=%23poweraddicts&amp;src=typed_query" target="_blank" rel="noreferrer noopener">#PowerAddicts&lt;/a>. It is an initiative of learning and sharing about Power Platform of a some awesome people: &lt;a href="https://twitter.com/aprildunnam" target="_blank" rel="noreferrer noopener">April Dunham&lt;/a>, &lt;a href="https://twitter.com/KeithWhatling" target="_blank" rel="noreferrer noopener">Keith Whatling&lt;/a>, &lt;a href="https://twitter.com/GSiVed" target="_blank" rel="noreferrer noopener">Geetha Sivasailam&lt;/a>, &lt;a href="https://twitter.com/Anton_Rob_Benz" target="_blank" rel="noreferrer noopener">Anton Benz&lt;/a>, &lt;a href="https://twitter.com/dchristian19" target="_blank" rel="noreferrer noopener">Daniel Christian&lt;/a> and &lt;a href="https://twitter.com/that_API_guy" target="_blank" rel="noreferrer noopener">Vivek Bavishi&lt;/a>, who gave me the nice opportunity of designing not only stickers but also &lt;a href="https://teespring.com/poweraddicts?utm_campaign=poweraddicts&amp;utm_medium=oembed&amp;utm_source=wordpress&amp;pid=373&amp;cid=100042" target="_blank" rel="noreferrer noopener">shirts&lt;/a> for all of us. This global movement of makers impresses me every single day in a way I can’t express enough. The inclusivity, diversity and kindness of this community is outstanding. Thank you as well to my girl &lt;a href="https://twitter.com/LTaylorEDU" target="_blank" rel="noreferrer noopener">Lauren&lt;/a>, who is my role model. Read about her here: &lt;a href="https://news.microsoft.com/features/you-dont-have-to-be-a-developer-to-turn-a-great-idea-into-an-app/" target="_blank" rel="noreferrer noopener">&lt;a href="https://news.microsoft.com/features/you-dont-have-to-be-a-developer-to-turn-a-great-idea-into-an-app/">https://news.microsoft.com/features/you-dont-have-to-be-a-developer-to-turn-a-great-idea-into-an-app/&lt;/a>&lt;/a>&lt;/p>
&lt;h2 id="my-crew">My Crew&lt;/h2>
&lt;p>Another big thank you goes to “My Crew” — very special friends &lt;a href="https://twitter.com/EdytaGorzon/" target="_blank" rel="noreferrer noopener">Edyta Gorzon&lt;/a>, &lt;a href="https://twitter.com/Rob_The_Ninja" target="_blank" rel="noreferrer noopener">Robert Mulsow&lt;/a>, &lt;a href="https://twitter.com/TomaszPoszytek" target="_blank" rel="noreferrer noopener">Tomasz Poszytek&lt;/a> and &lt;a href="https://twitter.com/eliostruyf" target="_blank" rel="noreferrer noopener">Elio Struyf&lt;/a> — thank you all for an awesome year, for your company and friendship. You all know what you mean to me.&lt;/p>
&lt;h2 id="community--special-thanks">Community &amp;amp; Special Thanks&lt;/h2>
&lt;p>As I already mentioned the experts I did sessions with, I wanted to also thank all supporters. Everyone who sat in front row making some noise or in the back seats applauding for my solutions — special thanks to &lt;a href="https://twitter.com/Laura_GB" target="_blank" rel="noreferrer noopener">Laura Graham Brown&lt;/a> and &lt;a href="https://twitter.com/cimares" target="_blank" rel="noreferrer noopener">Paul Hunt&lt;/a>, who were able to see something in me I didn’t believe in.&lt;/p>
&lt;p>Thank you &lt;a href="https://twitter.com/ExchangeGoddess" target="_blank" rel="noreferrer noopener">Phoummala Schmitt&lt;/a>, the Exchange Goddess for supporting me at MSBizAppSummit, &lt;a href="https://twitter.com/meetdux" target="_blank" rel="noreferrer noopener">Dux Raymond Sy&lt;/a> for always giving me thumbs up, &lt;a href="https://twitter.com/MiriRod" target="_blank" rel="noreferrer noopener">Miri Rodriguez&lt;/a>, we still owe each others a drink and &lt;a href="https://twitter.com/SimonWHChan" target="_blank" rel="noreferrer noopener">Simon Chan&lt;/a> for taking your time to work with me, I can’t express how much I love to be your Microsoft To-Do Ambassador. Very sure, there are lots of others, which do not mean less to me, but if I wanted to include all of you, I needed an entire new blog post for that. I am very sure we will meet again next year and celebrate our friendship.&lt;/p>
&lt;h1 id="personal">Personal&lt;/h1>
&lt;p>This whole post is quite personal, so perhaps you ask yourself how a paragraph called personal will be. I don’t believe in having a work — life balance. I live just one life and my work is a very important part of it but not everything my life is about. There are some intimate things that I want to share, although this seems to make me more vulnerable and weakens my position. But I can’t be my authentic self if I hide certain parts just for the sake of so-called professional behavior.&lt;/p>
&lt;h1 id="running">Running&lt;/h1>
&lt;p>Early September 2019, I began running because of medical reasons. I had pain in my neck, suffered from this well-known tech-neck. My doctor told me that I just need to do sports or would need surgery in less than two years. All my excuses like “I don’t have time”, “I travel too often”, or “I can’t stick to a fixed rhythm” didn’t count. My doctor proposed to start running because I could do this anywhere. Thank you &lt;a href="https://twitter.com/adraskovic" target="_blank" rel="noreferrer noopener">Aleksandar Draskovic&lt;/a> and &lt;a href="https://twitter.com/richriopel" target="_blank" rel="noreferrer noopener">Rich Riopel&lt;/a> for encouraging me as well — having role models made it easier for me.&lt;/p>
&lt;p>I downloaded an app, chose some songs and added them to a playlist. I found out, that Foo Fighters and Rage Against the Machine work best, #jsyk. I started with a program which was called “Couch to 5K” and it guided me through the process from not being able to run an all to easily running 5K. In the beginning it was seriously hard for me, to convince myself that I needed to do this. It took all my discipline and willpower to choose what I really wanted over what I wanted now. I ran 3 times a week, no matter what. I refused the urge of making excuses due to cold and rainy weather or telling myself that it would be ok to skip “just this time” because I was tired after long day at customer/conference and travels. I knew that if I didn’t stick to my new routine, it would be even harder to get back on track.&lt;/p>
&lt;p>After a few weeks I realized that I didn’t need to convince myself anymore. In fact I just did it, and finally also found joy in running. It was (and still is) not only an exercise for my body, but also for my mind. Being stronger than my weaker self, moving forward and making progress was something I needed in other parts of myself as well. 4 months after I started to run, it seriously became my habit. Nowadays, I pack my running gear every time I travel. Both body and mind ask for it and I can’t imagine that I will stop doing this.&lt;/p>
&lt;h1 id="what-the-heck-has-this-to-do-with-your-profession">What the heck has this to do with your profession?&lt;/h1>
&lt;p>I realized, that there was a pattern in my life:&lt;figure>&lt;/figure>&lt;/p>
&lt;p>I just assumed, that I am not talented /smart / strong / whatever ENOUGH to achieve something, start doing it anyway and then really rock it.&lt;/p>
&lt;p>&lt;strong>What about 2020?&lt;/strong>&lt;/p>
&lt;p>I just decided to stop that pattern. This year taught me to believe in myself. I achieved what seemed to out of reach for me. I surprised others and myself, I realized that making excuses doesn’t solve anything.&lt;/p>
&lt;p>Next year, there will be new challenges, new learnings, new struggles- But I am surrounded by you awesome community so I believe that I can make it.&lt;/p></description></item><item><title>privacy</title><link>https://m365princess.com/privacy/</link><pubDate>Wed, 18 Dec 2019 16:32:42 +0000</pubDate><guid>https://m365princess.com/privacy/</guid><description>&lt;h3 id="imprint">Imprint&lt;/h3>
&lt;p>Luise Freese | Uerdinger Straße 26 | 40474 Düsseldorf | Germany&lt;/p>
&lt;h3 id="privacy-policy">Privacy Policy&lt;/h3>
&lt;p>We are very delighted that you have shown interest in our enterprise. Data protection is of a particularly high priority for the management of the Luise Freese. The use of the Internet pages of the Luise Freese is possible without any indication of personal data; however, if a data subject wants to use special enterprise services via our website, processing of personal data could become necessary. If the processing of personal data is necessary and there is no statutory basis for such processing, we generally obtain consent from the data subject.&lt;/p>
&lt;p>The processing of personal data, such as the name, address, e-mail address, or telephone number of a data subject shall always be in line with the General Data Protection Regulation (GDPR), and in accordance with the country-specific data protection regulations applicable to the Luise Freese. By means of this data protection declaration, our enterprise would like to inform the general public of the nature, scope, and purpose of the personal data we collect, use and process. Furthermore, data subjects are informed, by means of this data protection declaration, of the rights to which they are entitled.&lt;/p>
&lt;p>As the controller, the Luise Freese has implemented numerous technical and organizational measures to ensure the most complete protection of personal data processed through this website. However, Internet-based data transmissions may in principle have security gaps, so absolute protection may not be guaranteed. For this reason, every data subject is free to transfer personal data to us via alternative means, e.g. by telephone.&lt;/p>
&lt;h4 id="1-definitions">1. Definitions&lt;/h4>
&lt;p>The data protection declaration of the Luise Freese is based on the terms used by the European legislator for the adoption of the General Data Protection Regulation (GDPR). Our data protection declaration should be legible and understandable for the general public, as well as our customers and business partners. To ensure this, we would like to first explain the terminology used.&lt;/p>
&lt;p>In this data protection declaration, we use, inter alia, the following terms:&lt;/p>
&lt;ul>
&lt;li>a)    Personal data Personal data means any information relating to an identified or identifiable natural person (“data subject”). An identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person.&lt;/li>
&lt;li>b) Data subject Data subject is any identified or identifiable natural person, whose personal data is processed by the controller responsible for the processing.&lt;/li>
&lt;li>c)    Processing Processing is any operation or set of operations which is performed on personal data or on sets of personal data, whether or not by automated means, such as collection, recording, organisation, structuring, storage, adaptation or alteration, retrieval, consultation, use, disclosure by transmission, dissemination or otherwise making available, alignment or combination, restriction, erasure or destruction.&lt;/li>
&lt;li>d)    Restriction of processing Restriction of processing is the marking of stored personal data with the aim of limiting their processing in the future.&lt;/li>
&lt;li>e)    Profiling Profiling means any form of automated processing of personal data consisting of the use of personal data to evaluate certain personal aspects relating to a natural person, in particular to analyse or predict aspects concerning that natural person’s performance at work, economic situation, health, personal preferences, interests, reliability, behaviour, location or movements.&lt;/li>
&lt;li>f)     Pseudonymisation Pseudonymisation is the processing of personal data in such a manner that the personal data can no longer be attributed to a specific data subject without the use of additional information, provided that such additional information is kept separately and is subject to technical and organisational measures to ensure that the personal data are not attributed to an identified or identifiable natural person.&lt;/li>
&lt;li>g)    Controller or controller responsible for the processing Controller or controller responsible for the processing is the natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of personal data; where the purposes and means of such processing are determined by Union or Member State law, the controller or the specific criteria for its nomination may be provided for by Union or Member State law.&lt;/li>
&lt;li>h)    Processor Processor is a natural or legal person, public authority, agency or other body which processes personal data on behalf of the controller.&lt;/li>
&lt;li>i)      Recipient Recipient is a natural or legal person, public authority, agency or another body, to which the personal data are disclosed, whether a third party or not. However, public authorities which may receive personal data in the framework of a particular inquiry in accordance with Union or Member State law shall not be regarded as recipients; the processing of those data by those public authorities shall be in compliance with the applicable data protection rules according to the purposes of the processing.&lt;/li>
&lt;li>j)      Third party Third party is a natural or legal person, public authority, agency or body other than the data subject, controller, processor and persons who, under the direct authority of the controller or processor, are authorised to process personal data.&lt;/li>
&lt;li>k)    Consent Consent of the data subject is any freely given, specific, informed and unambiguous indication of the data subject’s wishes by which he or she, by a statement or by a clear affirmative action, signifies agreement to the processing of personal data relating to him or her.&lt;/li>
&lt;/ul>
&lt;h4 id="2-name-and-address-of-the-controller">2. Name and Address of the controller&lt;/h4>
&lt;p>Controller for the purposes of the General Data Protection Regulation (GDPR), other data protection laws applicable in Member states of the European Union and other provisions related to data protection is:&lt;/p>
&lt;p>Luise Freese&lt;/p>
&lt;p>Uerdinger Str. 26&lt;/p>
&lt;p>40474 Duesseldorf&lt;/p>
&lt;p>DE&lt;/p>
&lt;p>Phone: 00491715508708&lt;/p>
&lt;p>Email: &lt;a href="mailto:luise@raeuberleiterin.de">luise@raeuberleiterin.de&lt;/a>&lt;/p>
&lt;p>Website: &lt;a href="https://www.m365princess.com">www.m365princess.com&lt;/a>&lt;/p>
&lt;h4 id="3-cookies">3. Cookies&lt;/h4>
&lt;p>The Internet pages of the Luise Freese use cookies. Cookies are text files that are stored in a computer system via an Internet browser.&lt;/p>
&lt;p>Many Internet sites and servers use cookies. Many cookies contain a so-called cookie ID. A cookie ID is a unique identifier of the cookie. It consists of a character string through which Internet pages and servers can be assigned to the specific Internet browser in which the cookie was stored. This allows visited Internet sites and servers to differentiate the individual browser of the dats subject from other Internet browsers that contain other cookies. A specific Internet browser can be recognized and identified using the unique cookie ID.&lt;/p>
&lt;p>Through the use of cookies, the Luise Freese can provide the users of this website with more user-friendly services that would not be possible without the cookie setting.&lt;/p>
&lt;p>By means of a cookie, the information and offers on our website can be optimized with the user in mind. Cookies allow us, as previously mentioned, to recognize our website users. The purpose of this recognition is to make it easier for users to utilize our website. The website user that uses cookies, e.g. does not have to enter access data each time the website is accessed, because this is taken over by the website, and the cookie is thus stored on the user’s computer system. Another example is the cookie of a shopping cart in an online shop. The online store remembers the articles that a customer has placed in the virtual shopping cart via a cookie.&lt;/p>
&lt;p>The data subject may, at any time, prevent the setting of cookies through our website by means of a corresponding setting of the Internet browser used, and may thus permanently deny the setting of cookies. Furthermore, already set cookies may be deleted at any time via an Internet browser or other software programs. This is possible in all popular Internet browsers. If the data subject deactivates the setting of cookies in the Internet browser used, not all functions of our website may be entirely usable.&lt;/p>
&lt;h4 id="4-collection-of-general-data-and-information">4. Collection of general data and information&lt;/h4>
&lt;p>The website of the Luise Freese collects a series of general data and information when a data subject or automated system calls up the website. This general data and information are stored in the server log files. Collected may be (1) the browser types and versions used, (2) the operating system used by the accessing system, (3) the website from which an accessing system reaches our website (so-called referrers), (4) the sub-websites, (5) the date and time of access to the Internet site, (6) an Internet protocol address (IP address), (7) the Internet service provider of the accessing system, and (8) any other similar data and information that may be used in the event of attacks on our information technology systems.&lt;/p>
&lt;p>When using these general data and information, the Luise Freese does not draw any conclusions about the data subject. Rather, this information is needed to (1) deliver the content of our website correctly, (2) optimize the content of our website as well as its advertisement, (3) ensure the long-term viability of our information technology systems and website technology, and (4) provide law enforcement authorities with the information necessary for criminal prosecution in case of a cyber-attack. Therefore, the Luise Freese analyzes anonymously collected data and information statistically, with the aim of increasing the data protection and data security of our enterprise, and to ensure an optimal level of protection for the personal data we process. The anonymous data of the server log files are stored separately from all personal data provided by a data subject.&lt;/p>
&lt;h4 id="5-contact-possibility-via-the-website">5. Contact possibility via the website&lt;/h4>
&lt;p>The website of the Luise Freese contains information that enables a quick electronic contact to our enterprise, as well as direct communication with us, which also includes a general address of the so-called electronic mail (e-mail address). If a data subject contacts the controller by e-mail or via a contact form, the personal data transmitted by the data subject are automatically stored. Such personal data transmitted on a voluntary basis by a data subject to the data controller are stored for the purpose of processing or contacting the data subject. There is no transfer of this personal data to third parties.&lt;/p>
&lt;h4 id="6-comments-function-in-the-blog-on-the-website">6. Comments function in the blog on the website&lt;/h4>
&lt;p>The Luise Freese offers users the possibility to leave individual comments on individual blog contributions on a blog, which is on the website of the controller. A blog is a web-based, publicly-accessible portal, through which one or more people called bloggers or web-bloggers may post articles or write down thoughts in so-called blogposts. Blogposts may usually be commented by third parties.&lt;/p>
&lt;p>If a data subject leaves a comment on the blog published on this website, the comments made by the data subject are also stored and published, as well as information on the date of the commentary and on the user’s (pseudonym) chosen by the data subject. In addition, the IP address assigned by the Internet service provider (ISP) to the data subject is also logged. This storage of the IP address takes place for security reasons, and in case the data subject violates the rights of third parties, or posts illegal content through a given comment. The storage of these personal data is, therefore, in the own interest of the data controller, so that he can exculpate in the event of an infringement. This collected personal data will not be passed to third parties, unless such a transfer is required by law or serves the aim of the defense of the data controller.&lt;/p>
&lt;h4 id="7-routine-erasure-and-blocking-of-personal-data">7. Routine erasure and blocking of personal data&lt;/h4>
&lt;p>The data controller shall process and store the personal data of the data subject only for the period necessary to achieve the purpose of storage, or as far as this is granted by the European legislator or other legislators in laws or regulations to which the controller is subject to.&lt;/p>
&lt;p>If the storage purpose is not applicable, or if a storage period prescribed by the European legislator or another competent legislator expires, the personal data are routinely blocked or erased in accordance with legal requirements.&lt;/p>
&lt;h4 id="8-rights-of-the-data-subject">8. Rights of the data subject&lt;/h4>
&lt;ul>
&lt;li>a) Right of confirmation Each data subject shall have the right granted by the European legislator to obtain from the controller the confirmation as to whether or not personal data concerning him or her are being processed. If a data subject wishes to avail himself of this right of confirmation, he or she may, at any time, contact any employee of the controller.&lt;/li>
&lt;li>b) Right of access Each data subject shall have the right granted by the European legislator to obtain from the controller free information about his or her personal data stored at any time and a copy of this information. Furthermore, the European directives and regulations grant the data subject access to the following information:
&lt;ul>
&lt;li>the purposes of the processing;&lt;/li>
&lt;li>the categories of personal data concerned;&lt;/li>
&lt;li>the recipients or categories of recipients to whom the personal data have been or will be disclosed, in particular recipients in third countries or international organisations;&lt;/li>
&lt;li>where possible, the envisaged period for which the personal data will be stored, or, if not possible, the criteria used to determine that period;&lt;/li>
&lt;li>the existence of the right to request from the controller rectification or erasure of personal data, or restriction of processing of personal data concerning the data subject, or to object to such processing;&lt;/li>
&lt;li>the existence of the right to lodge a complaint with a supervisory authority;&lt;/li>
&lt;li>where the personal data are not collected from the data subject, any available information as to their source;&lt;/li>
&lt;li>the existence of automated decision-making, including profiling, referred to in Article 22(1) and (4) of the GDPR and, at least in those cases, meaningful information about the logic involved, as well as the significance and envisaged consequences of such processing for the data subject. Furthermore, the data subject shall have a right to obtain information as to whether personal data are transferred to a third country or to an international organisation. Where this is the case, the data subject shall have the right to be informed of the appropriate safeguards relating to the transfer. If a data subject wishes to avail himself of this right of access, he or she may, at any time, contact any employee of the controller.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>c) Right to rectification Each data subject shall have the right granted by the European legislator to obtain from the controller without undue delay the rectification of inaccurate personal data concerning him or her. Taking into account the purposes of the processing, the data subject shall have the right to have incomplete personal data completed, including by means of providing a supplementary statement. If a data subject wishes to exercise this right to rectification, he or she may, at any time, contact any employee of the controller.&lt;/li>
&lt;li>d) Right to erasure (Right to be forgotten) Each data subject shall have the right granted by the European legislator to obtain from the controller the erasure of personal data concerning him or her without undue delay, and the controller shall have the obligation to erase personal data without undue delay where one of the following grounds applies, as long as the processing is not necessary:
&lt;ul>
&lt;li>The personal data are no longer necessary in relation to the purposes for which they were collected or otherwise processed.&lt;/li>
&lt;li>The data subject withdraws consent to which the processing is based according to point (a) of Article 6(1) of the GDPR, or point (a) of Article 9(2) of the GDPR, and where there is no other legal ground for the processing.&lt;/li>
&lt;li>The data subject objects to the processing pursuant to Article 21(1) of the GDPR and there are no overriding legitimate grounds for the processing, or the data subject objects to the processing pursuant to Article 21(2) of the GDPR.&lt;/li>
&lt;li>The personal data have been unlawfully processed.&lt;/li>
&lt;li>The personal data must be erased for compliance with a legal obligation in Union or Member State law to which the controller is subject.&lt;/li>
&lt;li>The personal data have been collected in relation to the offer of information society services referred to in Article 8(1) of the GDPR. If one of the aforementioned reasons applies, and a data subject wishes to request the erasure of personal data stored by the Luise Freese, he or she may, at any time, contact any employee of the controller. An employee of Luise Freese shall promptly ensure that the erasure request is complied with immediately. Where the controller has made personal data public and is obliged pursuant to Article 17(1) to erase the personal data, the controller, taking account of available technology and the cost of implementation, shall take reasonable steps, including technical measures, to inform other controllers processing the personal data that the data subject has requested erasure by such controllers of any links to, or copy or replication of, those personal data, as far as processing is not required. An employees of the Luise Freese will arrange the necessary measures in individual cases.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>e) Right of restriction of processing Each data subject shall have the right granted by the European legislator to obtain from the controller restriction of processing where one of the following applies:
&lt;ul>
&lt;li>The accuracy of the personal data is contested by the data subject, for a period enabling the controller to verify the accuracy of the personal data.&lt;/li>
&lt;li>The processing is unlawful and the data subject opposes the erasure of the personal data and requests instead the restriction of their use instead.&lt;/li>
&lt;li>The controller no longer needs the personal data for the purposes of the processing, but they are required by the data subject for the establishment, exercise or defence of legal claims.&lt;/li>
&lt;li>The data subject has objected to processing pursuant to Article 21(1) of the GDPR pending the verification whether the legitimate grounds of the controller override those of the data subject. If one of the aforementioned conditions is met, and a data subject wishes to request the restriction of the processing of personal data stored by the Luise Freese, he or she may at any time contact any employee of the controller. The employee of the Luise Freese will arrange the restriction of the processing.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>f) Right to data portability Each data subject shall have the right granted by the European legislator, to receive the personal data concerning him or her, which was provided to a controller, in a structured, commonly used and machine-readable format. He or she shall have the right to transmit those data to another controller without hindrance from the controller to which the personal data have been provided, as long as the processing is based on consent pursuant to point (a) of Article 6(1) of the GDPR or point (a) of Article 9(2) of the GDPR, or on a contract pursuant to point (b) of Article 6(1) of the GDPR, and the processing is carried out by automated means, as long as the processing is not necessary for the performance of a task carried out in the public interest or in the exercise of official authority vested in the controller. Furthermore, in exercising his or her right to data portability pursuant to Article 20(1) of the GDPR, the data subject shall have the right to have personal data transmitted directly from one controller to another, where technically feasible and when doing so does not adversely affect the rights and freedoms of others. In order to assert the right to data portability, the data subject may at any time contact any employee of the Luise Freese.&lt;/li>
&lt;li>g) Right to object Each data subject shall have the right granted by the European legislator to object, on grounds relating to his or her particular situation, at any time, to processing of personal data concerning him or her, which is based on point (e) or (f) of Article 6(1) of the GDPR. This also applies to profiling based on these provisions. The Luise Freese shall no longer process the personal data in the event of the objection, unless we can demonstrate compelling legitimate grounds for the processing which override the interests, rights and freedoms of the data subject, or for the establishment, exercise or defence of legal claims. If the Luise Freese processes personal data for direct marketing purposes, the data subject shall have the right to object at any time to processing of personal data concerning him or her for such marketing. This applies to profiling to the extent that it is related to such direct marketing. If the data subject objects to the Luise Freese to the processing for direct marketing purposes, the Luise Freese will no longer process the personal data for these purposes. In addition, the data subject has the right, on grounds relating to his or her particular situation, to object to processing of personal data concerning him or her by the Luise Freese for scientific or historical research purposes, or for statistical purposes pursuant to Article 89(1) of the GDPR, unless the processing is necessary for the performance of a task carried out for reasons of public interest. In order to exercise the right to object, the data subject may contact any employee of the Luise Freese. In addition, the data subject is free in the context of the use of information society services, and notwithstanding Directive 2002/58/EC, to use his or her right to object by automated means using technical specifications.&lt;/li>
&lt;li>h) Automated individual decision-making, including profiling Each data subject shall have the right granted by the European legislator not to be subject to a decision based solely on automated processing, including profiling, which produces legal effects concerning him or her, or similarly significantly affects him or her, as long as the decision (1) is not is necessary for entering into, or the performance of, a contract between the data subject and a data controller, or (2) is not authorised by Union or Member State law to which the controller is subject and which also lays down suitable measures to safeguard the data subject’s rights and freedoms and legitimate interests, or (3) is not based on the data subject’s explicit consent. If the decision (1) is necessary for entering into, or the performance of, a contract between the data subject and a data controller, or (2) it is based on the data subject’s explicit consent, the Luise Freese shall implement suitable measures to safeguard the data subject’s rights and freedoms and legitimate interests, at least the right to obtain human intervention on the part of the controller, to express his or her point of view and contest the decision. If the data subject wishes to exercise the rights concerning automated individual decision-making, he or she may, at any time, contact any employee of the Luise Freese.&lt;/li>
&lt;li>i) Right to withdraw data protection consent Each data subject shall have the right granted by the European legislator to withdraw his or her consent to processing of his or her personal data at any time. If the data subject wishes to exercise the right to withdraw the consent, he or she may, at any time, contact any employee of the Luise Freese.&lt;/li>
&lt;/ul>
&lt;h4 id="9-data-protection-provisions-about-the-application-and-use-of-instagram">9. Data protection provisions about the application and use of Instagram&lt;/h4>
&lt;p>On this website, the controller has integrated components of the service Instagram. Instagram is a service that may be qualified as an audiovisual platform, which allows users to share photos and videos, as well as disseminate such data in other social networks.&lt;/p>
&lt;p>The operating company of the services offered by Instagram is Facebook Ireland Ltd., 4 Grand Canal Square, Grand Canal Harbour, Dublin 2 Ireland.&lt;/p>
&lt;p>With each call-up to one of the individual pages of this Internet site, which is operated by the controller and on which an Instagram component (Insta button) was integrated, the Internet browser on the information technology system of the data subject is automatically prompted to the download of a display of the corresponding Instagram component of Instagram. During the course of this technical procedure, Instagram becomes aware of what specific sub-page of our website was visited by the data subject.&lt;/p>
&lt;p>If the data subject is logged in at the same time on Instagram, Instagram detects with every call-up to our website by the data subject—and for the entire duration of their stay on our Internet site—which specific sub-page of our Internet page was visited by the data subject. This information is collected through the Instagram component and is associated with the respective Instagram account of the data subject. If the data subject clicks on one of the Instagram buttons integrated on our website, then Instagram matches this information with the personal Instagram user account of the data subject and stores the personal data.&lt;/p>
&lt;p>Instagram receives information via the Instagram component that the data subject has visited our website provided that the data subject is logged in at Instagram at the time of the call to our website. This occurs regardless of whether the person clicks on the Instagram button or not. If such a transmission of information to Instagram is not desirable for the data subject, then he or she can prevent this by logging off from their Instagram account before a call-up to our website is made.&lt;/p>
&lt;p>Further information and the applicable data protection provisions of Instagram may be retrieved under &lt;a href="https://help.instagram.com/155833707900388">https://help.instagram.com/155833707900388&lt;/a> and &lt;a href="https://www.instagram.com/about/legal/privacy/">https://www.instagram.com/about/legal/privacy/&lt;/a>.&lt;/p>
&lt;h4 id="10-data-protection-provisions-about-the-application-and-use-of-twitter">10. Data protection provisions about the application and use of Twitter&lt;/h4>
&lt;p>On this website, the controller has integrated components of Twitter. Twitter is a multilingual, publicly-accessible microblogging service on which users may publish and spread so-called ‘tweets,’ e.g. short messages, which are limited to 280 characters. These short messages are available for everyone, including those who are not logged on to Twitter. The tweets are also displayed to so-called followers of the respective user. Followers are other Twitter users who follow a user’s tweets. Furthermore, Twitter allows you to address a wide audience via hashtags, links or retweets.&lt;/p>
&lt;p>The operating company of Twitter is Twitter International Company, One Cumberland Place, Fenian Street Dublin 2, D02 AX07, Ireland.&lt;/p>
&lt;p>With each call-up to one of the individual pages of this Internet site, which is operated by the controller and on which a Twitter component (Twitter button) was integrated, the Internet browser on the information technology system of the data subject is automatically prompted to download a display of the corresponding Twitter component of Twitter. Further information about the Twitter buttons is available under &lt;a href="https://about.twitter.com/de/resources/buttons">https://about.twitter.com/de/resources/buttons&lt;/a>. During the course of this technical procedure, Twitter gains knowledge of what specific sub-page of our website was visited by the data subject. The purpose of the integration of the Twitter component is a retransmission of the contents of this website to allow our users to introduce this web page to the digital world and increase our visitor numbers.&lt;/p>
&lt;p>If the data subject is logged in at the same time on Twitter, Twitter detects with every call-up to our website by the data subject and for the entire duration of their stay on our Internet site which specific sub-page of our Internet page was visited by the data subject. This information is collected through the Twitter component and associated with the respective Twitter account of the data subject. If the data subject clicks on one of the Twitter buttons integrated on our website, then Twitter assigns this information to the personal Twitter user account of the data subject and stores the personal data.&lt;/p>
&lt;p>Twitter receives information via the Twitter component that the data subject has visited our website, provided that the data subject is logged in on Twitter at the time of the call-up to our website. This occurs regardless of whether the person clicks on the Twitter component or not. If such a transmission of information to Twitter is not desirable for the data subject, then he or she may prevent this by logging off from their Twitter account before a call-up to our website is made.&lt;/p>
&lt;p>The applicable data protection provisions of Twitter may be accessed under &lt;a href="https://twitter.com/privacy?lang=en">https://twitter.com/privacy?lang=en&lt;/a>.&lt;/p>
&lt;h4 id="11-legal-basis-for-the-processing">11. Legal basis for the processing&lt;/h4>
&lt;p>Art. 6(1) lit. a GDPR serves as the legal basis for processing operations for which we obtain consent for a specific processing purpose. If the processing of personal data is necessary for the performance of a contract to which the data subject is party, as is the case, for example, when processing operations are necessary for the supply of goods or to provide any other service, the processing is based on Article 6(1) lit. b GDPR. The same applies to such processing operations which are necessary for carrying out pre-contractual measures, for example in the case of inquiries concerning our products or services. Is our company subject to a legal obligation by which processing of personal data is required, such as for the fulfillment of tax obligations, the processing is based on Art. 6(1) lit. c GDPR. In rare cases, the processing of personal data may be necessary to protect the vital interests of the data subject or of another natural person. This would be the case, for example, if a visitor were injured in our company and his name, age, health insurance data or other vital information would have to be passed on to a doctor, hospital or other third party. Then the processing would be based on Art. 6(1) lit. d GDPR. Finally, processing operations could be based on Article 6(1) lit. f GDPR. This legal basis is used for processing operations which are not covered by any of the abovementioned legal grounds, if processing is necessary for the purposes of the legitimate interests pursued by our company or by a third party, except where such interests are overridden by the interests or fundamental rights and freedoms of the data subject which require protection of personal data. Such processing operations are particularly permissible because they have been specifically mentioned by the European legislator. He considered that a legitimate interest could be assumed if the data subject is a client of the controller (Recital 47 Sentence 2 GDPR).&lt;/p>
&lt;h4 id="12-the-legitimate-interests-pursued-by-the-controller-or-by-a-third-party">12. The legitimate interests pursued by the controller or by a third party&lt;/h4>
&lt;p>Where the processing of personal data is based on Article 6(1) lit. f GDPR our legitimate interest is to carry out our business in favor of the well-being of all our employees and the shareholders.&lt;/p>
&lt;h4 id="13-period-for-which-the-personal-data-will-be-stored">13. Period for which the personal data will be stored&lt;/h4>
&lt;p>The criteria used to determine the period of storage of personal data is the respective statutory retention period. After expiration of that period, the corresponding data is routinely deleted, as long as it is no longer necessary for the fulfillment of the contract or the initiation of a contract.&lt;/p>
&lt;h4 id="14-provision-of-personal-data-as-statutory-or-contractual-requirement-requirement-necessary-to-enter-into-a-contract-obligation-of-the-data-subject-to-provide-the-personal-data-possible-consequences-of-failure-to-provide-such-data">14. Provision of personal data as statutory or contractual requirement; Requirement necessary to enter into a contract; Obligation of the data subject to provide the personal data; possible consequences of failure to provide such data&lt;/h4>
&lt;p>We clarify that the provision of personal data is partly required by law (e.g. tax regulations) or can also result from contractual provisions (e.g. information on the contractual partner). Sometimes it may be necessary to conclude a contract that the data subject provides us with personal data, which must subsequently be processed by us. The data subject is, for example, obliged to provide us with personal data when our company signs a contract with him or her. The non-provision of the personal data would have the consequence that the contract with the data subject could not be concluded. Before personal data is provided by the data subject, the data subject must contact any employee. The employee clarifies to the data subject whether the provision of the personal data is required by law or contract or is necessary for the conclusion of the contract, whether there is an obligation to provide the personal data and the consequences of non-provision of the personal data.&lt;/p>
&lt;h4 id="15-existence-of-automated-decision-making">15. Existence of automated decision-making&lt;/h4>
&lt;p>As a responsible company, we do not use automatic decision-making or profiling.&lt;/p>
&lt;p>This Privacy Policy has been generated by the Privacy Policy Generator of the &lt;a href="https://dg-datenschutz.de/services/external-data-protection-officer/?lang=en">DGD – Your External DPO&lt;/a> that was developed in cooperation with &lt;a href="https://www.wbs-law.de/eng/">German Lawyers&lt;/a> from WILDE BEUGER SOLMECKE, Cologne.&lt;/p></description></item><item><title>Consultancy &amp; Development</title><link>https://m365princess.com/consultancy/</link><pubDate>Wed, 18 Dec 2019 15:32:15 +0000</pubDate><guid>https://m365princess.com/consultancy/</guid><description>&lt;p>I help my customers around the globe to improve their business processes and to get rid of everything that only keeps them busy. To achieve that, I build amazing solutions on top of Microsoft 365 with Power Platform and Azure. And of course - AI 🤓&lt;/p>
&lt;p>Accessibility, intuitive design and usability as well as performance and adhering to security standards are not cherries on the cake, but my main guidelines in app development. I really have a thing on proper documentation and taking care of a streamlined deployment process.&lt;/p></description></item><item><title>Speaking Gigs</title><link>https://m365princess.com/speaker/</link><pubDate>Wed, 18 Dec 2019 15:32:15 +0000</pubDate><guid>https://m365princess.com/speaker/</guid><description>&lt;p>I love speaking at conferences and sharing my knowledge with community! You can find my sessions at &lt;a href="https://sessionize.com/luise-freese/">sessionize&lt;/a>&lt;/p>
&lt;h2 id="upcoming-events">Upcoming events&lt;/h2>
&lt;h3 id="codemotion-october-22-23-2024---milan-italy">Codemotion, October 22-23, 2024 - Milan, Italy&lt;/h3>
&lt;ul>
&lt;li>I&amp;rsquo;ll remember that (and other lies) - Why documentation makes your apps better and you a better dev&lt;/li>
&lt;/ul>
&lt;h3 id="directions-emea-november-6-8-2024---vienna-austria">Directions EMEA, November 6-8, 2024 - Vienna, Austria&lt;/h3>
&lt;ul>
&lt;li>Don&amp;rsquo;t Get Knocked Out by Licensing Costs: Calculating the Value of a Power Platform Solution&lt;/li>
&lt;li>Leveraging Copilot Plugins for Business Process Automation: An Advanced Guide (with &lt;a href="https://www.linkedin.com/in/mary-myers-631a0a103/">Mary Myers&lt;/a>)&lt;/li>
&lt;/ul>
&lt;h3 id="azure-meetup-duesseldorf-november-12-2024---duesseldorf-germany">Azure Meetup Duesseldorf, November 12, 2024 - Duesseldorf, Germany&lt;/h3>
&lt;ul>
&lt;li>How to get from DevOops to DevOps - Learn how to deploy your Power Platform projects with Azure DevOps.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://www.meetup.com/azure-meetup-dusseldorf/events/303136109/">Want to join?&lt;/a>&lt;/p>
&lt;h3 id="modern-work-meetup-muenster-november-14-2024---muenster-germany">Modern Work Meetup Muenster, November 14, 2024 - Muenster, Germany&lt;/h3>
&lt;ul>
&lt;li>to be announced soon&lt;/li>
&lt;/ul>
&lt;h3 id="wpc-2024-november-26-28-2024---milan-italy">WPC 2024, November 26-28, 2024 - Milan, Italy&lt;/h3>
&lt;ul>
&lt;li>to be announced soon&lt;/li>
&lt;/ul>
&lt;h3 id="espc-2024-december-2-5-2-24---stockholm-sweden">ESPC 2024, December 2-5 2-24 - Stockholm, Sweden&lt;/h3>
&lt;ul>
&lt;li>Workshop: &lt;a href="https://www.sharepointeurope.com/events/get-started-with-power-platform-on-sharepoint/">Get started with Power Platform on SharePoint&lt;/a> - together with &lt;a href="https://www.linkedin.com/in/%F0%9F%AA%84-robin-rosengr%C3%BCn-ab35091b6/">Robin Rosengruen&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.sharepointeurope.com/events/case-study-ai-at-work-a-real-life-playbook/">[CASE STUDY] AI at Work: A Real-Life Playbook&lt;/a> - together with &lt;a href="https://www.linkedin.com/in/nicki-borell/">Nicki Borell&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Adaptive Cards for Beginners- Monitor a Hashtag on Twitter</title><link>https://m365princess.com/blogs/2019-08-08-adaptive-cards-for-beginners-monitor-a-hashtag-on-twitter/</link><pubDate>Thu, 08 Aug 2019 12:15:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-08-08-adaptive-cards-for-beginners-monitor-a-hashtag-on-twitter/</guid><description>&lt;p>I wanted to work’n’play with Adaptive Cards, which are the future of notifications and I wanted to experiment with the Flow Bot in Microsoft Teams so I tried to figure out how I could monitor a hashtag and post an actionable card into a Channel or a chat into Teams.&lt;/p>
&lt;p>For my purposes, I want to monitor the hashtag &lt;a href="https://twitter.com/hashtag/LuiseINeedYourHelp?src=hashtag_click" target="_blank" rel="noreferrer noopener">#LuiseINeedYourHelp&lt;/a> on twitter, because I set up an Out-Of-Office Notification in my Outlook:&lt;/p>
&lt;blockquote class="wp-block-quote">
&lt;p>
I’m traveling between conferences to learn and grow and deliver you, my precious customers, most up-to-date value. Can’t catch up on email till xx.xx. Fastest way to reach me is is on Twitter- by the way: I monitor &lt;a href="https://twitter.com/hashtag/LuiseINeedYourHelp?src=hashtag_click" target="_blank" rel="noreferrer noopener">#LuiseINeedYourHelp&lt;/a> 
&lt;/p>
&lt;/blockquote>
&lt;h1 id="the-twitter-trigger">The Twitter Trigger&lt;/h1>
&lt;p>So I started on &lt;a href="https://flow.microsoft.com/" target="_blank" rel="noreferrer noopener">&lt;a href="https://flow.microsoft.com">https://flow.microsoft.com&lt;/a>&lt;/a> with a new Flow from blank. My trigger is: When a new tweet is posted:&lt;/p>
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/1575/1*UMJkRguYVwlEhZ-jJwm_cQ.png" alt="">
&lt;h1 id="the-adaptive-card-action">The Adaptive Card Action&lt;/h1>
&lt;p>My only action is:&lt;/p>
&lt;p>Post your own adaptive Card as the Flow Bot to a user:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1616/1*i-8m1ZWT2y0XsmV6tpQEYg.png" alt="">
&lt;p>I wrote my own email-address in the recipients field. After that, I opened &lt;a href="https://adaptivecards.io/designer/" target="_blank" rel="noreferrer noopener">&lt;a href="https://adaptivecards.io/designer/">https://adaptivecards.io/designer/&lt;/a>&lt;/a> and chose Microsoft Teams dark as host app:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/4074/1*kIwx6RlRZrvJAvKHHU8g2g.png" alt="">
&lt;p>Now I removed a elements from the card I don’t wanted to have:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1895/1*FUklbY6VLYZaObY9nPiEcw.png" alt="">
&lt;p>and replaced some text:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3713/1*vel80rpcorl4TWokGW2yQQ.png" alt="">
&lt;h1 id="the-json-code">The JSON Code&lt;/h1>
&lt;p>After that, I copied the JSON code and pasted it into my flow-action. Now I began to replace some text of the JSON with dynamic content of my twitter-trigger. Please note that JSON 1.2 is supported.&lt;/p>
&lt;p> &lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/831/1*HsBp-gUY63vtJCUSqKHEfA.png" alt="">&lt;/p>
&lt;p>and&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/863/1*a3ng5VigHvoaYM6Pa7rNzw.png" alt="">
&lt;p>and&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1062/1*wfnObTeP9JdlRxKECGzGRA.png" alt="">
&lt;h1 id="result">Result&lt;/h1>
&lt;p>Whenever somebody posts #LuiseINeedYourHelp on twitter, I get a private message on MicrosoftTeams in which I can view and open that tweet directly. Works on desktop, web and mobile (Because: If it doesn’t work on mobile, i doesn’t work)&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1553/1*CZ1zWvpaVQFOj96aM6phdA.png" alt="">
&lt;p>Happy Vacay to me!&lt;/p>
&lt;h1 id="feedback-questions">Feedback? Questions?&lt;/h1>
&lt;p>Do you like adaptive Cards? Want to learn more? Please clap if you like more of these posts. If you are more advanced, I’d love to point you to &lt;a href="https://www.poszytek.eu/" target="_blank" rel="noreferrer noopener">Tomasz Poszytek&lt;/a>.&lt;/p></description></item><item><title>Office 365 as an IT-only project – what could possibly go wrong?!</title><link>https://m365princess.com/blogs/2019-07-30-office-365-as-an-it-only-project-what-could-possibly-go-wrong/</link><pubDate>Tue, 30 Jul 2019 12:12:35 +0000</pubDate><guid>https://m365princess.com/blogs/2019-07-30-office-365-as-an-it-only-project-what-could-possibly-go-wrong/</guid><description>&lt;p>Note: I did a session with &lt;a href="https://medium.com/@gezeitenbrand" target="_blank" rel="noreferrer noopener">Michael Roth&lt;/a> on based on this blog post at #Office365UGHH — please connect with and follow him as well.&lt;/p>
&lt;p>If you want to run your Office 365 project like an usual IT project we just wanted to send you a non misunderstandable message:&lt;/p>
&lt;blockquote class="wp-block-quote">
&lt;p>
&lt;strong>ARE YOU NUTS?&lt;/strong>
&lt;/p>
&lt;/blockquote>
&lt;p>We know, we know, you ran a lot of projects, new Office Versions and operating systems, you migrated data and perhaps not everything went as planned, but work is what happens to you, while you are busy making other plans, right?&lt;/p>
&lt;p>So what could possibly go wrong if you just tried to continue how you treat your projects? As we have SOME experiences with projects, we collected some bad causes for you.&lt;/p>
&lt;h1 id="conflict-of-objectives">CONFLICT OF OBJECTIVES&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*4WfFUkwfBLq2BEhKomXkcA.jpeg" alt="">
&lt;p>If we treated our Office365 project like an IT only project we for sure create some conflicts of objectives between IT department and business departments.&lt;/p>
&lt;ul>
&lt;li>IT department one the one hand needs to ensure a stable, smooth operation. All key imgs (KPIs) and processes are geared to this and favor an attitude and habits that permit as few errors as possible by not experimenting and not experiencing something new. We are sure you are super familiar to that quote “ Never change a running system. “&lt;/li>
&lt;li>Business departments on the other hand do not only need a running system but also freedom and room to ideate, innovate and create new business opportunities and even change the way they do business. Without this change, business was stuck in operations but desperately needs ability to do some strategic work as well. This is only possible, if trying out, making errors, not running smooth is an option, too.&lt;/li>
&lt;/ul>
&lt;p>→This conflict leads to false assumptions in IT what business could need and we don’t take business needs into account.&lt;/p>
&lt;h1 id="conflict-of-scope">CONFLICT OF SCOPE&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*VfDDvQuOu8uLsXIbchzTwQ.jpeg" alt="">
&lt;p>These wrong assumptions lead us to the next point. If we in IT just think about rolling out a new version of Office, users will perceive Office 365 as a new version like Office 2010 was newer than 2003. For sure, we got a new nice UI (and still some employees struggle with using it or asked IT to install add/ins to make Office 2010 look like Office 2003). So we got a few new features and we didn’t need to change our ways to work.&lt;/p>
&lt;p>With Office 365, we need to transform business to drive digital transformation, and we need to make sure that we don’t just rebuild all those dysfunctional structures and unhealthy working habits in the Cloud. We can’t win the game if we don’t empower business departments and IT department itself to really change their way to run the company.&lt;/p>
&lt;h1 id="scratching-the-surface">SCRATCHING THE SURFACE&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*3--k3XDjkWSNmC8FTrkeVw.jpeg" alt="">
If we focus on new features, we are pretty sure that you can make a huge list of great features and perhaps you feel like a true communication mastermind by getting your Corporate Communications department on board and they write an article about the 10 hottest features in the new Office?
&lt;p>#SorryNotSorry, this again is not what is called “user centricity”. We need to take structures and processes into account, because if we only take care about some superficial new features, we don’t dig to the root of our working issues. Scratching on the surface can’t solve complex problems.&lt;/p>
&lt;h1 id="training-concept">TRAINING CONCEPT&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*3--k3XDjkWSNmC8FTrkeVw.jpeg" alt="">
Just scratching the surface when it comes to new possibilities results in a training concept that doesn’t fit to the DNA of Office 365: An evergreen Software As a Service doesn’t need a one-time in person training, but a diversified approach. First, users need to understand what is the purpose of Office 365 and which problems we can solve with it and which solutions we can just support with some nicer apps. We heard it all before: Never throw tech solutions on people problems 🙂
&lt;p>After a basic understanding they need to be empowered to evolve their structures and processes and ways of working and they need to be inspired to get a curious self-learning approach.&lt;/p>
&lt;h1 id="no-embrace-change-mindset">NO EMBRACE-CHANGE-MINDSET&lt;/h1>
&lt;img loading="lazy" class="w-100" src=" https://miro.medium.com/max/1920/1*4utHhumvDTl0Z8HgTHihrw.jpeg" alt="">
As if this wasn’t enough, we do not inspire employees to think out of the box and we don’t educate them to be part of solutions. Critical thinking, which is one of the most important skills, needs to be fostered and tends to degenerate.
&lt;h1 id="deepening-that-chasm-">DEEPENING THAT CHASM /&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*aeo4YIZCL1h-SEL2gb2pCA.jpeg" alt="">
By not learning and taking care about the needs of business departments, we deepen that chasm that has been growing and growing between IT and the other departments for decades.
&lt;p>You know, when admins don’t even care about being just a little bit discrete when rolling their eyes and making jokes about so-called dumbest assumable users — and business users hate the IT guys just because they seem to just say NO to any request.&lt;/p>
&lt;p>So IT becomes and stays the „Guardian of the Status Quo”, while the business departments fight against IT.&lt;/p>
&lt;p>IT — like any other service department — should enable the value chain in the company which is only possible when understanding and streamlining business processes.&lt;/p>
&lt;h1 id="no-digital-transformation">NO DIGITAL TRANSFORMATION&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*fxCJHjp7TzF7Wcl-hhJgmA.jpeg" alt="">
Can we really drive digital transformation without a proper mindset, the right usage of the right (secure) tools and mature processes? Not at all! Information siloes, old processes in new tools and employees that wait and suffer instead of changing themselves are the enemies of change. Is Office 365 the answer to all our problems? NO! It’s just one building block but a really great opportunity to rethink about hierarchy, information architecture, structure and processes.
&lt;h1 id="no-leadership--no-rolemodels--lack-of-adoption">NO LEADERSHIP — NO ROLEMODELS — LACK OF ADOPTION&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*BJ-CRDvxcEoNfoqdR0ciKw.jpeg" alt="">
Proper adoption needs role models and — best case: commitment by C-Level. If we don’t care about sponsorship (please don’t confuse this with “having budget”) and executive support, employees don’t see whom they should follow. Leading transformation by example is crucial is we make work and relationships even more visible wit Office 365.
&lt;h1 id="same-old-thinking--same-old-results">SAME OLD THINKING — SAME OLD RESULTS&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/1*JgYcRbVdC4sZ0C1nF2oChA.jpeg" alt="">
The way we thought about a user’s work (dealing with mail, nested folders, unable to retrieve information in a reasonable time, being under stress because assuming to need to reply to anything within minutes but still rather remain busy with being busy than providing value to the company by making customers happy) made them behave like they do. They behaved to survive in the structures we built for them. To fix that issue, we do not need to “fix the user” but to fix that system. If a plant doesn’t grow, you won’t pull it towards the right direction but take care about circumstances and proper environment like more light, less water etc. Same applies to humans.
&lt;h1 id="shadow-it">SHADOW IT&lt;/h1>
&lt;p>If our project is not aligned to business challenges and business goals, if we don’t care about the right learning approach and adoption, employees will choose other tools, that fit their needs. This makes Shadow IT solutions even more likely — which is a security issue as well. Instead of providing, managing and consulting a secure solution, IT is the last one who knows about cloud-services that get used.&lt;/p>
&lt;h1 id="wait--what-shall-we-do-instead">WAIT — WHAT SHALL WE DO INSTEAD?&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1190/1*-QfBubjUjBeIxUQUxdyn2w.png" alt="">
Evolve a holistic view on your organization: It‘s not IT vs. Business, but a
#bettertogether scenario
&lt;p>Adapt processes and structures so digital transformation may happen.&lt;/p>
&lt;p>Fix the system — not the user&lt;/p>
&lt;h1 id="call-for-feedback">CALL FOR FEEDBACK&lt;/h1>
&lt;p>Please clap if you like this post, follow me and &lt;a href="https://medium.com/@gezeitenbrand" target="_blank" rel="noreferrer noopener">&lt;a href="https://www.medium.com/@Gezeitenbrand">https://www.medium.com/@Gezeitenbrand&lt;/a>&lt;/a>. What are your thoughts? We are super keen on listening to your feedback and do another session on that crucial topic as well.&lt;/p></description></item><item><title>Manage your contacts during events with Microsoft Flow</title><link>https://m365princess.com/blogs/2019-06-25-manage-your-contacts-during-events-with-microsoft-flow/</link><pubDate>Tue, 25 Jun 2019 12:06:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-06-25-manage-your-contacts-during-events-with-microsoft-flow/</guid><description>&lt;p>As I travel really often to speak at or just attend conferences, I wanted a smooth workflow to capture contact data of the persons I meet during an event and make sure that I catch up with them later.&lt;/p>
&lt;p>Huge thanks to &lt;a href="https://twitter.com/JonJLevesque">Jon Levesque&lt;/a>, who demonstrated the idea of having a Microsoft Flow that sends a preconfigured email to a person after he handed out his phone to them at an event. I wanted to take that to the next level and was also inspired by &lt;a href="https://twitter.com/tracyvds">Tracy Van der Schyff&lt;/a> who had the amazing idea of providing a QR code on her phone, that others could scan.&lt;/p>
&lt;h1 id="my-wishlist-looked-like-this">My wishlist looked like this:&lt;/h1>
&lt;ol>
&lt;li>Create a form to ask the other person about name and email address and provide a QR code, so that this its easy to scan 2. store all the data of all conferences in a just one nice SharePoint list 3. create a new Outlook contact 4. send the email with all my contact details 5. setup a new task in my planner and 6. display it in a bucket which has the same name like the conference&lt;/li>
&lt;/ol>
&lt;h1 id="ok-lets-go">Ok, lets go&lt;/h1>
&lt;h2 id="preparations">Preparations&lt;/h2>
&lt;ol>
&lt;li>I created a Microsoft Forms form and asked for name, organization, email address and what topic the other one wants to talk about with me.&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1436/1*MWY-1QlPVdSYPSl0W1EoqQ.png" alt="">
&lt;ol start="2">
&lt;li>I created a SharePoint list with the following columns:&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3105/1*nDSPDhR-FB3fP9jQQAgvKQ.png" alt="">
&lt;ol start="3">
&lt;li>I created a new folder in my Outlook Contacts&lt;/li>
&lt;/ol>
&lt;h2 id="setup-the-flow">Setup the Flow&lt;/h2>
&lt;p>Now I wanted to put everything together in a nice flow. Here is the step-by step How-To:&lt;/p>
&lt;h2 id="trigger">Trigger&lt;/h2>
&lt;p>My trigger is a new response to the mentioned form:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1080/1*toKKqxsr39j9hcXWRWQCOg.png" alt="">
&lt;h2 id="actions">Actions&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Forms — get response details&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>my first action needs to be the forms action, because we need to make sure, that the flow reads the response details. If you just rebuild this flow while you are reading this, please note, that flow adds an APPLY TO ALL — don’t worry about that:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2370/1*dz6A9dKn-wrVE1urGP8UDg.png" alt="">
&lt;p>&lt;strong>2. Outlook — get events (v3)&lt;/strong>&lt;/p>
&lt;p>second action shall be the get events action of Outlook, because I want to filter the event that takes place at this moment while somebody submitts this form. The filter means: Please only get those events with start timestamp is less or equal that now and with end timestamp is greater or equal than now.&lt;/p>
&lt;p>We write this like this in the filter field:&lt;/p>
&lt;p>Start le [utcnow] and End ge [utcnow]&lt;/p>
&lt;p>le = less or equal&lt;/p>
&lt;p>ge = greater or equal&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1034/1*tWX8NlcAg1t8BlglPt0BoQ.png" alt="">
&lt;p>to get this [utcnow] please click on EXPRESSIONS on the right hand side:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/659/1*IWnIC-sj6omKHiGCDLooeQ.png" alt="">
&lt;p>&lt;strong>3. Create item in SharePoint list&lt;/strong>&lt;/p>
&lt;p>Now we want to write the info of the form and the event into a our prepared SharePoint list:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1026/1*7Ex5zF6UKkR6trG-z7Ld3g.png" alt="">
&lt;p>Please note, tat you get anoter APLY TO ALL — don’t worry again :-)&lt;/p>
&lt;p>Just fill the responses from the form and the subject of our Outlook Event (its the conference name!) in the blanks.&lt;/p>
&lt;p>&lt;strong>4. Create a contact in your Outlook&lt;/strong>&lt;/p>
&lt;p>For sure we want a new contact in our Outlook — thats super easy as well — please just note: you need to fill any number in the phone field — can be just a zero — once you get the number you can just add it as well — I don’t ask my super new conference contacts to give me their phone number, but thats up to you.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1049/1*GFBv3veQ9SWGPbpD3XSU3Q.png" alt="">
&lt;p>If you don’t like this contact to be stored in Outlook, but are a huge fan of doing this in Dynamics 365, please feel free to do this instead as well.&lt;/p>
&lt;p>&lt;strong>5. Send an Email&lt;/strong>&lt;/p>
&lt;p>As my fifth action I want to send my new contact an email with a few links to my content (website, blog, social media etc. ) — please challenge your own creativity :-)&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1526/1*c9x_wEHb9nep8PW2J5o0XQ.png" alt="">
&lt;p>I just added bit of my Dynamic Content and mentioned the Conference and the name of my contact to personalize this mail. You can even add some Emojis — Win + . is your best friend&lt;/p>
&lt;p>Please note&lt;/p>
&lt;ol>
&lt;li>the importance is by default: LOW — if you want normal importance, just use this little drop-down arrow. 2. if you add some super basic HTML like I did, please make sure that you answered this IS HTML? question with yes.&lt;/li>
&lt;/ol>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1523/1*d3jPWg7vYokfGvTdU_b8rQ.png" alt="">
&lt;p>&lt;strong>6. List Planner Buckets&lt;/strong>&lt;/p>
&lt;p>As I want not only a new task in my Planner to catch up with each person I met, but also a bucket for each new conference, I need to execute several actions to do that. First of course is to list all existing buckets in that planner board:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1511/1*jNBEI6pJugDmqnOD40DxKw.png" alt="">
&lt;p>Now I need to figure out, if a bucket with the name of the conference (Subject of our Event from Action №3) already exists. If it already exists, I need a new task in this bucket. If it doesn’t exist at this moment, this bucket shall be created; after that a new task shall be added to this bucket.&lt;/p>
&lt;p>&lt;strong>7. Filter array&lt;/strong>&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1530/1*LhdqN907iGGBe0cMrxmtRg.png" alt="">
&lt;p>I used the filter array and compared the name of the bucket with the Subject of the Outlook event. After that, I used a trick to compare the length of it to 0. If it is greater than zero, there NEEDS to be something, which means that I just need a new task in this bucket.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/969/1*EzVvCiurueWGJBtiuZ0mTQ.png" alt="">
&lt;p>Sounds super easy, but was a bit of a challenge, because when I wanted to create a new bucket with the “create a new task”-action, I wasn’t able to access the information of the events subject anymore in my Dynamic Content due to an automated APPLY TO ALL — this time, I WORRIED about it. I needed to find another way.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1674/1*Jbc9N3dyUdF75MGcrtPteA.png" alt="">
&lt;p>In this YES branch, I needed to parse Json code, and I learned an amazing trick by &lt;a href="https://twitter.com/BardyYves">Pierre-Yves Bardy&lt;/a>:&lt;/p>
&lt;p>I needed to know the code for “list buckets”, so I used another (!!!) flow, to trigger that action:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1574/1*Rnh2WEPB0pC3eAkRNdcuJA.png" alt="">
&lt;p>I ran a test (which was successful) and just copied the Output to my clipboard / CTRL C&lt;/p>
&lt;p>&lt;strong>8. Parse Json&lt;/strong>&lt;/p>
&lt;p>After that, I just jumped to my complex flow again and added this parse json action and clicked on the magic link: Use sample payload to generate a schema. A new Window opened itself and I just hit CTRL + V to paste the content and then clicked DONE:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1523/1*0-5VKM3HUEu8AwLjS5Wp_w.png">&lt;/p>
&lt;p>To me, this looks super pro :-) Thanks again! #communityrocks&lt;/p>
&lt;p>&lt;strong>9. Create a task in this bucket&lt;/strong>&lt;/p>
&lt;p>As I wanted a new task, I got an APPLY TO EACH (but this time, i wasn’t worrying at all ✔):&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1596/1*z_9SAX-SkvrL66Y2VJDwDA.png">&lt;/p>
&lt;p>Bucket ID was available in my Dynamic Content from Action No 8 and I used the name of the new contact to personalize my task and a simple adddays expression dor the Due date.&lt;/p>
&lt;p>Its written like this in the EXPRESSION field on your right hands side:&lt;/p>
&lt;p>AddDays(UtcNow(),10,’YYYY-mm-DD’)&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/983/1*2SGeO2odcgwaKyqMvvUkUw.png">&lt;/p>
&lt;p>It just means: Due Date is 10 days after today. You can put any other value instead of 10 here.&lt;/p>
&lt;p>Now, we need to make sure what happens, if we compare the bucket name with the events subject name and the length is not greater than zero, which means, that there IS already a bucket with my conferences name (this is always the case if its not the FIRST person who submits the form at a conference)&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1611/1*4VihA3DjKP0ahSW_pG9dfw.png">&lt;/p>
&lt;p>&lt;strong>10. Create a bucket and a new task&lt;/strong>&lt;/p>
&lt;p>This is more basic stuff, because we don’t need any tricks to do that, we just put two actions in this IF NO condition:&lt;/p>
&lt;ul>
&lt;li>Create a new bucket, Name is the Subject of our Event. * Create a new task inn THAT bucket, bucket ID is available in Dynamic Content, just repeat the magic with the date :-)&lt;/li>
&lt;/ul>
&lt;h1 id="thats-it--">Thats it :-)&lt;/h1>
&lt;p>This is my entire flow in its full beauty :&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/509/1*DvW17Nix9-GWH4Tfatmb1A.png">&lt;/p>
&lt;h1 id="feedback">Feedback?&lt;/h1>
&lt;p>What do you think? Is this a solution you’d like to copy or work with? Please use the clap feature to tell me if you found this valuable so I get an idea of which content is relevant for you.&lt;/p>
&lt;p>Use cases for this flow: Events like Conferences, Conventions, Workshops, trade fairs etc. — what&amp;rsquo;s your opinion? Always open for feedback :-)&lt;/p></description></item><item><title>What shall I use for managing tasks? Email? Outlook Tasks? Planner? To Do or even OneNote?</title><link>https://m365princess.com/blogs/2019-05-10-what-shall-i-use-for-managing-tasks-email-outlook-tasks-planner-to-do-or-even-onenote/</link><pubDate>Fri, 10 May 2019 12:02:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-05-10-what-shall-i-use-for-managing-tasks-email-outlook-tasks-planner-to-do-or-even-onenote/</guid><description>&lt;p>So many choices, which isn’t a bad thing. It just matters, what you want to achieve and what is the business case behind your wish list.&lt;/p>
&lt;h1 id="the-swiss-army-knife--and-the-confusion-about-it">The Swiss Army Knife — and the confusion about it&lt;/h1>
&lt;p>Microsofts Office 365 is so often compared to a Swiss Army knife — pretty handy, absolutely versatile and very practical to use — if you know how to use it right and if you know which tool meets your needs and the needs of the ones you work with.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2163/1*lDyK8Z2gcKcXXtkMScgzQQ.png" alt="">
sketched Swiss Army Knife entitled with “Office 365”
&lt;p>When I talk to customers, they often express a feeling of being drowned in a sea of tools, not only by Microsoft but also by tools of other vendors, too. They really struggle with making it easy to decide or even feel natural, which tool to use when. Although Microsoft seems to give a lot of information about this, at least some customers remain at a loss. The answer I need to give on the question “Hey Luise, what shall we use for task management?” always seems to be: It depends!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1545/1*RBwmMHa6u_o9nvaN1dpyRQ.png" alt="">
Office Drama sticker Woman with speechbubble: “It depends”
&lt;h1 id="unstick-from-email">Unstick from Email&lt;/h1>
&lt;p>To be able to use Office 365 in a way that actually helps us be more productive, have more time to focus on what’s important rather than being just busy, we not only need to use new tools, but also fundamentally change the way we work. But what does that mean exactly? It means for example to unstick from abusing e-mail for taskmanagement. Coming from a world where we have learned for 20 years to do everything with Email, it is perfectly natural for my clients to forward tasks to each other via email. Unfortunately, this has the big disadvantage that we always make each other a bottleneck. If you want to know the current state of affairs, you first have to ask the person who has been entrusted with it. That is not only a big time waster but it also means that we have less focus for really value-adding work. Moreover, we do not have a good overview of everything that needs to be done and there is a great lack of transparency.&lt;/p>
&lt;h1 id="planner--to-do">Planner &amp;amp; To-Do&lt;/h1>
&lt;p>As I wrote on Medium before in in &lt;a href="https://regarding365.com/11-reasons-why-i-fell-in-love-with-microsoft-to-do-27bd4e9b2846">my blogpost why I fell in love with Microsoft To-Do&lt;/a>:&lt;/p>
&lt;p>Planner relates to SharePoint TeamSites like To-Do relates to OneDrive. This means:&lt;/p>
&lt;p>Planner is best used to store your team-related tasks, everything you need to accomplish in the context of a project or process you work in with others together. You share responsibility and ownership for the overall outcome.&lt;/p>
&lt;p>To-Do is best used for your individual tasks where you own your tasks by yourself.&lt;/p>
&lt;p>When I explain these different concepts of Planner including Office 365 Groups (… and Microsoft Teams as well), the ability to group, filter, assign tasks and incorporate Planner tasks in your processes with Microsoft Flow, nearly everybody is totally amazed.&lt;/p>
&lt;p>Also To-Do earns lots of appreciation as I am more than enthusiastic about the MY DAY feature, which makes me decide every day very mindful and consciously what this day is made for. It brings me back in the driver’s seat because I am no longer reactive, but proactive, which affects my perception of tasks. Instead of experiencing feelings of guilt and the feeling of not being able to do everything anyway, I am happy about everything I have achieved — this positive approach changes the way I think about work. For most people this idea is quite contagious and they would enjoy being able to work more self-determined and in their own rhythm. For all who want to dig deeper into that topic I love to recommend the faboulous book &lt;a href="https://www.amazon.de/Make-Time-Focus-Matters-Every/dp/1984822438/ref=asc_df_1984822438/?tag=googshopde-21&amp;linkCode=df0&amp;hvadid=310585375631&amp;hvpos=1o1&amp;hvnetw=g&amp;hvrand=6503069958699644134&amp;hvpone=&amp;hvptwo=&amp;hvqmt=&amp;hvdev=c&amp;hvdvcmdl=&amp;hvlocint=&amp;hvlocphy=9043809&amp;hvtargid=pla-536060196459&amp;psc=1&amp;th=1&amp;psc=1&amp;tag=&amp;ref=&amp;adgrpid=64736365674&amp;hvpone=&amp;hvptwo=&amp;hvadid=310585375631&amp;hvpos=1o1&amp;hvnetw=g&amp;hvrand=6503069958699644134&amp;hvqmt=&amp;hvdev=c&amp;hvdvcmdl=&amp;hvlocint=&amp;hvlocphy=9043809&amp;hvtargid=pla-536060196459">Make Time&lt;/a>.&lt;/p>
&lt;h1 id="most-wanted-one-tool-to-rule-them-all">Most wanted: One tool to rule them all&lt;/h1>
&lt;p>But you can bet that there will be at least one person in the workshop who will say the magic words: “But if I use different apps for different purposes, then I have to open several apps to know what to do. I just want just ONE app in which everything should take place.” BOOM! — One tool to rule them all.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1535/1*PlsLKlKWiZ-xeDZBahHl8A.png" alt="">
&lt;p>One does not simply use one tool to rule tem all — Meme from Lord of the Rings&lt;/p>
&lt;p>Till now, I always needed to convince this wise guy to not only deal with change, but to embrace it, supporting him with “what’s in it for me” and explaing the purpose (e.g. transparency, overview, enabling teamwork) of a tool like Planner, because there was no &lt;a href="https://regarding365.com/do-we-really-need-only-one-pane-of-glass-d6f1ccb1eee">one pane of glass&lt;/a>.&lt;/p>
&lt;p>Please don’t get me wrong — I still think, that different workloads need specialized tools, but displaying content from different interlocking tools in just one app to get a better bird’s eye perspective to overview everything in order to then deep dive into one area of tasks to experience the worm’s-eye view is the best approach to deal with complexity. By achieving this, my calling from the title: “Lets stop confusing our customers” comes true:&lt;/p>
&lt;p>Microsoft does a phenomenal job listening to their customers and delivering awesome solutions that do not only represent what they are looking for but what really meet their needs. Since last improvements and updates, I love to spread the word: There is just one single app in which you can access all the tasks, that are relevant to you.&lt;/p>
&lt;h1 id="icymi-its-microsoft-to-do">ICYMI: It’s Microsoft To-Do!&lt;/h1>
&lt;ol>
&lt;li>My personal task — that is easy 2. Tasks assigned to me — Planner LOVES To Do, they teamed up and provide you with a new smart list called assigned to me. You can access all tasks that are assigned to you in Planner through the To-Do app and can even open them in Planner as well to dig deeper. 3. Flagged Emails — this isn’t totally new, but one of the features I truly love the most about To-Do. &lt;a href="https://regarding365.com/how-to-stay-organized-with-your-mails-in-outlook-like-a-rockstar-7b23f8106114">Perhaps you know my approach of dealing with Email&lt;/a>. It includes also flagging emails when you need something to do with it. These flagged emails appear also in your To-Do app.&lt;/li>
&lt;/ol>
&lt;p>That means, that To-Do is your window to all tasks you need to do, just like OneDrive isn’t only your pretty little document library but your perspective on all files you have access to. Overview your tasks and tick the boxes even on the go.&lt;/p>
&lt;h1 id="feedback--whats-next">Feedback &amp;amp; what’s next?&lt;/h1>
&lt;p>What do you think? What are your experiences with Planner integration of Microsoft To-Do? What do you like the most and what needs some extra improvements? Please share your thoughts right here or find me on &lt;a href="https://www.twitter.com/LuiseFreese">twitter&lt;/a>. If you liked this blogpost, please clap so I know what kind of content you are interested in.&lt;/p></description></item><item><title>On my way to become a Power User</title><link>https://m365princess.com/blogs/2019-03-26-129-2/</link><pubDate>Tue, 26 Mar 2019 11:54:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-03-26-129-2/</guid><description>&lt;p>Maybe you have been following my learning journey on the way to becoming a PowerUser for a while. If not, these articles can help:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://regarding365.com/what-happened-to-me-when-i-created-my-very-first-microsoftflow-61d2dd923660">how it feels to create my very first #MicrosoftFlow&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://regarding365.com/a-consultants-working-hours-tracker-build-in-microsoftflow-f054dc359748">working hours tracker&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://medium.com/@LuiseFreese/using-microsoft-flow-to-automate-my-process-of-sending-stickers-f9ec29eed930">Sticker process&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>To make a long story short here: I’m a business consultant in the Microsoft Office 365 universe and take care of all the non-technical, but strategic, interpersonal and work organizational parts of the project so that digital transformation can actually happen.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/827/1*v_TAsjJxL-IdmslU3OjDlA.png" alt="">
&lt;h2 id="my-journey">My Journey&lt;/h2>
&lt;p>I started working on Microsoft Flow 8 weeks ago and have already blogged about the feelings I had about creating my very first flow and of course what other solutions I built, see above.&lt;/p>
&lt;p>This blog post is less about new solutions than about what it actually does to me to work with Flow. In the beginning I thought: yes, this is an app that works like the Excel IF formula but across apps. And that’s true, too. Certain triggers (if) lead to certain actions (Then). And I knew, that these can be multiple actions, that nested conditions can be set and that there are multiple connectors. I knew all this, but it only really came to life for me when I tried it for myself. In one of my blogposts, I already said that I felt like Alice in Wonderland…&lt;/p>
&lt;p>I searched for and found usecases where I wanted to automate recurring tasks. Because I didn’t have a tutorial for it, I had to write it myself. I thought about the steps I would like to do one after the other and understood that I can transfer certain information to the next step. I am still at the very beginning of my journey and yet I have the feeling that I have just arrived at a nice viewpoint:&lt;/p>
&lt;p>The work with Flow has led me to think differently about work, which has a great impact on my work. I’ve always divided work into&lt;/p>
&lt;ol>
&lt;li>
&lt;p>the part of value-creating work that makes sense and where we feel we’re really making a contribution.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>the part of work that feels more like shoveling things from left to right or keeping us artificially busy and where we realize that the processes are just awkward.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>When consulting customers, I always tried to find solutions to make everyday work easier to do by figuring out, how apps should be used instead of how they are actually used plus explaining what is possible with Office365 (like share files instead of sending copies).&lt;/p>
&lt;h2 id="the-power-of-microsoft-flow">The Power of Microsoft Flow&lt;/h2>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1482/1*ecG1Dj2sPEUYe-cSQjP-iA.png" alt="">
&lt;p>Now that I realize the unbeatable power of MicrosoftFlow I start to think differently. It’s not only about the right use of the right apps. It’s also about focusing on the 1st part of work and automating the 2nd part as much as possible. Remember the claim of Microsoft Flow? _Work less — do more! _Whenever I think about or hear that someone tells about a task that belongs to the 2nd part of work, I just ask myself instantaniously: Can this be done wit Flow? Can I do this by myself? Should I just try that? Hell yeah!&lt;/p>
&lt;p>So I just did some fun stuff by creating a form just for myself, where I ask myself several questions about the idea, process these information to a SharePoint list and create tasks in dedicated list in my Microsoft To-Do app&lt;/p>
&lt;p>Why I just don’t use OneNote or Excel oder just TYPE in To-Do? To learn and to deepen my knowledge. To figure out what I am able to do and so that things become natural and easy for me.&lt;/p>
&lt;p>I literally had no idea how deep that rabbit hole was, where I just jumped in. I jumped in and gave a session about Flow at a regional Meetup and because it really went good, I had the selfconfidence to submit a session for a SharePoint Saturday — guess what:&lt;/p>
&lt;h2 id="speaking-engagements-are-learning-opprtunities">Speaking engagements are learning opprtunities&lt;/h2>
&lt;iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//twitter.com/luisefreese/status/1110507277902139398&amp;image=https%3A//i.embed.ly/1/image%3Furl%3Dhttps%253A%252F%252Fpbs.twimg.com%252Fmedia%252FD2lQeyPWsAcJygX.jpg%253Alarge%26key%3Da19fcc184b9711e1b4764040d3dc5c07" allowfullscreen="allowfullscreen" height="281" width="500">&lt;/iframe>
&lt;p>Being accepted as a speaker with a subject, that isn’t in my comfort zone is challenging but rewarding at the same time. Yes, I need more time to prepare than for my bread-and-butter-daily work. But the opportunity to learn, share and grow both personally and professionally is so amazing. Community lifts me up and guides me, whenever I need it. Belonging to a group of supportive people makes me feel strong and capable and this enables me to go further and further. Community makes me feel like this:&lt;/p>
&lt;iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//twitter.com/luisefreese/status/1110232933518987264&amp;image=https%3A//i.embed.ly/1/image%3Furl%3Dhttps%253A%252F%252Fpbs.twimg.com%252Fprofile_images%252F971112151975911430%252FugG5kUrN_400x400.jpg%26key%3Da19fcc184b9711e1b4764040d3dc5c07" allowfullscreen="allowfullscreen" height="466" width="680">&lt;/iframe>
&lt;p>Next stop on my learning journey? I don’t know, just stay tuned :-) But perhaps you wanna join me? It reminds me a bit of a sticker that made a few months ago:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2277/1*9T5k56Oj74Khj2DbCMQH1A.png" alt="">
&lt;h2 id="weqbeatsiq">WeQbeatsIQ&lt;/h2>
&lt;p>What do you think? Where are you on your learning journey? Do you share it? I would love to hear your feedback. Please clap, if this added value to your life / work so I know which content I should offer. You can always find me on &lt;a href="http://www.twitter.com/LuiseFreese">twitter &lt;/a>.&lt;/p></description></item><item><title>Using Microsoft Flow to automate my process of sending stickers…</title><link>https://m365princess.com/blogs/2019-03-25-using-microsoft-flow-to-automate-my-process-of-sending-stickers/</link><pubDate>Mon, 25 Mar 2019 11:49:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-03-25-using-microsoft-flow-to-automate-my-process-of-sending-stickers/</guid><description>&lt;p>It all started with my nice idea of sketching some stickers and I offered on twitter to send those stickers for free.&lt;/p>
&lt;p>Never expected that amount of direct messages and replies, so I took that as a learning opportunity to automate the handling. What I want:&lt;/p>
&lt;h1 id="must-haves">must-haves&lt;/h1>
&lt;ol>
&lt;li>Nice and easy-to-use form to ask users to submit their shipping details 2. Envelopes with addresses of the users 3. Email-Notification for me&lt;/li>
&lt;/ol>
&lt;p>Let’s do this!&lt;/p>
&lt;h2 id="1-create-a-form-in-microsoft-forms">1. Create a Form in Microsoft Forms&lt;/h2>
&lt;p>Just ask the following questions: How many stickers do you need?, (Why do you need more than 3?), Email Address, Twitter Handle, First Name, Last Name, Street &amp;amp; Number, ZIP Code, City, Country&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2019/1*jhKzU4FtAVUwFGFdfXR2WQ.png" alt="">
&lt;h2 id="2-create-a-word-document">2. Create a Word Document&lt;/h2>
&lt;p>a) enable the Developer-Tab (File → Option → Customze Ribbon → Check Box at Developer Tab)&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2798/1*Z2vzx4lYxhFutquPOUVUEQ.png" alt="">
&lt;p>b) Create new Word Document, save it to OneDrive (AutoSave enabled)&lt;/p>
&lt;p>c) Design the labels for the envelope using Plain Text fields (Aa)&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/695/1*ZErvHV4s1zxue_-M1XIy5w.png" alt="">
&lt;p>like this:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/887/1*BD0OzoKeXQVYvRcguCV4mA.png" alt="">
&lt;p>d) go to &lt;a href="http://flow.microsoft.com/">flow.microsoft.com&lt;/a> and click on My Flows → New → Create from Blank → Create from Blank&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1686/1*aq5dOzADNn8Z0byrDe9dmg.png" alt="">
&lt;p>e) Type “forms” in search bar and choose “Microsoft Forms”&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1670/1*efq5M0t28pUMoL7HeOcz1A.png" alt="">
&lt;p>f) choose “ When a new respond is submitted”&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1599/1*AHvo6ivL7FTgYdEP26MQUg.png" alt="">
&lt;p>g) Pick your form&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1676/1*xVANPRHyCTlAjQidhqFXdg.png" alt="">
&lt;p>h) Click “Add action”, then type “Forms” again and select “Microsoft Forms”. Select now “Get response details”.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1653/1*RHXm6ZgQP66EqlcNKI98lg.png" alt="">
&lt;p>i) Again, select your form and now click on “Add dynamic Content” for the Response ID. Select “List of response Notifications Response ID” on the right hand side.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2583/1*4TcRy5Y06HE-KFCBvrCKaw.png" alt="">
&lt;p>j) Click “Add action” and type “Create Item”, then select “SharePoint Create Item”&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1611/1*OQO6F2lRdmTXSJqVFSYXZA.png" alt="">
&lt;p>k) Hop over to SharePoint, create a new List.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/603/1*QTC5qzmSdsC6I9j0_YsgGQ.png" alt="">
&lt;p>l) Create a few Columns, make sure they are ALL just text. Rename the Title Column with “Twitter Handle”. These are the Columns needed:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2291/1*lOImuXGXo5x-PdJkX4YXhw.png" alt="">
&lt;p>(as these represent the questions of your form.)&lt;/p>
&lt;p>m) Now that you created your SharePoint list, we want to make sure, that Flow processes the information from Forms to SharePoint. Just hop over to Flow again, and select your SharePoint Site and in that Site your SharePoint list:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2591/1*I8_CuIqC1dTY220M9_Uneg.png" alt="">
&lt;p>Just click on the Dynamic Contents at the right hand side:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1584/1*qIF21IfujxKT6FD4RpJlRA.png">&lt;/p>
&lt;p>n) Click Next Step, Type “Word” and select “Word for Business”, then Select “Polulate a Microsoft Word template”&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1605/1*LOjbgjO39QPPNMbkiZ6s3g.png">&lt;/p>
&lt;p>o) Select “OneDrive for Business” as Location, “OneDrive” as your Doc Library and browse to your Word document by clicking the tiny folder icon. Fill out the other fields with the Dynamic Contents from Forms just like this.&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2609/1*FL3wyCd0n3dOqxgZSVSMYg.png">&lt;/p>
&lt;p>p) Click “next step”, then “Create file”, because after populated the new Word document we need to create a new file for each time a new form is submitted:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1631/1*AbctUHhUX0zR6whO7l3FHg.png">&lt;/p>
&lt;p>Make sure that each Document has a unique name, I just chose “Envelope First Name Last Name” which makes it easy to recognize.&lt;/p>
&lt;h2 id="3-turn-on-notifications-on-your-sharepoint-list">3. Turn on Notifications on your SharePoint List&lt;/h2>
&lt;p>Just click on the … icon and select notifications&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2865/1*S9dgiommpJtEHoGJ_untuw.png">&lt;/p>
&lt;p>Now choose the notification you prefer and click OK. I prefer a daily summary — dont want to spam myself :-)&lt;/p>
&lt;h2 id="thats-it--for-must-haves">That’s it! — for must-haves&lt;/h2>
&lt;p>Every time, a new form is submitted, a new Word Document, which can be an envelope or label is generated and once a day I get a summary of all submissions in my SharePoint list.&lt;/p>
&lt;h1 id="nice-to-haves">nice-to-haves&lt;/h1>
&lt;p>Just because this works, we don’t need to stop and can add some additional steps. Here are the nice-to-haves:&lt;/p>
&lt;h2 id="thank-you-tweet">Thank-you tweet&lt;/h2>
&lt;p>Click the +Icon to add a next step wherever you like, the order does not play any role.&lt;/p>
&lt;p>Type “Twitter” in search bar, select “Post a tweet”&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1688/1*rBFISVsqA4BvGwQWnIhPsg.png">&lt;/p>
&lt;p>Type in the text you want to tweet, enrichen that with some Dynamic Contect like First Name etc. Please note: If you choose Twitter Handle this won’t cause an @ mention in your tweet, because twitter doesn’t want you to automate @ mentions — for reasons :-)&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2543/1*KnTvsuIvfE_16bEmJcCONg.png">&lt;/p>
&lt;h2 id="picture-for-thank-you-tweet">Picture for thank-you tweet&lt;/h2>
&lt;p>tweets with pictures are more engaging than those without pictures. To be able to post a picture (that of the stickers) you need to upload that photo first to your OneDrive and then add an action before (!) that twitter action:&lt;/p>
&lt;p>Type “get file content” in search bar an select the OneDrive action:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1598/1*xwo2ndhRTIVmVJgKc5ZGjQ.png">&lt;/p>
&lt;p>Browse to the picture you like to post by clicking on the folder icon. Now insert that file in your tweet action:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1593/1*WSnyoM4roxOkPTrABCxhFg.png">&lt;/p>
&lt;h2 id="email-to-user">Email to user&lt;/h2>
&lt;p>If you additionally like to send your user an email, just add an action “Send email”&lt;/p>
&lt;p>Type “send email” in search bar and select the Office 365 Outlook action:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1598/1*G9kQjeeJAJCSDy8Z9GLnow.png">&lt;/p>
&lt;p>Just specify the 3 fields with your text and some Dynamic Content:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1641/1*sUWgNEJ8iNz1JSpRAgwH4g.png">&lt;/p>
&lt;p>Click on “Advanced Options” and click on “Is HTML” → yes!&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1610/1*BhIN25vkH5ghRaaaZHHlWQ.png">&lt;/p>
&lt;h2 id="to-do-in-microsoft-to-do">To Do in Microsoft To-Do&lt;/h2>
&lt;p>If you like your To Dos in Microsoft To-Do, it could be helpful to automate this as well.&lt;/p>
&lt;p>First, let’s create a new list in Microsoft To-Do.&lt;/p>
&lt;p>Then just add another action in your flow, type “To-Do” in search bar and select “Microsoft To-Do”, then “Add a to-do”&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1701/1*Rk1ySX1DfpdkKBvTSugJzw.png">&lt;/p>
&lt;p>I want the twitter Handle and the amount of stickers in the title, so I just add that Dynamic Content:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1578/1*G2kcUC5h4oEc91mteEQUJw.png">&lt;/p>
&lt;p>In the body I just inserted the address of the user, just in case :-)&lt;/p>
&lt;h2 id="alternative-to-microsofttodo-microsoftplanner">Alternative to #MicrosoftToDo: #MicrosoftPlanner!&lt;/h2>
&lt;p>If you need your tasks in a Planner Board, thats easy to manage. Please do yourself a favor and create first a bucket in planner where you want those tasks to appear. Saves you some back-and-forth work (trust me! :-))&lt;/p>
&lt;p>Just delete the Microsoft To-Do action (as we don’t want duplicates of our tasks in several apps!) and add another action. Type “Planner” in search bar and choose “create a new task”. Pick the rigt plan and give the task a nice name like that:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2022/1*b79BuBvSV6WX-gZjPUo5bw.png">&lt;/p>
&lt;p>Also choose the bucket you created.&lt;/p>
&lt;p>Now the hardest part… Took me some time to figure out how that works, but I will guide you through. I want to set a due date, which is 3 days after the task was automatically created.&lt;/p>
&lt;p>For that we need to use those expression you can find on the right hand side. Just click Dynamic Content and then the tab “Expressions”:&lt;/p>
&lt;p>A few explainations:&lt;/p>
&lt;ol>
&lt;li>just now = &lt;em>utcNow()&lt;/em>&lt;/li>
&lt;/ol>
&lt;p>the brackets are important!&lt;/p>
&lt;p>I want to add 3 days to now.&lt;/p>
&lt;ol start="2">
&lt;li>The formula has named addDays and needs that syntax (like an Excel formula, so nothing really scary!):&lt;/li>
&lt;/ol>
&lt;p>&lt;em>addDays(timestamp, number of days, format)&lt;/em>&lt;/p>
&lt;p>the timestamp is our &lt;em>utcNow&lt;/em>&lt;/p>
&lt;p>number of days shall be 3, but you can choose another value as well.&lt;/p>
&lt;p>format needs to be &lt;em>‘YYYY-MM-dd’&lt;/em>&lt;/p>
&lt;p>Ok lets put that together:&lt;/p>
&lt;ol start="3">
&lt;li>&lt;em>addDays(utcNow(),3,’yyyy-MM-dd’)&lt;/em>&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://miro.medium.com/max/1296/1*4YlXEg1LGl1XEEKXf9wz4Q.png">&lt;/p>
&lt;p>you see, it’s not &lt;strong>that&lt;/strong> hard :-)&lt;/p>
&lt;p>Please note: right now, this is actually broken in ToDo and in Outlook tasks, only works in planner.&lt;/p>
&lt;h2 id="again-thats-it">Again, that’s it!&lt;/h2>
&lt;p>Every time, a new form is submitted, additionally to our earlier achievements with the must-haves, we now post a tweet with a nice picture, send an automated mail to user and are able to check our To-Dos in Microsoft To-Do or in Planner.&lt;/p>
&lt;h1 id="what-do-you-think">What do you think?&lt;/h1>
&lt;p>Is there anything missing? Please keep in my mind, that I am a beginner in Microsoft Flow, I started 8 Weeks ago with that journey.&lt;/p>
&lt;p>Read here the very beginning &lt;a href="https://regarding365.com/what-happened-to-me-when-i-created-my-very-first-microsoftflow-61d2dd923660">what happened to me when I created my very first flow&lt;/a> and here &lt;a href="https://regarding365.com/a-consultants-working-hours-tracker-build-in-microsoftflow-f054dc359748">how I build some stuff to make it easier for me (and for my customer) to track working hours&lt;/a>.&lt;/p>
&lt;p>I would love to take that to the next level! Any recommendations are more than welcome, please engage right here or on twitter, just use hasttag #JustGoWithTheFlow&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/1800/1*Qm_WKmyfoXiS5haUiAjP1Q.jpeg">&lt;/p>
&lt;h1 id="update">Update&lt;/h1>
&lt;p>I updated my flow with two small changes:&lt;/p>
&lt;ol>
&lt;li>I deleted the twitter-action (and the get file content action as well) because I got feedback that someones whole timeline was full of my tweets :-) 2. I added some Power-Bi magic to just visualize the data. Its really easy.&lt;/li>
&lt;/ol>
&lt;p>a) go to &lt;a href="http://powerbi.microsoft.com/">powerbi.microsoft.com&lt;/a> and login.&lt;/p>
&lt;p>b) click in My Workspace on Create (upper right Corner) and choose “Streaming Data Set”, then select API, next&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/920/1*qt_k3-Ph3Y1aZgv5Mmo1Mg.png">&lt;/p>
&lt;p>c) Just name your Data Set and Create two values, I chose Country and Amount (needs to be a number). Click Finish.&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/923/1*bLjfGv0q0lXOBGA5L4ALUg.png">&lt;/p>
&lt;p>d) Hop over to flow again and add an action. Type “PowerBi” and select “Add rows to a Streaming Data Set”&lt;/p>
&lt;p>e) Fill out required fields with your data and the additional fields with Dynamic Content from the form.&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/2672/1*pu2zeBngSNLb32mMgvNZ5A.png">&lt;/p>
&lt;p>f) Play around in PowerBI:&lt;/p>
&lt;p>Just try some buttons at the right hand side to create an awesome lookung report:&lt;/p>
&lt;p>&lt;img src="https://miro.medium.com/max/3738/1*JbTsYBaGDls5_nnkOWQSnQ.png">&lt;/p>
&lt;h2 id="stickers">Stickers&lt;/h2>
&lt;p>You want to experience the frontend or just need some stickers? Click&lt;/p>
&lt;p>&lt;a href="https://forms.office.com/FormsPro/Pages/ResponsePage.aspx?id=zi4c4gpLsECfwB6QfFGD2HpekuraHSRMguJdcA-4CV1UQTNSWVdRN0FDUVhFT0Q2QUIwREVPWE5XSS4u">Sticker Request Form&lt;/a>&lt;/p>
&lt;p>Do you find this kind of blogpost helpful? Please clap to make sure I get that feedback.&lt;/p></description></item><item><title>No email just for the sake of #NoEmail?</title><link>https://m365princess.com/blogs/2019-03-10-95-2/</link><pubDate>Sun, 10 Mar 2019 11:16:30 +0000</pubDate><guid>https://m365princess.com/blogs/2019-03-10-95-2/</guid><description>&lt;p>When you follow my blogposts, you know that I really don’t like email. Wait a minute, is that true? Do I dislike email? No, not at all. I only feel that the daily and widespread email abuse is not only unproductive, but also pointless.&lt;/p>
&lt;p>I wrote about &lt;a href="https://regarding365.com/how-to-stay-organized-with-your-mails-in-outlook-like-a-rockstar-7b23f8106114">how&lt;/a> I use my Outlook to tame my inbox and I wrote about &lt;a href="https://regarding365.com/how-we-overcome-the-pain-with-email-9a50c238252">why&lt;/a> I think that an aware and mindful handling of our working time and our attention is crucial to us: We all seem to be torn between FOMO (Fear of Missing Out), and the resulting information overload.&lt;/p>
&lt;h1 id="why-do-we-struggle-with-email">Why do we struggle with Email?&lt;/h1>
&lt;p>We mis-use email as a tool for document management, task management, project management, as a substitute for chat, as a reminder, as a notebook. No wonder that we are in danger of drowning in the flood and that a healthy use seems almost impossible. Many users have simply given up and the number of unread items is no longer helpful information.&lt;/p>
&lt;p>Sometimes I hear or read conversations in which people who have also made themselves aware of how harmful our unproductive our use of email is, and they come to the following conclusion: “Our email habits are evil! So, let’s just stop writing emails.”&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/5184/1*7tnfhOP_Yy8YdjR6SPZa1g.jpeg" alt="">
&lt;p>For company internal use cases, you may probably shift to Microsoft Teams and Yammer or other tools from other vendors. For more information on this, &lt;a href="https://regarding365.com/will-microsoft-teams-replace-email-4c295cc28c4a">please read my blogpost if Microsoft Teams will replace Email&lt;/a> (Spoiler Alert: It won’t).&lt;/p>
&lt;p>And for those internal communication, its mostly a very good idea not to use Email, because we already have better tools for document management, task- and project management, chat and taking notes. We have a secure workplace solution where we can access the files, we need to access without having to send copies of copies of copies.&lt;/p>
&lt;p>For company external communication, we tend to use email, because we don’t have this secure workplace solution with every customer or lead or supplier. Sure, sometimes we work so closely with externals, that we have that need to invite them to our platform, but most of the time the effort would not justify this procedure and email is simply ubiquitous. Everyone owns an email address and gets instant notifications by default on all their devices.&lt;/p>
&lt;p>To me personally, this is a nightmare, but we have to deal with it! Some people feel helpless and are not willing / no longer able to deal with this email madness that has completely gone off the rails: A seemingly endless stream of notifications forces us to be constantly interrupted, we cannot work, eat, talk or even go to the restroom without looking at our mobile phones.&lt;/p>
&lt;h1 id="escape-from-email">Escape from Email?&lt;/h1>
&lt;p>So, some are trying to escape that email nightmare by driving a #NoEmail strategy. So, they don’t try to control the inbox, they want to avoid email at all.&lt;/p>
&lt;h2 id="internal-usage">Internal usage&lt;/h2>
&lt;p>For the internal use it works quite marvelously to rely on other tools, mostly these are Teams, Yammer, Skype (in the Microsoft universe… other tools from other vendors). Everyone notices how beneficial it is to use real collaboration tools, because. Let’s face it: Email is NOT a tool for teamwork: It’s not social, knowledge dies in personal mailboxes, and it’s very hard to read content in the right context. Email is simply the lowest common ground. It’s the tool we must use when we don’t have a shared platform where we can work together.&lt;/p>
&lt;h2 id="private-usage">Private usage&lt;/h2>
&lt;p>For private use, we are all familiar with using several text messaging services like WhatsApp, iMessage etc. Some of us share photos or documents with family / friends on iCloud/ google drive etc. or plan events with Trello.&lt;/p>
&lt;h2 id="external-business-usage">External business usage&lt;/h2>
&lt;p>And now it’s getting freaky for me: Those #NoEmail / #ZeroEmail advocates try to avoid email just for the sake of #NoEmail. They pride themselves on planning a project with twitter or LinkedIn direct messages. And this really irritates me: By default, you get an instant email notification about those direct messages, and if you turn them off, you just have another channel you constantly must check — this is as bad as email — but without the advantages of email; e.g. being super searchable. But Congratulations: Sharing information on social media direct messages is what we call Shadow IT — and we must fight that for several reasons!&lt;/p>
&lt;h1 id="how-can-we-establish-healthy-email-habits">How can we establish healthy Email habits?&lt;/h1>
&lt;p>If we really want less or a healthy amount and a healthy behavior with emails, we need to understand that this full inbox is just a symptom, but not the real trigger. we must tackle the problem at the root, so we’d rather fight the source of the inflammation than the resulting fever.&lt;/p>
&lt;p>But what is the cause for so many emails? Unfortunately, there is not only one single cause, but several, which are mutually interdependent and mutually conducive.&lt;/p>
&lt;h2 id="abuse-as-document-management-system--lack-of-an-easily-accessible-place-to-store-and-edit-files">abuse as document management system — lack of an easily accessible place to store and edit files&lt;/h2>
&lt;p>Just to be clear — this is mostly not users’ fault or responsibility. Employees should store their files on shared drives — usually each department has its own drives and employees from other departments do not have access to them. This is because companies have long been structured as silos and are only gradually realizing that cross-functional teams are much more capable of working. These information silos were also replicated in the structure of permissions on folders and documents, which now has the disadvantage that people who want to work together have to find awkward workarounds. Sure, there is a probably a process to manage access of employees from other departments to a certain folder, but it takes time, the process is cumbersome, and it is much faster to simply send a copy of the document by email, which causes a few new problems:&lt;/p>
&lt;h2 id="version-chaos">version chaos&lt;/h2>
&lt;p>We create version chaos (is THAT really the final version? Let’s send an email to colleague just to be sure….)&lt;/p>
&lt;h2 id="information-iceberg">information iceberg&lt;/h2>
&lt;p>we have to deal with the fact that we hoard a lot of outdated documents, which exacerbates the additional problem of the information iceberg. Is this a bad thing? Absolutely, because if we mostly see stuff unrelated to our work, irrelevant at this moment or just don’t know how to deal with it, we train our minds to oversee information, while we need to have a deep look into relevant data.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2811/1*2HpcNFzDXYYj7BW74i8D1w.png" alt="">
&lt;h2 id="waste-of-time">waste of time&lt;/h2>
&lt;p>It is time consuming to reassemble these versions repeatedly and keep track of changes.&lt;/p>
&lt;p>You see — in every small problem is a bunch of bigger ones that absolutely wants to get out :-)&lt;/p>
&lt;h2 id="abuse-as-task-management-system">abuse as task management system&lt;/h2>
&lt;p>Email is often abused for “Hey Christina can you please the announcement on our website? Kind regards Tom”&lt;/p>
&lt;p>What’s wrong with it? Email isn’t meant to be a task management tool and you can see this when you think of tasks:&lt;/p>
&lt;h2 id="tasks-need-a-due-date">tasks need a due date&lt;/h2>
&lt;p>sure, Tom could write “till Friday”, but neither he nor Christina see this deadline in a filterable list.&lt;/p>
&lt;h2 id="tasks-need-ownership">tasks need ownership&lt;/h2>
&lt;p>Tom has no overview which tasks should be completed by whom. There is no way to create a view in Outlook.&lt;/p>
&lt;h2 id="tasks-need-to-be-tracked">tasks need to be tracked&lt;/h2>
&lt;p>Did Christina realize that she needs to act? How does Tom recognize how she progresses on the task and when it is/was finished? Sure, he can ask… causing MORE emails.&lt;/p>
&lt;p>But what should you use? Depending on your corporate culture, you have a few options in the Office 365 Universe (where my consultancy takes place):&lt;/p>
&lt;ol>
&lt;li>Outlook tasks: Assign them to another person, let him/her decide whether to accept or refuse that task and be able to not only track all tasks you assigned to others but also to get nice overview. This mostly gits with companies with top-down approach 2. Planner: You can assign tasks to others and to yourself if you like to and everything is stored in a nice looking Kanban board ensuring openness and tranparency. Works best with teams who appreciate that everyone can see all the tasks. 3. To-Do: Your personal taskmanager. I wrote a &lt;a href="https://regarding365.com/11-reasons-why-i-fell-in-love-with-microsoft-to-do-27bd4e9b2846">blogpost why I really fell in love with it&lt;/a>&lt;/li>
&lt;/ol>
&lt;h1 id="abuse-as-a-tool-to-chat--realtime-conversation">abuse as a tool to chat / realtime conversation&lt;/h1>
&lt;p>Email was designed to be asynchronous communication, so please just stop sending an email when you need to have a real time conversation. When we try to chat in email, we continuedly get interrupted by notifications because in the meantime while waiting on the reply, we try to be “productive” and do something else. Its also a sign of not committing yourself to a single conversation.&lt;/p>
&lt;h1 id="imagine">Imagine&lt;/h1>
&lt;p>if we just stopped using emails for sending files, assigning tasks and chat. What would happen? We would dramatically reduce the amount of emails we receive each day. From my experience over the last few years with my customers, only 10–20% of the amount of emails remains. Is there any reason now to replace the remaining 10 -20% of the emails with another tool (like LinkedIn direct message), which we handle in the same way just to be able to attach a #ZeroEmail tag to us? Of course, not.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/7776/1*V6zpCK2o3wz3D_YAQgOeiA.jpeg" alt="">
&lt;p>In my consultancy, I wouldn’t stop at that point of the journey to establish healthy habits. From my point of view, it’s important to analyze which kind of emails you get and then try to cluster them. After analyzing and clustering we need to figure out, if the processes which cause these email could be improved or if there are better tools to achieve your goals. For example, is there a better way than manually asking customers/ colleagues about their feedback or can we just automate this? By this approach we not only reduce the number of emails, but also improve our service: We stop being busy with being busy and start focusing on meaningful, value-adding work.&lt;/p>
&lt;h1 id="and-what-if-this-isnt-just-technical-circumstances-or-personal-habits">And what if this isn’t just technical circumstances or personal habits?&lt;/h1>
&lt;p>Now that we have clarified the technical circumstances and personal habits, I would like to change the flight altitude:&lt;/p>
&lt;p>Regardless of which tools are available and how consciously employees use them, a high volume of emails is also a symptom of a global problem in the company.&lt;/p>
&lt;p>Email flood almost always means: On the one hand there is a lot of need for formal coordination and on the other hand a &lt;em>save my ass&lt;/em> attitude (“&lt;em>I gave it to you in writing, I saved the email and will rummage it out again if I have to prove something&lt;/em>.”&lt;/p>
&lt;p>Why? Perhaps there are too many opposing projects and approaches, or because there is no clear direction but conflicting goals and no effective communication of strategy from the executive level. This is a structural problem which is very closely linked to the cultural identity of the company.&lt;/p>
&lt;p>To improve that, a social network like yammer is really beneficial. Here are some suggestions of questions to ask:&lt;/p>
&lt;ul>
&lt;li>Are both employees and executives clear on the role of collaboration to achieve the organizations strategic goals? * How do executives see the changing role of leadership in network organisations? * Do they see the value of participating personally, not through communications managers, assistants or other teams?&lt;/li>
&lt;/ul>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>We need more than just other tools. A clearly communicated corporate strategy, a trustful relationship and conscious interaction with our fellow human beings ensure that we can develop a healthy attitude that enables us to work in a meaningful and value-creating way.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/9000/1*sJxjNyzC3ipEMrEh6TdiCA.jpeg" alt=""></description></item><item><title>How can we overcome the pain with email?</title><link>https://m365princess.com/blogs/overcome-pain-email/</link><pubDate>Tue, 05 Feb 2019 11:37:36 +0000</pubDate><guid>https://m365princess.com/blogs/overcome-pain-email/</guid><description>&lt;h2 id="inbox-zero">Inbox Zero?&lt;/h2>
&lt;p>I reached this goal, inbox zero. I wrote a &lt;a href="https://regarding365.com/how-to-stay-organized-with-your-mails-in-outlook-like-a-rockstar-7b23f8106114">tutorial&lt;/a> how everyone can achieve that, too. Why is this important? For several reasons:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Your inbox folder is just a mailbox. You empty your mailbox to decide what you should do with every single letter on a daily basis. And after you read a letter, you don&amp;rsquo;t put that letter back in your inbox or file that letter in a folder till you completed the task in it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>White space is calming your mind!&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Zero Inbox demonstrates that you have your shit together :-)&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>But to dig a bit deeper, I like to explain why and how I came to my conclusions. I&amp;rsquo;ve given a lot of thought to&lt;/p>
&lt;ul>
&lt;li>how employees deal with email&lt;/li>
&lt;li>what is so painful about e-mail and&lt;/li>
&lt;li>what you can do to become productive again&lt;/li>
&lt;/ul>
&lt;p>In order to understand why I recommend what, I certainly find it useful to learn a little more about the background.&lt;/p>
&lt;h2 id="phases-in-your-workingday">Phases in your working day&lt;/h2>
&lt;p>Every human being has several phases in their work. Know your work to make your work work is a very good approach. So let&amp;rsquo;s figure out, what kind of phases we experience usually:&lt;/p>
&lt;h3 id="1-communicative-collaborative-phases">1. communicative-collaborative phases&lt;/h3>
&lt;p>Networking, exchange, encounter. In this phase we want to get into contact with others and build relationships. This is best done face to face, but also with tools such as video call/call. For best results, use this formula:&lt;/p>
&lt;p>The lower the information density, the longer it takes and the less satisfactorily we perceive it.
f2f &amp;gt; video call &amp;gt; call &amp;gt; text (synchronous) &amp;gt; text (asynchronous)&lt;/p>
&lt;p>Examples:&lt;/p>
&lt;ul>
&lt;li>Meet with a customer or colleague to really listen and really understand.&lt;/li>
&lt;li>Work together with colleagues on a concept: Brainstorming, idea generation, discussion, development of a concept draft, refinement, publication&lt;/li>
&lt;/ul>
&lt;p>It&amp;rsquo;s collaboration, if we really do have a common responsibility for the result and a common vision.&lt;/p>
&lt;h3 id="2-superficial-phases">2. superficial phases&lt;/h3>
&lt;p>Here we perform easier tasks at short intervals, which often belong to our absolute routine. We often switch back and forth between different tasks and often have the feeling that we are completing tasks at the same time. This causes stress in us, which in turn leads to a higher frequency of errors and makes us more tired. Cal Newport calls this &amp;ldquo;shallow work&amp;rdquo;.
Examples:&lt;/p>
&lt;ul>
&lt;li>Coordinating meetings&lt;/li>
&lt;li>checking social media&lt;/li>
&lt;li>skimming email&lt;/li>
&lt;/ul>
&lt;h3 id="3-focusedphases">3. focused phases&lt;/h3>
&lt;p>This so-called deep work is about experiencing really long phases of uninterrupted thought work. We enter into that flow feeling, where we are neither underwhelmed nor overstrained, where we are completely absorbed in our work, where time is forgotten and we are at the same time focused but relaxed. In this state we are highly productive because we work without distraction and without task switching. We only make very few mistakes and do not feel exhausted.&lt;/p>
&lt;h3 id="challenge-dont-mix-thesephases">Challenge: Don&amp;rsquo;t mix these phases&lt;/h3>
&lt;p>Now that we have clarified which work phases we have, we need to understand why we should not combine or mix these phases: Anyone who skims their emails in a conversation or calls a colleague while working on a concept, disturbs themselves and quickly gets out of concentration.&lt;/p>
&lt;p>Fact: If we are being disturbed in our deep work phase (smartphone pling, Outlook pling, ringing phone, colleague standing at the desk &amp;ldquo;do you just have 5 minutes?&amp;rdquo;), we need more than a quarter of an hour until we have reached our concentration level again… But since there will probably be more disturbances during this time, we drive the whole day with the handbrake on…&lt;/p>
&lt;p>So when we try to do our job without a plan and are interrupted by several devices, apps and colleagues, we are neither effective nor efficient. Emails are a special challenge for that.&lt;/p>
&lt;h2 id="problem-areas">Problem areas&lt;/h2>
&lt;p>We have a few problem areas which result of our understanding what email is to us. We should re-learn that email really is what it is, asynchronous communication. Asynchronous means: We and the other(s) are not in a dialogue (nearly) at the same time. We have several kinds of interruptions:&lt;/p>
&lt;h3 id="1-notifications">1. notifications&lt;/h3>
&lt;p>By default email notifications are enabled in Outlook, unfortunately. If we do not understand the asynchronous approach and try to simulate synchronous communication, we are permanently under stress and are constantly interrupted by these notifications (we all know them, they appear on the lower right hand corner).
Even if we think we don&amp;rsquo;t even notice them anymore: This is already the worst case: Our sense of where our attention actually is, is unfortunately disrupted and we have to teach ourselves again to be mindful of our working behavior.&lt;/p>
&lt;p>So please, do yourself a favor and disable notifications.&lt;/p>
&lt;p>(File → Options → Email → Message arrival → remove marks from all checkboxes
We don&amp;rsquo;t even need that envelope in our taskbar, trust me :-)&lt;/p>
&lt;p>&lt;img alt="disable notifications in Outlook" src="images/../../../../static/images/no-notifications.png">&lt;/p>
&lt;h3 id="2-curiosity">2. Curiosity&lt;/h3>
&lt;p>Once we stopped getting interrupted by Outlook Desktop, we make sure you turn off notification for Email (regardless in which app!) on your mobile phone(s). This was the easy part! Because now we have to deal with our working behavior. We trained ourselves for years or decades to be interrupted every few minutes and to confuse important with urgent so we feel a certain kind of curiosity and misunderstood sense of duty so we click on inbox although we already deal with another task. We need to stop that. Perhaps, some techniques like pomodoro may help you in the beginning. To me personally, it is more about making a conscious decision what I want to achieve and to established a self-paced working behavior.&lt;/p>
&lt;h3 id="3-colleagues">3. Colleagues&lt;/h3>
&lt;p>&amp;ldquo;Have you already read my email?&amp;rdquo; We have probably all heard and perhaps even said this sentence before. Stop asking that. Putting pressure on others doesn&amp;rsquo;t make their work better.
Perhaps it helps to manage expectations if you include a quick part in your reply to emails which will take longer to finally complete. Something like &amp;ldquo;I have marked the processing of your request for the end of next week. Should it be more urgent, please call me at xxx, thank you very much.&amp;rdquo; You can partly automate this using quicksteps in Outlook.&lt;/p>
&lt;h3 id="4-content-does-not-come-in-the-right-context--cumbersome-workingmethods">4. Content does not come in the right context / cumbersome working methods&lt;/h3>
&lt;p>Email doesn&amp;rsquo;t display information linked to processes and content by default and because most of us didn&amp;rsquo;t get proper training on how to deal with email, we just assume that it is our job to always try to figure out which belongs what. So we are busy with being busy, which is time-consuming, stress-causing and frustrating. Get over it! Stop moving emails to a huge amount of folders, subfolders and nested folders. Nested folders don&amp;rsquo;t make sense in a digital world where we can search and filter for information. Just think of your Outlook like an Excel table with lots of columns where you can just filter the information you need right now. &lt;a href="https://regarding365.com/how-to-stay-organized-with-your-mails-in-outlook-like-a-rockstar-7b23f8106114">Read more in-depth instructions here&lt;/a>&lt;/p>
&lt;h3 id="5-missing-overview">5. Missing Overview&lt;/h3>
&lt;p>Nearly every time we switch to Outlook, we scan/scroll through our emails, looking for answers:&lt;/p>
&lt;ul>
&lt;li>What has to be done right now? Is this urgent or important? (most of us are confusing this!)&lt;/li>
&lt;li>What am I not able to do now?&lt;/li>
&lt;li>What can I leave behind?&lt;/li>
&lt;/ul>
&lt;p>Again, we waste time because we didn&amp;rsquo;t decide consciously for every single email how to deal with it.
Solution:
Decide what phase you are in. If you communicate / collaborate, stop doing other things. If you focus, make sure not to be interrupted. If you do shallow work, stop trying to talk with others at the same time. Stop trying to multitask.
Deal with email the right way:&lt;/p>
&lt;ol>
&lt;li>Qualify your email 1–3 times a day, decide for each item: What do I need to do (next step) and till when do I have to complete that. Configure your Outlook as shown in instructions.&lt;/li>
&lt;li>Close Outlook while in Deep Work Phase&lt;/li>
&lt;/ol>
&lt;p>This makes sure you use the power of being focused when you need that and being reactive as far it is necessary. Please don&amp;rsquo;t ever underestimate of being in the flow zone :-)&lt;/p>
&lt;p>I once mind mapped my thoughts on email and hopefully this sketchnote will help you, too.&lt;/p>
&lt;p>&lt;img alt="sketchnote" src="https://m365princess.com/images/stop-living-in-mail-sketchnote.png">&lt;/p>
&lt;h2 id="final-note">Final note&lt;/h2>
&lt;p>A lot of my thoughts will also apply to messages in Teams :-)&lt;/p></description></item><item><title>How do we organize our phones?</title><link>https://m365princess.com/blogs/2019-02-05-how-do-we-organize-our-phones/</link><pubDate>Tue, 05 Feb 2019 11:37:36 +0000</pubDate><guid>https://m365princess.com/blogs/2019-02-05-how-do-we-organize-our-phones/</guid><description>&lt;p>After my last blog post about &lt;a href="https://regarding365.com/11-reasons-why-i-fell-in-love-with-microsoft-to-do-27bd4e9b2846">11+3 reasons why I fell in love with Microsoft To-Do&lt;/a>, my dear colleague &lt;a href="https://medium.com/@DarrellaaS">Darrell&lt;/a> from &lt;a href="https://regarding365.com/">Regarding 365&lt;/a> emphasized how cool he is about &lt;a href="https://twitter.com/DarrellaaS/status/1092316742469136384">sorting my apps on my iPhone&lt;/a>. That inspired me to look at it again.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1322/1*EgP3-JBm__wgpZqyHVBGyQ.png" alt="">
Screenshot of Tweet
&lt;p>For me personally, my smartphone is the device I spend most of my time with. I travel a lot and enjoy being able to do most of what I want to do with my thumb or voice and not having to open my SurfacePro. How I organize my apps depends largely on what habits I follow.&lt;/p>
&lt;h1 id="dock">&lt;strong>Dock&lt;/strong>&lt;/h1>
&lt;p>In the dock you can find Microsoft To-Do, twitter, LinkedIn and Outlook.&lt;/p>
&lt;p>In the morning I plan my day in bed with Microsoft To-Do and my Outlook calendar and then check what happened over the night on twitter (yes, I’m a bit addicted to twitter, I admit it!). Later I’ll take care of LinkedIn (still on the smartphone) and my &lt;a href="https://regarding365.com/how-to-stay-organized-with-your-mails-in-outlook-like-a-rockstar-7b23f8106114">emails (Outlook Desktop Client)&lt;/a>. I only do that once a day, click that link for how and why!&lt;/p>
&lt;p>Because I work with the My Day feature of Microsoft To-Do and really love to get involved in social media, these apps have to be permanently visible.&lt;/p>
&lt;h1 id="homescreen">Homescreen&lt;/h1>
&lt;p>After trying for a long time to organize my apps according to application areas (too many overlaps), automatically as suggested by ios (I don’t think again), not at all (I hate scrolling across multiple pages), I had the idea to create folders where only apps with a certain color belong to.&lt;/p>
&lt;p>At first it sounded strange, but I tried it too. I know that I learn very visually and that my image memory works well. So I always remember quite well what an app looks like, even if I can’t remember the name for less frequently used apps.&lt;/p>
&lt;p>Now that Darrell had made twitter aware of the screenshot of my iPhone, many others joined the conversation and also posted screenshots and explained their concepts:&lt;/p>
&lt;h1 id="other-concepts">Other concepts&lt;/h1>
&lt;h2 id="all-my-apps-except-the-4-in-the-tray-in-a-single-folder-by-brad-grissom">all my apps except the 4 in the tray in a single folder by Brad Grissom&lt;/h2>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/933/1*FiVOfgoWmdJ4DuZn7j62pw.png" alt="">
&lt;h2 id="swiping-down-and-typing-by-darrell-webster">Swiping down and Typing by Darrell Webster&lt;/h2>
&lt;p>Best advice ever! Just search for your apps by name!&lt;/p>
&lt;h2 id="alphanumerical-by-connorhttpstwittercomgxrneyme">Alphanumerical by &lt;a href="https://twitter.com/gxrneyme">Connor&lt;/a>&lt;/h2>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1130/1*zMKbVoBJ-L4wja4FzTWwgg.png" alt="">
&lt;p>Special place for the Chrome :-)&lt;/p>
&lt;h2 id="what-do-you-think">What do you think?&lt;/h2>
&lt;p>How do your organize the apps on your phone? Would you like to see a blogpost about how I deal with notifications? Any other suggestions?&lt;/p></description></item><item><title>11 + 5 reasons to fall in love with Microsoft To Do</title><link>https://m365princess.com/blogs/2019-02-03-11-5-reasons-to-fall-in-love/</link><pubDate>Sun, 03 Feb 2019 10:23:20 +0000</pubDate><guid>https://m365princess.com/blogs/2019-02-03-11-5-reasons-to-fall-in-love/</guid><description>&lt;p>Wow! Really?Falling in love with a tool? Are you nuts, Luise?&lt;/p>
&lt;p>Yes, maybe :-)&lt;/p>
&lt;p>I’m a very creative person. Not only because I draw #sketchnotes at conferences and show others how important #functionaldrawing is in business, but also because my mind never stops giving birth to new ideas, marrying ideas and inspiring others to step outside their comfort zone.&lt;/p>
&lt;p>The problem with this is that whenever I think about something new and a task “gets in the way” of my thinking process, I unfortunately stop innovating because I feel I have to take care of the task so that I don’t forget it.&lt;/p>
&lt;p>Analog To-Do lists and Outlook Tasks don’t really fit into my very mobile life. I am nearly everytime on the go, working on my iPhone and on my SurfacePro. As a former Wunderlist user I was very happy to discover Microsoft To-Do and to see it evolve. Here are my 10 points what I like so much about it.&lt;/p>
&lt;h1 id="1-its-free-or-part-of-your-o-365-subscription">1. It’s free or part of your O 365 subscription&lt;/h1>
&lt;p>Absolutely amazing, that everyone can use this! I love the idea of granting access to cool apps by making them free or incorporating them in a subscription.&lt;/p>
&lt;h1 id="2-its-mobile">2. It’s mobile!&lt;/h1>
&lt;p>You can use To-Do in your desktop client, webbrowser or in the mobile apps for iOs and Android. This really saves my life. The To-Do app is one of my 4 apps I have in the iOs dock:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1125/1*d3SWjoPcE-i_QbavrSvy1A@2x.jpeg" alt="">
Screenshot of Homescreen of an iPhone with Microsoft To-Do App in the dock
&lt;p>So I prepare myself for the day while still in bed — and before checking twitter, LinkedIn and email :-) So if you know me, you know what this means! To-Do rules my world!&lt;/p>
&lt;h1 id="3-its-organized">3. It’s organized!&lt;/h1>
&lt;p>Create lists to group your tasks, give them due days and reminders, attach files, links or notes to them. I love this clean interface, it calms me down, when times get busy or stressful or when I’m in a rush.&lt;/p>
&lt;h1 id="4-its-good-looking">4. It’s good-looking!&lt;/h1>
&lt;p>Pretty up your lists with Emojis and nice colors in the headers:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/930/1*ph95XhOC2Q4SwpqolXtmyw.png" alt="">
list of lists in Microsoft To-Do
&lt;h1 id="5-its-fast">5. It’s fast&lt;/h1>
&lt;p>Especially the mobile app works fast as fast can: No delays, syncs across the devices with no issues, even with bad wifi connection I am able to work! This is crucial to me as my days are usually full-packed and I don’t have the patience for apps that load to death.&lt;/p>
&lt;h1 id="6-its-focused">6. It’s focused&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2004/1*aNsTitb4QP0nrqBme8Fk1g.png" alt="">
every day is a fresh start
&lt;p>At the heart of Microsoft To-Do I found: MY DAY — it’s up to me to choose what I want to achieve today. Smart suggestions based on my lists help me, but it’s my decision what Im going to do with my day.&lt;/p>
&lt;p>This is more important than ever. I think most of us suffer from information overload and always feel the pressure to decide: What is important? What is urgent? What shall I really do today? Giving back people their self-sufficiency by letting them choose mindfully what this day is made for, is a great way to encourage them to regain control and take action while focusing and not getting destracted by all the items on other lists.&lt;/p>
&lt;p>I love this proactive attitude of To-Do.&lt;/p>
&lt;h1 id="7-its-easy-to-use">7. It’s easy to use&lt;/h1>
&lt;p>I believe the beauty of this app lies in its simplicity. No training needed, everyone gets it: Both my mother and my daughters are able to use it while feeling absolutely comfortable with it.&lt;/p>
&lt;h1 id="8-its-connected">8. It’s connected&lt;/h1>
&lt;p>Microsoft To-Do or Outlook? There is no OR — it’s already an AND! No need to decide which tool you prefer or if &lt;a href="https://medium.com/@LuiseFreese/10-signs-you-are-an-office-365-consultant-db38e5b3999a">it depends…&lt;/a>&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/4080/1*zNNeFa7iRTHQHQ6bN4_gpw.png" alt="">
Microsoft Outlook Tasks
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/4100/1*kMyrFfZg9PeDwuit8w-lqQ.png" alt="">
Microsoft To-Do
&lt;p>If you had the great idea to move emails to To-Do, &lt;a href="https://medium.com/@LuiseFreese/a-consultants-working-hours-tracker-build-in-microsoftflow-f054dc359748">Microsoft Flow&lt;/a> may help you:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1691/1*XMlZysSSRTpMxVtgRGdy2w.png" alt="">
Microsoft Flow
&lt;h1 id="9-its-caring-share-your-lists">9. It’s caring: share your lists&lt;/h1>
&lt;p>Although Microsoft To-Do is your personal task manager, you may share some lists with others, like you share some personal files in your OneDrive with colleagues as well. Inviting others to work on something jointly is always a good idea!&lt;/p>
&lt;hr>
&lt;h1 id="10-its-smart">10. It’s smart&lt;/h1>
&lt;p>Smart lists aren’t folders, but intelligent filter across your lists. Just have a look at important or planned tasks, regardless in which list they belong to.&lt;/p>
&lt;h1 id="11-the-pling">11. the “PLING”&lt;/h1>
&lt;p>Oh I cant tell you how adducted I am to that PLING! Even when I spot someone else using the To-Do app and I hear the PLING, I have to smile.&lt;/p>
&lt;p>What do you think? Does To-Do help you manage your tasks? Would love to hear your feedback :-)&lt;/p>
&lt;p>After I published this blogpost I found myself in a very nice conversation with colleagues who fell in love with Microsoft To-Do, too. So here are 3 additional wonderful capabilities we absolutely adore:&lt;/p>
&lt;h1 id="12-the-desktop-app-is-a-beauty">12. the desktop app is a beauty&lt;/h1>
&lt;p>— same simpicity like the iOs App — thanks to &lt;a href="https://medium.com/@grissombrad">Brad Grissom&lt;/a>&lt;/p>
&lt;h1 id="13-onenote-task-sync">13. OneNote Task Sync&lt;/h1>
&lt;p>Microsoft To-Do also syncs with tasks you create in OneNote (right-click, “Outlook Tasks” aka the red flag)! You can even create another Microsoft Flow with certain keywords, thanks for inspiration, &lt;a href="https://tracyvanderschyff.com/2018/03/08/action-ideas-from-onenote-with-outlook-flow-planner-and-teams/">Tracy van der Schyff&lt;/a>&lt;/p>
&lt;h1 id="14-siri-on-ios">14. Siri on iOs&lt;/h1>
&lt;p>Amazingly, you can just talk to Siri like this:”Siri, remember me write a blogpost and sketchnote why I love Microsoft To-Do”. This is the result:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1691/1*PJU0zFz7fJ9taqiKeco2Qg.png" alt="">
Microsoft To-Do iOs App
&lt;p>Just change the default task list of your iOs reminders! Learn mor in this &lt;a href="https://todosupport.helpshift.com/a/microsoft-to-do/?s=tips-tricks&amp;f=using-siri-and-reminders-with-microsoft-to-do-on-ios11-and-ios12&amp;p=web">How-To!&lt;/a> I love doing this!&lt;/p>
&lt;h1 id="15-live-tiles-in-windows10">15. Live-Tiles in Windows10&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1889/1*37EObtSi5KYSKIhQc8cBPg.png" alt="">
Screenshot Windows 10 Start Menu with Microsoft To-Do Live Tiles
&lt;p>Once you right-click a list in your Desktop app, you can easily add this list as a LIVE-tile to your Windows 10 start Menu. Live means, that you can see realtime information about your ToDos.&lt;/p>
&lt;h1 id="16-tags">16. #tags!&lt;/h1>
&lt;p>OMG! Although I really, really love that app, I just tried figured out how to use tags 2 days ago:&lt;/p>
&lt;p>Just write a word with # wherever you want in your lists, tasks, steps or notes. It instantly becomes a filter when you click on that word. Amazing!&lt;/p>
&lt;h1 id="dldr">Dl;dr?&lt;/h1>
&lt;p>If this was too long, so you didn’t read, here is my handy sketchnote:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2208/1*dnPOYWeTMuCOQI9BSqXDIA.png" alt=""></description></item><item><title>10 signs you are an Office 365 Consultant</title><link>https://m365princess.com/blogs/2019-01-29-10-signs-you-are-an-office-365-consultant/</link><pubDate>Tue, 29 Jan 2019 11:03:19 +0000</pubDate><guid>https://m365princess.com/blogs/2019-01-29-10-signs-you-are-an-office-365-consultant/</guid><description>&lt;p>I wrote this blogpost jointly with my colleague &lt;a href="https://andikrueger.wordpress.com/">MVP Andi Krueger,&lt;/a> originally published in &lt;a href="https://sway.office.com/sspCTXPf0mXqkkje?ref=Link">Microsoft Sway&lt;/a>&lt;/p>
&lt;h1 id="1-your-answer-to-nearly-everything-begins-with-it-depends">1. Your answer to nearly everything begins with… “It depends…”&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1200/0*0HP0cntNw7bKTNe5" alt="">
&lt;p>T-Shirt with Print: Single, Taken, Depends on who is asking&lt;/p>
&lt;p>Given the complexity, there are no simple answers. And although “it depends” is a cliché, it is simply an expression of the fact that as consultants we try hard to look at the customer with all his peculiarities before we answer.&lt;/p>
&lt;p>Your answer to nearly everything begins with… “It depends…”&lt;/p>
&lt;h1 id="2-you-work-in-more-tenants-than-you-can-remember-the-passwords-for">2. You work in more tenants than you can remember the passwords for.&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1920/0*DVewA43ycA9A2OoG" alt="">
Screen with Password
&lt;p>And although we know exactly which tool we would recommend for which application, we use quite different services that work independently of Office 365.&lt;/p>
&lt;p>You have to scroll through your Microsoft Authenticator App* and provide a keychain for RSA tokens.&lt;/p>
&lt;ul>
&lt;li>..and did create a folder on your smartphone’s home screen to have all the authenticator apps in one place.&lt;/li>
&lt;/ul>
&lt;h1 id="3-your-single-browser-is-google-chrome-because-you-need-different-profiles">3. Your single browser is Google Chrome because you need different profiles&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/959/0*N3tnpg3icBXAGElS" alt="">
Avatar Pictures
&lt;p>With Google Chrome you have the ability to create several user profiles and use them simultaneously. This is a great help and the only way to&lt;/p>
&lt;ul>
&lt;li>do demos in Office 365 that require more than one active user. * keep up to date in your customers tenants&lt;/li>
&lt;/ul>
&lt;p>Update: EDGE!&lt;/p>
&lt;h1 id="4-your-laptop-has-more-stickers-than-keys--">4. Your Laptop has more stickers than keys :-)&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1536/0*RYCLMk2Slr6JBoQq" alt="">
Laptop with lots of geeky stickers
&lt;p>In order to show our nerd status credibly and uniquely, we stick many geek stickers on our Laptops :-)&lt;/p>
&lt;h1 id="5-while-your-devices-are-slim-and-elegant-your-bag-with-cables-and-dongles-becomes-bigger-and-bigger">5. While your devices are slim and elegant, your bag with cables and dongles becomes bigger and bigger.&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1536/0*mU7U0f1JPbAcrSe3" alt="">
Bags with cables and dongles
&lt;p>With slim laptops a new challenge arises: You run out of ports. By now there are several standards on how to connect a laptop to a beamer: VGA, DisplayPort, DVI, HDMI, Thunderbold, Ethernet or via a ClickShare Dongle (which requires USB). You know there are even more but can’t list them.&lt;/p>
&lt;h1 id="6-you-have-at-minimum-three-personal-devices">6. You have at minimum three personal devices.&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/3900/0*LJO5HUZhMvkCARST" alt="">
an iPad and three iPhones
&lt;p>Seriously? Yes. Private, Business, Demo.&lt;/p>
&lt;h1 id="7-you-are-a-travel-pro">7. You are a travel pro.&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2123/0*lX3kskHkFVmgiTh6" alt="">
&lt;p>You are on the road a lot, because you need to see your customers and spend a lot of time at conferences, SharePoint Saturdays and other community events — this makes you a #TravelPro&lt;/p>
&lt;p>Covid 19 Update: You are a kick ass badass in delivering online sessions&lt;/p>
&lt;h1 id="8-recently-you-thought-about-cleaning-up-your-test-and-demo-environments">8. Recently you thought about cleaning up your test and demo environments…&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2556/0*2uIH6Busg3hco3OS" alt="">
a very messy room
&lt;p>You have a messy pile of Site Collections, test- users and documents with random text in it. You should #MarieKondo it :-)&lt;/p>
&lt;h1 id="9-your-parents-describe-your-job-using-the-words-does-something-with-computers-and-clouds">9. Your parents describe your job using the words “does something with computers and clouds”.&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/750/0*Ap085UKCTssYnXqf" alt="">
typing man looking into the camera
&lt;p>Since you started working with computers you are RESPONSIBLE for every computer, smartphone and anything related to internet connectivity issues and the configuration of any television.&lt;/p>
&lt;h1 id="10-you-are-a-wifi-and-lte-addict">10. You are a WiFi and LTE addict.&lt;/h1>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1800/0*EHDqQS-MlHeGXgRy" alt="">
Wifi logo
&lt;p>All cloud services rely on a reliable internet connection. There are times, there is no such thing like a gigabyte direct route into a Microsoft data center. In these times you start to honor your very own LTE or WiFi network.&lt;/p>
&lt;h1 id="what-about-you">What about you?&lt;/h1></description></item><item><title>10 things IT Pros should care about while designing a great digital work experience</title><link>https://m365princess.com/blogs/2019-01-29-10-things-it-pros-should-care-about-while-designing-a-great-digital-work-experience/</link><pubDate>Tue, 29 Jan 2019 07:32:37 +0000</pubDate><guid>https://m365princess.com/blogs/2019-01-29-10-things-it-pros-should-care-about-while-designing-a-great-digital-work-experience/</guid><description>&lt;h2 id="1-the-8020-rule">1. The 80/20 rule&lt;/h2>
&lt;p>&lt;a href="https://medium.com/p/14f23b760738/share/facebook?source=post_actions_header---------------------------">&lt;/a>&lt;/p>
&lt;p>As we always say: digital transformation is 80 % about people and 20 % about technology, we should walk the talk and think about our customers — our users. They deserve a workplace experience where they can evolve a creative, productive and sustainable way of work in order to create value, not to be occupied by managing their insane inbox the whole day.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/731/0*8IchmLlhIApXl_YM" alt="">
&lt;h2 id="2-dont-call-your-office365-launch-an-it-project">2. Don’t call your Office365 launch an IT project&lt;/h2>
&lt;p>Please be careful to call your launch of Office365 an “IT-Project”.&lt;/p>
&lt;p>First, it’s not a project at all; by definition, a project is a specific activity that has a beginning and an end and the result is a tangible and well defined product or service. When the organization moved it’s files to SharePoint and/ or mailboxes to Exchange Online, transformation isn’t done!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/929/0*MDKO_Se23ugbF9xB" alt="">
&lt;p>Second, if we want the benefits of Office365 for our organization, it’s more than the IT perspective of deploying tools &amp;amp; services and it’s way more than a few feature-fucking-trainings. We need to change our customers behavior and this is only possible when our users are adaptable and willing to change. But this say- yes-and-embrace-change-attitude doesn’t come up automatically.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1116/0*lg-WvhCJoI5l3Vpj" alt="">
&lt;h2 id="3-startwithwhy">3. #startwithwhy&lt;/h2>
&lt;p>Why does your organization want Office365 to be introduced? To save on licensing costs or because the support for an outdated version expires? Your boss told you so? Its very important to clarify which business objectives can be facilitated, accompanied or benefited by an Office365 introduction. Office365 is not a means to an end! Office365 is not just a small, unimportant IT-thing, but is intended to be used to support the company’s goals and help employees work more creatively, productively, faster, more networked and more mobile.&lt;/p>
&lt;h2 id="4-sponsors-at-c-level">4. Sponsors at C-Level&lt;/h2>
&lt;p>To be able to work out the deeper sense and actually make changes in the company, it requires the understanding and participation of the management level of the organization. It is therefore imperative to have one or more friendly allies in the management circle.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1164/0*23blNo13NRYZmFiI" alt="">
&lt;h2 id="5-communication-vs-contribution">5. Communication vs Contribution&lt;/h2>
&lt;p>Not only communicate at an early stage, but let your users contribute to those decisions which will affect them directly. Ask departments and teams what they need to get work done, how they work, with whom they work and what tools and business processes are involved. Take care about media discontinuities and whenever they say “email”. You will learn a lot about ways to work and I strongly encourage you to listen — you will have the chance to learn a lot about your company. You can’t skip that part, it’s crucial. If you don’t take your time for this, your users won’t get what they need.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/627/0*l-FcoKKHWugwQjI8" alt="">
&lt;h2 id="6-visualization">6. Visualization&lt;/h2>
&lt;p>Visualize (or let visualize) what you have heard in order to show the big picture, to make ideas visible, to ensure better decision making and to promote creativity. It’s a great way to encourage employees and its’s an inspiring way to communicate.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/737/0*OAbEVUM_oNdZeLjW" alt="">
&lt;h2 id="7-dont-focus-on-only-on-risks-see-chances">7. Don’t focus on only on risks, see chances&lt;/h2>
&lt;p>In a data migration, of course, there is the chance to create a better structure. Information retrieval is becoming more and more important. Displaying content in the right context makes it easier for knowledge workers to get their work done. With new technical features, we can not only digitize cumbersome, analogue processes (and then have cumbersome, digital processes), but have the ability to think processes completely new — which can have a big positive impact throughout the company.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/578/0*EZBjEcCZyXOkqnSE" alt="">
&lt;h2 id="8-clean-up-the-mess">8. Clean up the mess&lt;/h2>
&lt;p>So we have to clean up!. This means for the employees: mailboxes (Outlook wasn’t built as enterprise content tool), personal drives, USB sticks or cloud storage places in shadow IT solutions. Delete all that copies of copies of copies of outdated teammeeting material and archive those files your organization needs. Departmental and public drives must also be evaluated: what is useful, helpful content? What is ballast or just a copy? If you skip that part, SharePoint and Office365 won’t flourish. You all know that rule: Garbage in — garbage out.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1247/0*MRHWh23Mew-oaaob" alt="">
&lt;h2 id="9-focus-on-adoption">9. Focus on adoption&lt;/h2>
&lt;p>Design the workplace experience your users need and deserve, but do not focus on tools, focus on adoption. Increase adoption with professional coaching and great self-learning material. Give users both ability and opportunity to exchange their views with others.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/756/0*VxAnFs22aC30n8tb" alt="">
&lt;h2 id="10-empower-every-person">10. Empower every person&lt;/h2>
&lt;p>As said above, users (and IT Pros) need to evolve, so stay connected and don’t stop to learn and grow together. And don’t even think about naming users with limited IT skills “dumbest assumable user” or something like that. Respect every person, empower every person! You need people in your team who don’t suck at things you suck at .&lt;/p></description></item><item><title>How to build a working hours tracker</title><link>https://m365princess.com/blogs/2019-01-22-how-to-build-a-working-hours-tracker/</link><pubDate>Tue, 22 Jan 2019 10:09:00 +0000</pubDate><guid>https://m365princess.com/blogs/2019-01-22-how-to-build-a-working-hours-tracker/</guid><description>&lt;p>After I wrote &lt;a href="https://medium.com/@LuiseFreese/what-happened-to-me-when-i-created-my-very-first-microsoftflow-61d2dd923660">the story about my very first MicrosoftFlow&lt;/a> I was curious if I could do another one. So I don’t wanted to create a new flow, but to improve the one I created before. What I want to achieve: A flow button, from which different freelancers can submit their working hours for different projects in a SharePoint list which accessed by HQ. Bonus: I want to have a minimum troubleshooting feature :-).&lt;/p>
&lt;p>Step 1: I started with a list in SharePoint and 5 columns: Project Name, Hours spent, Today’s date,problem, and User.&lt;/p>
&lt;p>Step 2: I chose the manual trigger flow button:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1344/1*fcBvoNptVv8L-e0DSL7CLQ.png" alt="">
Microsoft Flow trigger manual
&lt;p>For the Project Name, I decided to add a drop-down list of options for the different projects and called them Project A, Project B, Project C. Hours Spent is just a text field, Problem is a yes/no field and User is just the Users name so we get a better overview who submitted which row.&lt;/p>
&lt;p>This was just easy!&lt;/p>
&lt;p>Step 3: I chose Create item in SharePoint, and connected to my SharePoint site and to the list I just created:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1346/1*ApIpFMsi0HWRtPXhCzmgUg.png" alt="">
Microsoft Flow Create Item in SharePoint action
&lt;p>I The Fields are chosen from the Add Dynamic Content menu so that my inputs from the mobile button fills my SharePoint list. Magic :-)&lt;/p>
&lt;p>Step 4: I want a mobile notification and further action depending on if a problem was submitted or not for two reasons: On the one hand I wanted to give my users a confirmation, that this button works and on the other hand I want to provide help if users need it.&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1361/1*RXVkzFXHoy4-_E5IJNapzA.png" alt="">
Microsoft Flow Condition
&lt;p>Step 5: notification for NO problem:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1436/1*_d_SIaPi3LEH6bYtxg-omw.png" alt="">
Microsoft Flow send mobile notification action
&lt;p>To customize this a little bit, I used the User name and some other details for the mobile notification. I dont need links or link labels here.&lt;/p>
&lt;p>Step 6: The notification for YES problem is a bit more extensive plus I want to send an email to my user:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1392/1*ZnWFO906sHm0pbraGjbjdg.png" alt="">
Microsoft Flow send notification and email actions
&lt;p>Step 7: Back to my SharePoint list I want to format the problem column:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1136/1*IAhnKwV_KeDWsmNfHQazsQ.png" alt="">
SharePoint format column sidebar
&lt;p>so I get a clear overview which Projects need my attention! I sorted from yes to no and I’m done!&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/2810/1*fEG4HISfEVf6j8NyMARnNg.png" alt="">
SharePoint list with a formatted column
&lt;p>Bam! So if you are interested: this is the whole flow:&lt;/p>
&lt;img loading="lazy" class="w-100" src="https://miro.medium.com/max/1998/1*cfWgqvSDVNH3LkhFidVaRQ.png" alt="">
Microsoft Flow flow</description></item><item><title>Search Result</title><link>https://m365princess.com/search/</link><pubDate>Mon, 24 Sep 2018 11:07:10 +0600</pubDate><guid>https://m365princess.com/search/</guid><description/></item><item><title>Azure &amp; Power Platform Architect</title><link>https://m365princess.com/work-with-me/azure-power-platform-architect/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/work-with-me/azure-power-platform-architect/</guid><description>&lt;p>Need help in Azure &amp;amp; Power Platform?&lt;/p>
&lt;ul>
&lt;li>Getting started securely&lt;/li>
&lt;li>Building and shipping solutions that are performant, secure,
accessible, and intuitive to use&lt;/li>
&lt;li>Understanding and making the best out of licensing&lt;/li>
&lt;li>Extending low-code with code-first approaches when needed&lt;/li>
&lt;/ul>
&lt;h2 id="am-i-the-right-consultant-for-your-organization">Am I the right consultant for your organization?&lt;/h2>
&lt;ul>
&lt;li>I’m not your usual consultant.&lt;/li>
&lt;li>If you want to work with me, we will work radically -
meaning we will always get to the root of the causes instead of just treating symptoms.&lt;/li>
&lt;li>I am accurate and creative, a serial critical thinker and professional learner&lt;/li>
&lt;li>A princess, punk at heart and advocate for doing the right things in the right way. Half-baked solutions aren’t my thing&lt;/li>
&lt;li>I do what I say and say what I think&lt;/li>
&lt;/ul>
&lt;p>Sounds good?&lt;/p>
&lt;div class="button-container">
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn-cta">Get in touch&lt;/a>
&lt;/div>
&lt;style>
.btn-cta {
background-color: #ff69b4;
color: black;
font-weight: bold;
padding: 10px 20px;
text-transform: uppercase;
cursor: pointer;
border: none;
text-decoration: none;
margin-top: 10px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
&lt;/style></description></item><item><title>Contact Me</title><link>https://m365princess.com/contact/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/contact/</guid><description>&lt;blockquote class="wp-block-quote is-style-large">
&lt;p>
&lt;span class="has-inline-color has-vivid-red-color">You want to get me involved in one of your projects? Please feel free to send me an email via this contact form.&lt;/span>
&lt;/p>
&lt;/blockquote>
&lt;p>&lt;span class="has-inline-color has-very-dark-gray-color">If you want to connect with me more informal, follow me on social media, my main focus is on &lt;a href="https://www.linkedin.com/in/LuiseFreese">LinkedIn&lt;/a>. &lt;/span>&lt;/p></description></item><item><title>Copilot Readiness – Preparing your data and environments for AI success</title><link>https://m365princess.com/work-with-me/copilot-readyness/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/work-with-me/copilot-readyness/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Are you ready to make the most out of AI-powered features like Microsoft Copilot? This service is all about getting your data, permissions, and search capabilities in order so that AI tools can truly add value to your business without any surprises.&lt;/p>
&lt;h2 id="key-focus">Key Focus&lt;/h2>
&lt;ul>
&lt;li>Cleaning and structuring data for optimal AI use&lt;/li>
&lt;li>Permissions review: Ensuring that the right people have the right access&lt;/li>
&lt;li>Making search work: Optimizing your environment for accurate and relevant AI search results&lt;/li>
&lt;li>Integrating with existing Microsoft 365 services for seamless AI adoption&lt;/li>
&lt;/ul>
&lt;p>A prioritized action plan to get your environment AI-ready&lt;/p>
&lt;h2 id="why-choose-me">Why Choose Me?&lt;/h2>
&lt;p>As an &lt;strong>Independent Azure &amp;amp; Power Platform Architect&lt;/strong>, I specialize in designing and implementing robust cloud solutions that align with your business goals. I provide tailored strategies that drive efficiency and innovation. With extensive experience in both Azure and Power Platform, I bring a wealth of knowledge and hands-on expertise to every project. My focus is on delivering value and ensuring that your solutions are not just effective, but also sustainable in the long term.&lt;/p>
&lt;div class="button-container">
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn-cta">Get in touch&lt;/a>
&lt;/div>
&lt;style>
.btn-cta {
background-color: #ff69b4;
color: black;
font-weight: bold;
padding: 10px 20px;
text-transform: uppercase;
cursor: pointer;
border: none;
text-decoration: none;
margin-top: 10px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
&lt;/style></description></item><item><title>DevOps jumpstart for Azure &amp; Power Platform</title><link>https://m365princess.com/work-with-me/devops-jumpstart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/work-with-me/devops-jumpstart/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>This workshop introduces your team to a streamlined DevOps approach tailored specifically for Azure and Power Platform projects. We’ll cover how to use Azure DevOps tools to manage Power Platform projects, automate deployment pipelines, and maintain high-quality standards—without drowning in unnecessary complexity.&lt;/p>
&lt;h2 id="key-focus">Key Focus&lt;/h2>
&lt;ul>
&lt;li>Setting up a basic DevOps pipeline for Power Platform and Azure&lt;/li>
&lt;li>Source control and versioning for low-code and code-first projects&lt;/li>
&lt;li>CI/CD (Continuous Integration/Continuous Deployment) best practices&lt;/li>
&lt;li>Automated testing and quality assurance with Azure DevOps&lt;/li>
&lt;/ul>
&lt;h2 id="why-choose-me">Why Choose Me?&lt;/h2>
&lt;p>As an &lt;strong>Independent Azure &amp;amp; Power Platform Architect&lt;/strong>, I specialize in designing and implementing robust cloud solutions that align with your business goals. I provide tailored strategies that drive efficiency and innovation. With extensive experience in both Azure and Power Platform, I bring a wealth of knowledge and hands-on expertise to every project. My focus is on delivering value and ensuring that your solutions are not just effective, but also sustainable in the long term.&lt;/p>
&lt;div class="button-container">
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn-cta">Get in touch&lt;/a>
&lt;/div>
&lt;style>
.btn-cta {
background-color: #ff69b4;
color: black;
font-weight: bold;
padding: 10px 20px;
text-transform: uppercase;
cursor: pointer;
border: none;
text-decoration: none;
margin-top: 10px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
&lt;/style></description></item><item><title>Root cause accelerator – focused problem-solving sessions</title><link>https://m365princess.com/work-with-me/root-cause/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/work-with-me/root-cause/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Stop fighting fires and start fixing the underlying issues. This intensive approach will help you and your team get to the core of a problem—whether it&amp;rsquo;s a performance bottleneck, a deployment challenge, or security concerns in Azure or Power Platform. We don’t just diagnose; we develop a sustainable solution that fixes the root cause.&lt;/p>
&lt;h2 id="key-focus">Key Focus&lt;/h2>
&lt;ul>
&lt;li>Root cause analysis of a specific, business-critical issue&lt;/li>
&lt;li>Identifying gaps in security, performance, or process that need addressing&lt;/li>
&lt;li>Crafting a targeted solution and developing an action plan&lt;/li>
&lt;li>Guidance on long-term monitoring and maintenance&lt;/li>
&lt;/ul>
&lt;h2 id="why-choose-me">Why Choose Me?&lt;/h2>
&lt;p>As an &lt;strong>Independent Azure &amp;amp; Power Platform Architect&lt;/strong>, I specialize in designing and implementing robust cloud solutions that align with your business goals. I provide tailored strategies that drive efficiency and innovation. With extensive experience in both Azure and Power Platform, I bring a wealth of knowledge and hands-on expertise to every project. My focus is on delivering value and ensuring that your solutions are not just effective, but also sustainable in the long term.&lt;/p>
&lt;div class="button-container">
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn-cta">Get in touch&lt;/a>
&lt;/div>
&lt;style>
.btn-cta {
background-color: #ff69b4;
color: black;
font-weight: bold;
padding: 10px 20px;
text-transform: uppercase;
cursor: pointer;
border: none;
text-decoration: none;
margin-top: 10px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
&lt;/style></description></item><item><title>Secure foundations workshop – getting started with Power Platform without the fluff</title><link>https://m365princess.com/work-with-me/secure-foundations/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/work-with-me/secure-foundations/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Kickstart your journey with Power Platform securely and efficiently. This workshop isn’t about covering everything - it&amp;rsquo;s about giving your team the essential, secure foundation to build and deploy solutions confidently. We&amp;rsquo;ll focus on governance, performance, and security while eliminating the noise.&lt;/p>
&lt;h2 id="key-focus">Key Focus&lt;/h2>
&lt;ul>
&lt;li>Establishing a secure and compliant environment for Power Platform&lt;/li>
&lt;li>Governance best practices: user permissions, environment controls, and data loss prevention&lt;/li>
&lt;li>Core components for sustainable performance and scalable architecture&lt;/li>
&lt;li>Identifying initial use cases that align with your business goals&lt;/li>
&lt;/ul>
&lt;h2 id="why-choose-me">Why Choose Me?&lt;/h2>
&lt;p>As an &lt;strong>Independent Azure &amp;amp; Power Platform Architect&lt;/strong>, I specialize in designing and implementing robust cloud solutions that align with your business goals. I provide tailored strategies that drive efficiency and innovation. With extensive experience in both Azure and Power Platform, I bring a wealth of knowledge and hands-on expertise to every project. My focus is on delivering value and ensuring that your solutions are not just effective, but also sustainable in the long term.&lt;/p>
&lt;div class="button-container">
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn-cta">Get in touch&lt;/a>
&lt;/div>
&lt;style>
.btn-cta {
background-color: #ff69b4;
color: black;
font-weight: bold;
padding: 10px 20px;
text-transform: uppercase;
cursor: pointer;
border: none;
text-decoration: none;
margin-top: 10px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
&lt;/style></description></item><item><title>Smart licensing optimization – Power Platform cost management &amp; planning</title><link>https://m365princess.com/work-with-me/licensing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://m365princess.com/work-with-me/licensing/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>The Power Platform can get expensive if not managed correctly. This session is all about cutting through the complexity of licensing options, ensuring your organization is getting the most value from your current plans, and identifying opportunities for smarter spending aligned with your growth.&lt;/p>
&lt;h2 id="key-focus">Key Focus&lt;/h2>
&lt;ul>
&lt;li>Analysis of your current Power Platform usage&lt;/li>
&lt;li>Cost-effective recommendations to maximize existing licenses&lt;/li>
&lt;li>Planning for future growth to avoid unexpected costs&lt;/li>
&lt;li>Ensuring that your environment scales efficiently and securely&lt;/li>
&lt;/ul>
&lt;h2 id="why-choose-me">Why Choose Me?&lt;/h2>
&lt;p>As an &lt;strong>Independent Azure &amp;amp; Power Platform Architect&lt;/strong>, I specialize in designing and implementing robust cloud solutions that align with your business goals. I provide tailored strategies that drive efficiency and innovation. With extensive experience in both Azure and Power Platform, I bring a wealth of knowledge and hands-on expertise to every project. My focus is on delivering value and ensuring that your solutions are not just effective, but also sustainable in the long term.&lt;/p>
&lt;div class="button-container">
&lt;a href="mailto:luise@raeuberleiterin.de" class="btn-cta">Get in touch&lt;/a>
&lt;/div>
&lt;style>
.btn-cta {
background-color: #ff69b4;
color: black;
font-weight: bold;
padding: 10px 20px;
text-transform: uppercase;
cursor: pointer;
border: none;
text-decoration: none;
margin-top: 10px;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
&lt;/style></description></item></channel></rss>