<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Anoop on Medium]]></title>
        <description><![CDATA[Stories by Anoop on Medium]]></description>
        <link>https://medium.com/@anoopt?source=rss-3cb1c1004f96------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*dz5vnjn5ta7L2Ar2zq-k-w.jpeg</url>
            <title>Stories by Anoop on Medium</title>
            <link>https://medium.com/@anoopt?source=rss-3cb1c1004f96------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 05:21:18 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@anoopt/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Exclude SharePoint online modern page templates from search results]]></title>
            <link>https://anoopt.medium.com/exclude-sharepoint-online-modern-page-templates-from-search-results-1a3a216ab2a3?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/1a3a216ab2a3</guid>
            <category><![CDATA[sharepoint]]></category>
            <category><![CDATA[sitepageflags]]></category>
            <category><![CDATA[page-template]]></category>
            <category><![CDATA[search]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Mon, 11 Oct 2021 12:29:54 GMT</pubDate>
            <atom:updated>2021-10-11T12:29:54.584Z</atom:updated>
            <content:encoded><![CDATA[<p>In this short blog post we will look at how to exclude the modern page templates from search results in SharePoint online.</p><h3>Introduction</h3><p><a href="https://support.microsoft.com/en-us/office/page-templates-in-sharepoint-faa92408-0c84-4e3d-8460-3c28065e7873">Page template</a> is a great feature in SharePoint online and helps users save a lot of time. In some scenarios, for example — rolling up site pages in a web part, we might have to exclude these page templates as it wouldn’t make sense to display them in such scenarios.</p><h3>Options to exclude page templates</h3><h4>SharePoint REST API</h4><p>If we are using SharePoint REST API to get the site pages, then we can add the filter OData__SPSitePageFlags ne &quot;Template&quot; to our query. The details of this property is explained in detail by Beau Cameron in this <a href="https://beaucameron.com/2019/05/13/exploring-modern-page-templates-in-sharepoint-online-with-rest/">blog post</a>.</p><h4>SharePoint search</h4><p>If we are using SharePoint search to get the site pages, then we have a couple of options to exclude page templates</p><h4><strong>Option 1 — Excluding the “templates” folder (not preferred)</strong></h4><p>With this option we can exclude the “path” of the templates folder in our search query. This can be done by adding -Path:&quot;https://yourtenant.sharepoint.com/sites/yoursite/sitepages/templates&quot; . This will exclude all the pages under the “templates” folder in “Site Pages” of a given site.</p><h4><strong>Option 2 — Using the “SitePageFlags” managed property (preferred)</strong></h4><p>As mentioned in Beau’s blog post there is an internal column called “OData__SPSitePageFlags” which will have the value as “Template” for a page template. The corresponding managed property for this column is “SitePageFlags”. By adding -SitePageFlags:&quot;Template&quot; to our search query, we can exclude all the page templates in our search results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/438/1*d1RVoVTMDz6ZcT8JzCFffg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1a3a216ab2a3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub Actions + Power Automate + Teams]]></title>
            <link>https://anoopt.medium.com/github-actions-power-automate-teams-a46d7b643c7f?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/a46d7b643c7f</guid>
            <category><![CDATA[microsoft-teams]]></category>
            <category><![CDATA[action]]></category>
            <category><![CDATA[microsoft-power-automate]]></category>
            <category><![CDATA[github]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Wed, 11 Aug 2021 17:02:09 GMT</pubDate>
            <atom:updated>2021-08-11T17:12:46.593Z</atom:updated>
            <content:encoded><![CDATA[<p>In this blog post we will look at how we can integrate GitHub Actions with Power Automate and Microsoft Teams. We will look at an example in which using GitHub Actions we post all the open issues (that are marked as important) on to a Teams channel and then send a mobile notification about the same. The result would be like the image shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/921/1*EEyv6bN53mFPHKEFWMBHFQ.png" /></figure><h4>GitHub Actions</h4><p>GitHub Actions can be thought of as a workflow that runs when certain event occurs in GitHub. An event might be for e.g. someone pushes code to a repository, or someone creates an issue etc. When such events occur, we can run some tasks or actions — e.g. sending an email or deploying code to a site etc.</p><p>If GitHub Actions are new to you, please have a quick look at <a href="https://www.lynda.com/GitHub-tutorials/What-GitHub-Actions/5002838/5017855-4.html">this short video</a> to understand what GitHub actions are and then have a look at <a href="https://www.youtube.com/watch?v=E1OunoCyuhY">this video by GitHub</a> to understand more details.</p><p>In this post we will create a workflow that will run on a schedule. The action that it will perform are:</p><ol><li>Get all the issues in the repository that are marked as important</li><li>Pass those issues to a Power Automate flow</li><li>The flow then posts a message in a Teams channel and sends a notification</li></ol><p>The diagram below shows the overall process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U-Y7FjW7w-V1YTDvz_ka3Q.png" /></figure><h4>Power Automate</h4><p>Let’s start by creating a flow. Note: We use a trigger that requires a premium license. So please make sure a right license has been purchased/subscribed to. (If premium features of Flow need to be avoided then we can think of using SharePoint list item triggers.)</p><p>You can import the <a href="https://github.com/anoopt/get-issues-http-post/blob/main/assets/Post_GitHub_Issues_to_Teams_Channel.zip?raw=true">flow provided here</a> so that everything will be setup. Once imported, the flow should look as shown below (The flow uses simple steps and is self-explanatory)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/489/1*GhY0pLg9fbRm4vpNHlA_sw.png" /></figure><p>There are 2 items that need to be done after importing:</p><ol><li>Edit the “Post issues in a Team channel” step in the flow to point to the required Team and channel.</li><li>Save the flow so that it generates the HTTP POST URL in the first step. Please copy this URL as it will be needed later.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/511/1*fw2z7mlXNCOZZIeGWiN3WQ.png" /></figure><h4>GitHub workflow</h4><p>Now we need to create a workflow in GitHub which will send the required issues to the flow we created above.</p><p>To do that, navigate to the GitHub repository from which you need to send weekly report of the issues. Click on “Actions” tab. GitHub will recognize project type and will show us some predefined workflows which can be used. However, we will not use a predefined one and instead start from scratch. So, click on “Set up a workflow yourself”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/616/1*aEaXKbRgzr-aq3axejQ2cQ.png" /></figure><p>This will open a YAML file with some code in it. Delete all the code and enter the following</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5e44253e1b8def6316b24df56a9b33ef/href">https://medium.com/media/5e44253e1b8def6316b24df56a9b33ef/href</a></iframe><p>This is a YAML file in which we do the following:</p><ol><li>Run the workflow on every Monday at 9AM</li><li>While running</li></ol><ul><li>Use latest version of Ubuntu OS</li><li>Calls a GitHub action (anoopt/get-issues-and-call-flow) which will</li><li>Get all the issues that are open and have the label as ‘bug’</li><li>Build an object to hold all the issues and some other information</li><li>Post all the issues to the flow we created earlier</li></ul><p>We need to update the following lines in the file as per our requirement:</p><ol><li>Line number 16 — At the moment this says ‘bug’ which means we get the issues that are labelled with ‘bug’. If a different label is needed, please change this accordingly.</li><li>Line number 18 — Please enter the flow HTTP POST URL that was copied in the previous section.</li></ol><p>Commit this file to save the changes.</p><h4>Performing a quick test</h4><p>If a quick test needs to be performed, then please we need to update the trigger in the YAML file. To do that, please comment line number 4 and 6 and write “push” instead — which means every time a file is pushed, the workflow runs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/602/1*CGn6v4GlmSFgDm1xXLXKEg.png" /></figure><p>Save that file and then click on “Actions” tab, we should the see the workflow running</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/943/1*TQRDCk_EWmNZS5KQ98WGow.png" /></figure><p>Once the run is complete, make sure that the message is posted in Teams and a notification is received.</p><p>If all is ok, please revert the workflow file (YAML) to the way it was earlier i.e. removing the push trigger and uncommenting lines 4 and 6.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/602/1*U63KUaigorLLJTLrBotNQg.png" /></figure><h4>Other options for workflow</h4><p>In the workflow above we are using a GitHub action which gets all the required issues and posts some JSON data (in a particular format) to a Flow. If this feels a bit restricted i.e. if the JSON format needs to be updated or say if the Flow can be skipped entirely then the following options can be looked at.</p><p><strong>Writing custom script in the workflow</strong></p><p>Instead of calling the action in the workflow, we can write our script which gets the required issues and then we build the JSON in the format we need. After that we can post that JSON to Flow. One such <a href="https://gist.github.com/anoopt/6dd7765ca55ab3053e0025c26179eba6">sample workflow can be found here</a>.</p><p><strong>Creating an Action using Graph API</strong></p><p>The action used in the workflow (<a href="https://github.com/anoopt/get-issues-http-post">anoopt/get-issues-http-post</a>) calls a Flow using the flow URL. Instead of that, we can think of creating a new Javascript action or Typescript action which uses Graph API to post a message to Teams. So, the idea would be to get all the required issues first like “anoopt/get-issues-and-call-flow” does and then post that JSON to Teams using Graph API. Once that action is built, we can use that new action in the workflow.</p><p><strong>Creating an Action using Teams API</strong></p><p>This option is similar to the previous one except that we use Teams API instead of Graph API.</p><p><strong>Using Logic App</strong></p><p>Instead of a the Power Automate flow, a logic app can be used which performs similar steps.</p><p><strong>Using adaptive cards</strong></p><p>The Power Automate flow can be edited and actions related to adaptive cards can be added to post to Teams.</p><h4><strong>Details of the GitHub Action</strong></h4><p>The code for the <a href="https://github.com/anoopt/get-issues-http-post">get-issues-http-post</a> GitHub Action is written in Typescript. The important file in the project is <a href="https://github.com/anoopt/get-issues-http-post/blob/main/src/main.ts">main.ts</a> . This file has code to get issues of the repository where the workflow runs. This is done with the help of octokit client as shown in the code below.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9435b97e511f1ef2017f6eb852cf8929/href">https://medium.com/media/9435b97e511f1ef2017f6eb852cf8929/href</a></iframe><p>Once the issues are obtained, we extract only the required properties, form an array of the same and send it to the HTTP endpoint using node-fetch.</p><h4>Summary</h4><p>GitHub Actions are very powerful and provide a fantastic ability to automate lot of tasks. In this post we have seen how we can communicate between GitHub and Teams via Actions and Power Automate. Communicating between GitHub and Microsoft 365 becomes much simpler as soon as Actions and Power Automate come into picture. Hope in future more and more workflows and actions get developed so that more and more tasks can be automated.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a46d7b643c7f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub Actions — Create an Event with Microsoft Graph using MSAL]]></title>
            <link>https://anoopt.medium.com/github-actions-create-an-event-with-microsoft-graph-using-msal-ee7aa7b49dc7?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/ee7aa7b49dc7</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[microsoft]]></category>
            <category><![CDATA[msal]]></category>
            <category><![CDATA[graph]]></category>
            <category><![CDATA[action]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Wed, 07 Jul 2021 17:06:53 GMT</pubDate>
            <atom:updated>2021-07-09T14:40:11.233Z</atom:updated>
            <content:encoded><![CDATA[<h3>GitHub Actions — Create an Event with Microsoft Graph using MSAL</h3><p>In this post, we look at a <a href="https://github.com/marketplace/actions/create-an-event-with-microsoft-graph-using-msal">GitHub Action</a> that creates an event in a user’s Outlook calendar with Microsoft Graph using <a href="https://www.npmjs.com/package/@azure/msal-node">MSAL for Node</a>.</p><p>In a <a href="https://medium.com/swlh/github-actions-create-an-event-using-microsoft-graph-3c39f5839df2">related post</a> published previously, I have explained how we can do the same using PnP JS. In this post we will be modifying that approach to use MSAL for Node instead of PnP JS.</p><h3>Example scenario</h3><p>An example usage might be — when a pull request is submitted in the MAIN branch on the GitHub repository, an event is created in the repository owner’s calendar on the next day which contains the details of the pull request. The owner will get a reminder on the next day at the specified time and then the owner can review the pull request and can the required take action.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1smv9vN7t2EN7Nkq.png" /></figure><h3>Setup</h3><p>Firstly, if GitHub Actions are new to you, please have a quick look at <a href="https://www.lynda.com/GitHub-tutorials/What-GitHub-Actions/5002838/5017855-4.html">this short video</a> to understand what GitHub actions are and then have a look at <a href="https://www.youtube.com/watch?v=E1OunoCyuhY">this video by GitHub </a>to understand more details.</p><h3>Pre-requisites for using the action</h3><h4><strong>App registration</strong></h4><p>Since the GitHub Action explained in this post uses Microsoft Graph, we need to create an app registration in Azure AD so that we can communicate with Office 365. To do that please follow the instructions mentioned in <a href="https://docs.microsoft.com/en-us/graph/auth-register-app-v2">this video by Microsoft</a>. Provide the app “Calendars.ReadWrite” permissions and create a client secret for that app.</p><h4><strong>Repository secrets</strong></h4><p>In the GitHub repository where this action will be used, create the following 3 <a href="https://docs.github.com/en/actions/reference/encrypted-secrets">secrets</a> by navigating to “<strong>Settings &gt; Secrets</strong>”</p><ol><li>CLIENT_ID : The Id of the app registration created above</li><li>CLIENT_SECRET : A secret of the app registration created above.</li><li>TENANT_ID : The Id of your Microsoft 365 tenant.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/936/1*TMAwZ0m2XzCMnEHh2wbQHQ.png" /></figure><h4>Using the action in workflow</h4><p>In the GitHub repository, click on “Actions”. GitHub will recognize project type and will show us some predefined workflows which can be used. However, we will not use a predefined one and instead start from scratch. So click on “Set up a workflow yourself”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/615/1*Z9n3EKpuwroCmZaSP9RbTw.png" /></figure><p>This will open a YAML file with some code in it. Delete all of and enter the following</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/92d6bf6c86afc8265abfc70114e543e1/href">https://medium.com/media/92d6bf6c86afc8265abfc70114e543e1/href</a></iframe><p>When there is a pull request, the above workflow will create an event for user ‘<a href="mailto:user@contoso.onmicrosoft.com">user@contoso.onmicrosoft.com</a>’ on the next day from 12:00 to 13:00 (automatic as no start or end is specified).</p><p>If more information about the event is needed then, all of the information attached to an event is available in the github.event variable. We can use github.event object in the required steps — e.g. in the body of the calendar event github.event.compare .</p><p>More details on the usage of this action can be found <a href="https://github.com/marketplace/actions/create-an-event-with-microsoft-graph-using-msal">here</a>.</p><h3>Repo related to the action</h3><p>The entire code of the action can be <a href="https://github.com/anoopt/ms-graph-create-event-msal">found here</a>. Please feel free to fork it and make changes accordingly.</p><p>The process-related to developing an Action (Node js) is similar to developing any Node js project. Once we are happy the code does what it is should do, we add some files related to make the code into a GitHub action. Instead of me rewriting the whole process, <a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-a-javascript-action">here is an article from GitHub</a> which explains on how to create a simple JavaScript action.</p><h4>Packages</h4><p>The same concept was used to create the “Create an event with Microsoft Graph using MSAL” action. Once the base setup is ready, we install the MSAL for node package</p><p>npm install @azure/msal-node</p><p>We also install</p><ol><li>The package related to Microsoft Graph so that we can use the Graph client.</li><li>date-fns package to help with the date related functionality</li></ol><p>npm install @microsoft/microsoft-graph-client isomorphic-fetch date-fns</p><p>To make use of the code related to actions i.e. say getting inputs or providing outputs we install the ‘@actions/core’ library</p><p>npm install @actions/core</p><p>There are more such packages provided by the GitHub team which can be found <a href="https://github.com/actions/toolkit#packages">here</a> which will help in creating the code for Actions.</p><blockquote>We can instead download the <a href="https://github.com/anoopt/ms-graph-create-event-msal">repository</a> and install all the packages by running npm i .</blockquote><h4>Code</h4><p>The repo contains 3 typescript files.</p><ol><li>main.ts — Gets the inputs from the workflow and passes them to helper methods in other files.</li><li>graph.ts — Has methods to create an event in a person’s calendar with the help of Graph client.</li><li>auth.ts — Makes use of MSAL to get an access token which will then be used by methods in graph.ts</li></ol><p>In main.ts we get the input variables using the example code shown below:</p><p>const subject = core.getInput(&#39;subject&#39;);</p><p>We do the same for all the inputs.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/62bfb764858752b6db66e4d6f142c86d/href">https://medium.com/media/62bfb764858752b6db66e4d6f142c86d/href</a></iframe><p>Once we have all the inputs, we perform the <a href="https://github.com/anoopt/ms-graph-create-event-msal/blob/main/src/auth.ts#L7">authentication </a>with the help of client id, client secret and the tenant id. After that we compose the event object and pass it to the <a href="https://github.com/anoopt/ms-graph-create-event-msal/blob/main/src/graph.ts#L22">Graph API</a> /users/{username}/calendars/events .</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/42670b1ba25f1c30b4e15a5a41fba758/href">https://medium.com/media/42670b1ba25f1c30b4e15a5a41fba758/href</a></iframe><h3>Summary</h3><p>What we have seen above is a simple action that uses the power of MSAL and Microsoft Graph to create an event. With Microsoft Graph there are endless possibilities that we can think of developing.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee7aa7b49dc7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[VS code + SSH + Azure VM setup for remote SPFx development]]></title>
            <link>https://anoopt.medium.com/vs-code-ssh-azure-vm-setup-for-spfx-development-a897b9ee5850?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/a897b9ee5850</guid>
            <category><![CDATA[ssh]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[spfx]]></category>
            <category><![CDATA[azure-vm]]></category>
            <category><![CDATA[vscode]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Mon, 21 Jun 2021 17:25:26 GMT</pubDate>
            <atom:updated>2021-07-14T10:53:30.376Z</atom:updated>
            <content:encoded><![CDATA[<p>In this blog post we will have a look at one of the setup options for remote SPFx related development which involves connecting to an Azure VM using the VS code “Remote-SSH” extension.</p><p><em>Note</em>: The details that we will see below are the ones that I tend to follow for remote development. The approach mentioned is not the only approach. There are several other setup options that can be utilized for remote development.</p><h4>Setup</h4><p>In this setup we will be using a Linux based Azure VM. The access/interaction with the VM <strong>will not be</strong> via GUI. Instead it will all be via commands. We will be installing node js on the VM and all our SPFx code will reside on the VM. For developing SPFx solutions we need to access port 4321 on the VM. This is taken care by VS code remote-SSH with the help of port forwarding.</p><p>In summary, all the development is done on a remote machine, we do not access the UI of that machine, we consume only the files on that machine and with VS code the experience will be as if we were writing code on our local machine.</p><p>The setup in brief consists of the following:</p><ol><li>VM in Azure with SSH configured.</li><li>VS code Remote-SSH extension to connect to the VM.</li><li>Commands to execute to get started with SPFx development.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/971/1*z-wFAOMNdv0yqb631NHLAg.png" /></figure><h4>Azure VM</h4><p>If we have a Visual Studio subscription, then we get a certain amount of free Azure credits per month that we can use for development. The amount of credit depends on the subscription. More details can be found <a href="https://azure.microsoft.com/en-us/pricing/member-offers/credit-for-visual-studio-subscribers/">here</a>. So, let’s take that opportunity to create resources in Azure. One such resource is an Azure VM.</p><p>To get started with the setup we need to create a VM in Azure and configure it so that we can connect to it using SSH. Microsoft VS code team have written<a href="https://code.visualstudio.com/docs/remote/ssh-tutorial"> a nice post on how to do that</a>. So, we can follow all the instructions in that post except for the last 2 sections (creating a Node.js app).</p><blockquote><a href="https://code.visualstudio.com/docs/remote/ssh-tutorial"><strong>Creating a VM in Azure</strong></a></blockquote><h4>SPFx setup</h4><p>Once we have our VM up and running and we are able to communicate with it via SSH, we need to start the setup related to SPFx.</p><p>In the VS code terminal window let’s run the following command to <a href="https://linux.die.net/man/8/apt-get">re-sync the package index files</a> from their sources.</p><blockquote>sudo apt-get update</blockquote><p>After that let’s install the curl package using the following command</p><blockquote>sudo apt install curl</blockquote><p>Next step is to install node js. We have a couple of options for that</p><ol><li>We can install a specific version of node js by following the instructions <a href="https://tecadmin.net/install-latest-nodejs-npm-on-ubuntu/">here</a>.</li><li>We can install nvm and then choose a version of node js we want.</li></ol><p>We will use the NVM option (so that we have multiple versions of Node js if needed in the future). So run the following command to install nvm</p><blockquote>curl -o- <a href="https://raw.githubusercontent.com/nvm-sh/nvm/0/37.2/install/sh">https://raw.githubusercontent.com/nvm-sh/nvm/0/37.2/install/sh</a> | bash</blockquote><p>We will then install node js version 14.15.5 (one of the supported versions for SPFx development)</p><blockquote>nvm install 14.15.5</blockquote><p>If there is an error while installing, trying restarting the terminal in VS code</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/266/1*goqkOJydscILL4p-IgKiGw.png" /></figure><p>Check if the correct version of node js is installed with the following command</p><blockquote>node -v</blockquote><p>Now that we have node js setup, let’s install the packages needed for SPFx. They are gulp, yeoman, yeoman generator for SPFx projects. Let’s also install an SPFx development utility tool called “<a href="https://www.npmjs.com/package/spfx-fast-serve">spfx-fast-serve</a>” which helps us in saving time while development.</p><blockquote>npm i -g gulp yo @microsoft/generator-sharepoint spfx-fast-serve</blockquote><p>Once that is done we can start creating SPFx projects. Let’s run the following commands:</p><blockquote>mkdir projects &amp;&amp; cd projects</blockquote><blockquote>mkdir spfx-hello-world &amp;&amp; cd spfx-hello-world</blockquote><p>This will create folder named “spfx-hello-world” and we will create our project in that folder. To that run the command:</p><blockquote>yo @microsoft/sharepoint</blockquote><p>Follow the instructions and wait for the command to complete. Once done, run the following:</p><blockquote>spfx-fast-serve</blockquote><blockquote>npm i</blockquote><p>This will configure spfx-fast-serve for the project.</p><p>Let’s now run the project with the following command</p><blockquote>npm run serve</blockquote><p>This will build the project and open browser window pointing to “<a href="https://localhost:4321/temp/workbench.html">https://localhost:4321/temp/workbench.html</a>”.</p><p>VS code automatically takes care of <a href="https://code.visualstudio.com/docs/remote/ssh#_forwarding-a-port-creating-ssh-tunnel">forwarding the port</a> with number 4321 i.e. the port number 4321 from the Azure VM is forwarded to our local machine and we can continue working in the same way like we do on our local machine.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/444/1*QfFUDdxjM83u2bKxkMn-Yw.png" /></figure><p>If the SPFx web part / extension is present on a page in SharePoint site then we can <a href="https://www.eliostruyf.com/testing-and-debugging-your-spfx-solutions-in-production-without-causing-any-impact/">debug the web part / extension directly</a> by appending the following to the page URL</p><blockquote>?loadSPFX=true&amp;debugManifestsFile=https://localhost:4321/temp/manifests.js</blockquote><p>With spfx-fast serve, if we make any changes to the code, the SharePoint page which has the webpart (or the workbench page) is automatically refreshed to reflect the changes.</p><h4>Watching file changes</h4><p>In case changing the code in the files, doesn’t refresh the page to show the changes and/or a waring like below is seen in VS code then this is related to <a href="https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc">file watcher running out of handles</a> because of the large workspace.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/465/1*B_wFgzJR9ViApTRZD9XFHg.png" /></figure><p>To resolve that issue we need to update a system file with the following commands:</p><blockquote>cd /</blockquote><blockquote>sudo vi /etc/sysctl.conf</blockquote><p>This will open the sysctl.conf file in the terminal. Type</p><blockquote>i</blockquote><p>to enter in insert mode. Scroll down to the bottom of the file and add the following</p><blockquote>fs.inotify.max_user_watches=524288</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/337/1*ag1HSiisicl7oryh_As7nw.png" /></figure><p>Press esc to come out of insert mode.</p><p>Type :wq! to save and quit.</p><p>Verify the changes by executing the following command</p><blockquote>sudo sysctl -p</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/335/1*4b2ifXV78csRx4icyzSDaw.png" /></figure><p>If we then navigate to our project directory and run the project, we should not see the VS code warning and changing the code should be reflected on the page.</p><h4>Summary</h4><p>With the help of Azure machines, VS code and Remote-SSH extension we can easily set up a remote development environment. We can then install the packages required for SPFx development and start developing SPFx projects. The development experience is very smooth and since the VMs are powerful the build and debug time will be very less. With VS code in picture, the remote development experience feels the same as local development experience.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/915/1*3tiYLSajwisU1utuAghjzA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a897b9ee5850" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Microsoft Graph API for taxonomy (beta) + SPFx]]></title>
            <link>https://medium.com/geekculture/microsoft-graph-api-for-taxonomy-beta-spfx-c5c252b2f5da?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/c5c252b2f5da</guid>
            <category><![CDATA[microsoft-graph]]></category>
            <category><![CDATA[spfx]]></category>
            <category><![CDATA[sharepoint]]></category>
            <category><![CDATA[taxonomy]]></category>
            <category><![CDATA[cascading-dropdown]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Sun, 20 Jun 2021 23:51:25 GMT</pubDate>
            <atom:updated>2023-07-17T11:33:02.015Z</atom:updated>
            <content:encoded><![CDATA[<p>In this short post we will take a look at how to consume the Microsoft Graph API for taxonomy in SPFx web parts. This endpoint is currently in beta.</p><h4>Taxonomy Graph API</h4><p>The Microsoft Graph API for taxonomy helps us to perform CRUD operations on the taxonomy data present in our Microsoft 365 tenant. More details about this can be found <a href="https://docs.microsoft.com/en-us/graph/api/resources/termstore-store?view=graph-rest-beta">here</a>.</p><p>The taxonomy data can be used in several places for several purposes in SharePoint sites. It helps us in classifying the SharePoint content in such a way that users will be able to get to the content they require faster. Since taxonomy plays an important role in SharePoint, understanding how to interact with taxonomy in SPFx would be very helpful for us so that we can develop web parts / extensions to handle various scenarios related to taxonomy.</p><h4>Sample code</h4><p>In this blog we will look at a scenario in which we will read the data from taxonomy using Microsoft Graph. All the code explained in the blog can be found <a href="https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-graph-cascading-managed-metadata">here</a>.</p><h4>Cascading taxonomy dropdowns using SPFx</h4><p>In order to explain the usage of the taxonomy API, let’s assume the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/475/1*ZA6RC4mxAADO6RkWd7cfjA.png" /></figure><ol><li>A term group named “1 company”</li><li>A term set named “Our location”</li><li>Parent terms as countries — France, Germany, India and UK</li><li>1st level Child terms as cities — London and Manchester.</li></ol><p>We will see how to read all the parent terms (countries) from a term set and then how to read all the child terms (cities) under a parent term.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/569/0*FOWvWCfxRwScQD61.gif" /></figure><h4>Using the taxonomy API</h4><p>The SPFx web part is developed using React hooks and accepts only one property as input and that is the ID of the term set (Our locations).</p><p>When the web part loads, we get all the countries (parent terms) present under “Our location” (term set). This can be seen in the <a href="https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-graph-cascading-managed-metadata/src/webparts/cascadingManagedMetadata/components/CascadingManagedMetadata.tsx#L42">getCountries</a> method. In that method we call the <a href="https://docs.microsoft.com/en-us/graph/api/termstore-term-list-children?view=graph-rest-beta&amp;tabs=http">List children</a> endpoint of the term set taxonomy API which will list the first level children under a term set — which in this is case is all the countries. We then populate those countries in a country dropdown.</p><p>When a country is selected from a the dropdown, we get all the cities (child terms) of the selected country — the <a href="https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-graph-cascading-managed-metadata/src/webparts/cascadingManagedMetadata/components/CascadingManagedMetadata.tsx#L61">_onCountryChange</a> method. We call the <a href="https://docs.microsoft.com/en-us/graph/api/termstore-term-list-children?view=graph-rest-beta&amp;tabs=http">List children</a> endpoint of the term taxonomy API which will list the first level children present under a term — in this case all the cities. These are the populated in the city dropdown.</p><p>At the time of writing this post getting the custom properties of a term from taxonomy API was not possible. Hence description of a term was used to store data associated with a term.</p><p>However we <a href="https://learn.microsoft.com/en-us/graph/api/resources/termstore-term?view=graph-rest-1.0#properties">can now access “Shared Custom Properties”</a> of a term using the taxonomy API by adding “properties” to the “$select” clause.</p><p>In this scenario we store the co-ordinates of a city in the description. We use <a href="https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-graph-cascading-managed-metadata/src/webparts/cascadingManagedMetadata/components/CascadingManagedMetadata.tsx#L94">that data</a> to show the exact location in the <a href="https://pnp.github.io/sp-dev-fx-controls-react/controls/Map/">PnP SPFx map control</a>.</p><p>To send a request to Microsoft Graph, we use a <a href="https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-graph-cascading-managed-metadata/src/webparts/cascadingManagedMetadata/services/MSGraph.ts">helper class</a>. This was created by <a href="https://www.techmikael.com/?m=1">Mikael Svenson</a> and it can be used in any SPFx project. This class has 4 methods to perform CRUD operations using Microsoft Graph.</p><h4>Caching</h4><p>In the scenario explained here, the data in the taxonomy doesn’t change when the user is interacting with the web part. Hence to improve performance, it makes sense to cache the data instead of calling the taxonomy API each and every time. When we get the city terms under a country we can store them in cache (local storage). To do that we use a class that has methods related to <a href="https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-graph-cascading-managed-metadata/src/webparts/cascadingManagedMetadata/services/MMDService.ts#L45">adding data to local storage</a> and <a href="https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-graph-cascading-managed-metadata/src/webparts/cascadingManagedMetadata/services/MMDService.ts#L58">fetching data from local storage</a> when needed.</p><h4>Summary</h4><p>Thanks to the Microsoft Graph team we are now able to interact with the taxonomy data. In this blog post we have seen a simple way to read the data in SPFx web part. This web part can be easily extended to create/update/delete taxonomy data as well. All this is now easily possible with the help of the various taxonomy endpoints provided by this API.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c5c252b2f5da" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/microsoft-graph-api-for-taxonomy-beta-spfx-c5c252b2f5da">Microsoft Graph API for taxonomy (beta) + SPFx</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Copy PnP search results web part settings easily across pages]]></title>
            <link>https://anoopt.medium.com/copy-pnp-search-results-web-part-settings-easily-across-pages-81886c09658a?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/81886c09658a</guid>
            <category><![CDATA[search]]></category>
            <category><![CDATA[spfx]]></category>
            <category><![CDATA[pnp]]></category>
            <category><![CDATA[extension]]></category>
            <category><![CDATA[pnpj]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Sun, 20 Jun 2021 00:14:29 GMT</pubDate>
            <atom:updated>2021-06-22T15:32:38.935Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post we will have a look at how we can easily copy the settings of the PnP search results web part from one page to many pages with the help of PnP JS in an SPFx extension.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rxFfB13aPP9lE8FJlKhLww.png" /></figure><h4>PnP search web parts</h4><p>PnP search web parts are a set of modern web parts which help us a lot in providing a smooth search experience in modern SharePoint sites. More information about those web parts can be found <a href="https://microsoft-search.github.io/pnp-modern-search/">here</a>.</p><h4>Editing the settings</h4><p>In a SharePoint site we can create pages based on a template that might have these web parts on them. Say for example, we might have PnP search results web part(s) on the pages at a particular location, to roll up content based on the metadata of the page. If we want to change the settings in those results webpart across all the pages, then we have a couple of options:</p><ol><li>Manually edit each page that has these webparts and update the settings.</li><li>Change the settings in one page, export the settings and import those settings, in all the other pages manually. This is with the help of “Import / Export settings” capability seen in the web part property pane.</li></ol><p>This blog post aims to introduce a 3rd option to update the settings of the PnP search results webpart across all the pages.</p><h4>Copy PnP search results web part settings SPFx extension</h4><p>We now have <a href="https://github.com/pnp/sp-dev-fx-extensions/tree/main/samples/react-command-copy-pnp-search-webpart-settings">a list view command set</a> available to carry out the above task. Let’s establish a scenario to see how this extension works.</p><p>Say we have created hundreds of department pages in our site that have a PnP search results webpart in the second column of the first section. This web part rolls up all the documents of department content type. A new requirement comes up to say each of these department pages must roll up only documents belonging to that department. We then come up with a query/settings to do solve this requirement. Now our task is to apply that query/settings to the web part in all the pages.</p><p>That is when we can use the copy PnP search results web part settings SPFx extension. If we change the query/settings in one page, then using this extension we can copy those changes to all other pages.</p><h4>Using the extension</h4><p>After the query/settings is changed in a page (source) we need to do the following:</p><ol><li>Navigate to the pages library and select all the other pages (destination) where the web part query/settings needs to be changed.</li><li>Select “Copy PnP search settings” from the command.</li><li>Provide the name of the source page and location of the web part on the source page i.e. the section, column and order.</li><li>Provide the location of the web part in the destination pages.</li><li>Press submit.</li></ol><p>This will then copy the query/settings of the PnP search results web part from the source to the web part in all destination pages and output the result by showing which pages were successfully updated and which were not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rVHtQdmEXEc2bxaBuvv7gA.gif" /></figure><h4>Details of the code</h4><p>Based on the details provided the extension uses PnP JS to get the properties of the web part where the query was updated. This can be seen in the <a href="https://github.com/pnp/sp-dev-fx-extensions/blob/main/samples/react-command-copy-pnp-search-webpart-settings/src/extensions/copyPnPSearchWebpartSettings/components/WorkerDialog.tsx#L126">getSourceWebpartSettings</a> method. We get the json of the web part using PnP JS and then webPartData.properties.</p><p>After that, in the <a href="https://github.com/pnp/sp-dev-fx-extensions/blob/main/samples/react-command-copy-pnp-search-webpart-settings/src/extensions/copyPnPSearchWebpartSettings/components/WorkerDialog.tsx#L157">updateDestinationWebpartSettings</a> method, we can see that the extension uses PnP JS to update the properties of the webparts in the destination pages. The extension also provides to perform this operation in batches using the PnP JS batching capability. This can be seen in <a href="https://github.com/pnp/sp-dev-fx-extensions/blob/main/samples/react-command-copy-pnp-search-webpart-settings/src/extensions/copyPnPSearchWebpartSettings/components/WorkerDialog.tsx#L200">upddateDestinationWebpartSettingsUsingBatch</a> method.</p><p>The extension also provides an option to export the <a href="https://github.com/pnp/sp-dev-fx-extensions/blob/main/samples/react-command-copy-pnp-search-webpart-settings/src/extensions/copyPnPSearchWebpartSettings/components/WorkerDialog.tsx#L313">PnP JS code</a>. This code can be copied and then tweaked as per the requirement. After that the SP editor browser extension can be used to run the code.</p><h4>Enhancements</h4><p>Currently this SPFx extension is developed such that it works with PnP search results web part only. It can be changed to work with other web parts if needed. This would involve updating the code to get and set intended web part properties (from the web part json) based on the type of the web part.</p><h4>Summary</h4><p>Settings/properties of a web part in a modern page can be easily copied from one page to other pages using PnP JS. We need to get the web part object first based on it’s location and then get the webPartData.properties property of that object. This data then needs to be set as the properties of the web part on other pages. We saw how we can do the same for PnP search results web part using an SPFx extension.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81886c09658a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Set modern page header author byline using PnP PowerShell]]></title>
            <link>https://anoopt.medium.com/set-modern-page-header-author-byline-using-pnp-powershell-d6e958bbc99b?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/d6e958bbc99b</guid>
            <category><![CDATA[header]]></category>
            <category><![CDATA[authorbyline]]></category>
            <category><![CDATA[modern-pages]]></category>
            <category><![CDATA[sharepoint]]></category>
            <category><![CDATA[pnp]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Mon, 19 Apr 2021 13:05:58 GMT</pubDate>
            <atom:updated>2021-04-19T13:17:45.343Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post we will look at a way to set the author byline (shown in the image below) of a modern SharePoint page using PnP PowerShell.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*55AYGoczkB7heEZFO3W0dw.png" /></figure><h4>The script</h4><p>The script to set the author byline in the header can be found <a href="https://gist.github.com/anoopt/45abde570c228f2a168fec9c4508b230">here</a>.</p><h4>_AuthorByline field</h4><p>Modern pages have a field called _AuthorByLine. When we set the value of this field of a page to the login name (or the email address) of a user, we will see the user details appear in the page header byline. However, as soon as we edit the page those details disappear (or the details of the user that were set before are shown).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1022/1*J_qRFMSC366RTT8MFZJVXg.gif" /></figure><p>It appears that when in view mode of the page, the page header byline loads the data from _AuthorByLine field of the page. (If no data is found then the page header byline tries to get user data from LayoutWebpartsContent field of the page.) Whereas in edit mode, page header byline tries to get user data directly from LayoutWebpartsContent field of the page. Since LayoutWebpartsContent will not have the details of the user the header byline won’t show anything in edit mode.</p><p>In the image below we set the _AuthorByLine field of Page-1.aspx and then we get the LayoutWebpartsContent field of the page. We can see that the authors data in the HTML is empty.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9_s2C1zVcZLN-srGWOyV3A.png" /></figure><p>So to make this work, along with the _AuthorByLine field, we also need to set LayoutWebpartsContent field of the page with the details of the user.</p><h4>PnP Core SDK</h4><p>LayoutWebpartsContent field contains HTML content and to update that field we can rely on the <a href="https://pnp.github.io/pnpcore/">PnP Core SDK</a>. PnP have made our task easy by extracting the HTML content from LayoutWebpartsContent field and mapping them to properties in the <a href="https://github.com/pnp/pnpcore/blob/262aefb87f4f75f410c7e821af31f919bb4669c4/src/sdk/PnP.Core/Model/SharePoint/Pages/Internal/PageHeader.cs#L15">PageHeader property</a> of the page. The properties that we are interested in are Authors and AuthorByLine. We need to set these properties and save the page. This will then set the LayoutWebpartsContent property of the page.</p><p>When we set the Authors property of PageHeader , PnP Core SDK will also set the _AuthorByLine field of the page. So there is no need for us to worry about setting the _AuthorByLine field separately.</p><h4>Setting Authors and AuthorByLine properties</h4><p>The Authors and AuthorByLine properties of PageHeader are of type String . We can set them using PnP PowerShell with the following lines of code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DqjlCU95NTJHapiMw8r-uQ.png" /></figure><blockquote>The entire script can be found <a href="https://gist.github.com/anoopt/45abde570c228f2a168fec9c4508b230">here</a>.</blockquote><p>We can simply compose the strings in the above format using an email address, run the above two lines and save the page. However, if the email address is invalid then we will face a problem by which an invalid value gets set as the header byline. Therefore, the script checks if the email address passed is valid by using Get-PnPUser and New-PnPUser functions.</p><p>Running the script sets the above two properties, saves the page and publishes it. By reloading the page we can see the user information in the page header byline.</p><h4>Displaying user information</h4><p>In the view mode, the page header byline gets the user information (like Name, Job Title) from the User Information List for user that is set. If the Job Title doesn’t appear then it might be because it is not yet updated in the User Information List. The information in the User Information List takes a few minutes to update/sync.</p><p>In the edit mode (or in view mode when _AuthorByline field is empty) the page header byline picks the user information based on the id set in Authors data of LayoutWebpartsContent .</p><ul><li>If the id in Authors is set to email address of the user then the user information is obtained using Microsoft Graph.</li><li>If the id in Authors is set to LoginName of the user (i.e. of the format i:0#.f|membership|... then the user information is obtained using the SharePoint user profile service using REST API (SP.UserProfiles.PeopleManager/GetPropertiesFor).</li></ul><h4>Summary</h4><p>To display a user’s information in header byline of a modern SharePoint page, we need to set _AuthorByline and LayoutWebpartsContent field of the page. We can do that using PnP PowerShell by setting the Authors and AuthorByline properties of the PageHeader property of a page.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d6e958bbc99b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Options for obtaining an access token with Azure application to application authentication]]></title>
            <link>https://anoopt.medium.com/few-ways-of-obtaining-access-token-in-azure-application-to-application-authentication-40a9473a2dde?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/40a9473a2dde</guid>
            <category><![CDATA[managed-identity]]></category>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[aml]]></category>
            <category><![CDATA[identity]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Tue, 06 Apr 2021 15:53:55 GMT</pubDate>
            <atom:updated>2021-04-08T10:55:49.280Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post we will take a look at a few different options that we can use, to get an access token so that a daemon application (say <strong>client</strong>) can communicate with another application (say <strong>server</strong>). Both client and server applications can be developed as Azure app services and/or Azure function. In this post we will be sticking with Azure functions for both client and server where needed.</p><h3>Setup</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KBYDVi-GZnlDFpVAYeofEg.gif" /></figure><p>Let’s assume the following setup (in the same tenant) for this post:</p><ul><li>Client runs on a timer (or it receives requests from end users).</li><li>Client app runs code to get an access token and calls the server app for data (say Orders).</li><li>The server app receives requests only from apps (not users) and expects certain claims to be present in the request.</li><li>The server app is secured using an app registration.</li><li>Based on the request received, the serve app does some computation and returns the data (Orders) to the client app.</li><li>If applicable, the client app then returns that data to end users.</li></ul><p>In this blog post we will be focusing on points 2, 3 and 4 above which are related to the way the client app sends requests to the server app. An HttpClient object will be used by the client app to call the server app. This HttpClient will have the access token in it’s request headers. In the sections below we will see a few ways of getting this access token.</p><h4>Server app registration</h4><p>An app registration is associated with the server function app. This app registration is server’s identity and is used to <a href="https://www.voitanos.io/blog/securing-an-azure-function-app-with-azure-ad-works-with-with-sharepoint-framework/">secure the server function app</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0mx6TzJSVXUpjyWZtLxTFQ.png" /><figcaption>Secure the server function app</figcaption></figure><h4>Application role</h4><p>The server app registration is setup with an <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps">application role</a> let’s say “<strong><em>Orders.Read</em></strong>” — which is a custom role. (This role is exposed as an application permission that can be added in other app registrations.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/937/1*7R_R_KQFZ2uoKNBVv9-wXg.png" /><figcaption>Create an app role for the server app reg</figcaption></figure><p>To make sure that <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-registration#ensuring-that-azure-ad-issues-tokens-for-your-web-api-to-only-allowed-clients">incoming requests have role assignments</a> and only apps with “Orders.Read” permission can get the data from the server, the code in the server function app <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Server/GetOrders.cs#L30">verifies that</a> the incoming requests have “Orders.Read” role in the request. This is done by checking the “roles” claim of the incoming request.</p><blockquote><a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Server/GetOrders.cs#L30">req.HttpContext.ValidateAppRole(new string[] { “Orders.Read” });</a></blockquote><h4>Client function app</h4><p>The client function app is a <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-overview">daemon app</a> which uses it’s own identity (client app registration) to get data from the server function app. (The client function app can also be a non-daemon app but will still use it’s own identity (not users identity) to get data from server).</p><p>The client app registration will have “Orders.Read” application permission that will be approved by the admin. With this, the access token that gets generated in the client function app’s code will have “Orders.Read” role in the roles claim.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iqJ7Sqrw08Y9-_luTdKS9w.png" /><figcaption>Orders.Read app permissions of the client app reg</figcaption></figure><p>In the following sections we will see how to generate the access token in the client function app.</p><h4>Code</h4><p>All the code explained below can be found in this <a href="https://github.com/anoopt/Azure-application-to-application-authentication">GitHub repository</a>. Please note that the code is to show the concepts only and is not PROD ready.</p><h3>Generating access token</h3><p>Microsoft provide us with various authentication libraries which do all the hard work of computing the access token and checking it’s expiry. So, all we need to do is install one of those libraries in our project and use the methods that are provided by these libraries and get an access token. Let’s take a look at those libraries and the methods they provide:</p><h4>1. ADAL (not recommended / deprecated)</h4><p><strong><em>Note</em></strong>: Using ADAL is <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-migration">deprecated</a> hence not recommended. So use MSAL instead.</p><p>Using ADAL we create an AuthenticationContext object and get the access token using AcquireTokenAsync method of the authentication context object as shown in the image below and the code <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Client/Helpers/ADALAuth.cs">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JXwKPxJg8eMKhdDKOnL0KQ.png" /></figure><h4>2. MSAL.NET</h4><p>In <a href="https://www.nuget.org/packages/Microsoft.Identity.Client/">MSAL.NET</a>, apps are represented either as <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-applications">public client or confidential client</a> applications. In our case the client app registration will be represented as confidential client application. So, we create a variable of type IConfidentialClientApplication using the ConfidentialClientApplicationBuilder class provided by MSAL. We need to provide the client app registration’s ID, it’s secret (or certificate in case of PROD) and the tenant ID.</p><p>We then use the AcquireTokenForClient method with the <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#request-the-permissions-in-the-app-registration-portal">scopes as</a> “ServerAppRegId/.default” to get an access token. The access token will have the server app registration’s ID in the aud claim and will have the Orders.Read entry in the roles claim.</p><p>The code for the same can be seen in the image below and <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Client/Helpers/MSALAuth.cs">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rgm2SyxJ_Hh7LoTsodm0tw.png" /></figure><h4>Managed identities</h4><p>Instead of using the client app registration to represent the client function app, we can create an identity in a different way for that function app by using <a href="https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview">managed identity</a>. This will help us move away from maintaining client secret or certificate.</p><p>Managed identity for the client function app can be created by following the information in <a href="https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=dotnet">this Microsoft article</a>.</p><p>The managed identity that is created will not have any permissions to talk to the server app. Hence we need to add “Orders.Read” application permission to the managed identity. We can do that either using <a href="https://docs.microsoft.com/en-us/powershell/module/az.resources/new-azroleassignment?view=azps-5.7.0">Azure PowerShell</a> or <a href="https://pnp.github.io/cli-microsoft365/">Microsoft 365 CLI</a>. The Microsoft 365 command will be:</p><blockquote>m365 aad approleassignment add — displayName “client-function-app” — resource “server-app-reg-name” — scope “Orders.Read”</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PO40qdbxYbybWgQGFSjrag.png" /><figcaption>Permissions assigned to the managed identity</figcaption></figure><p>Once the permission is assigned we can create a request to get an access token, to access the server app, using the managed identity of the client function app. Let’s see a couple of ways in which we can do that.</p><h4>3. App Authentication client library for .NET</h4><p><strong><em>Note</em></strong>: For new applications <a href="https://docs.microsoft.com/en-us/dotnet/api/overview/azure/service-to-service-authentication">Microsoft recommend</a> using Azure.Identity instead of this library.</p><p>With this library we create an instance of AzureServiceTokenProvider with the connection string as RunAs=App. We then use the GetAccessTokenAsync method by passing in the server app registration’s ID to get an access token.</p><p>The access token will have the server app registration’s ID in the aud claim and will have the Orders.Read entry in the roles claim.</p><p>The code for the same can be seen in the image below and <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Client/Helpers/AppAuthLib.cs">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KUzQpz0dKXmX2kSnUHhc6A.png" /></figure><h4>4. Azure.Identity library</h4><p>Microsoft have mentioned in <a href="https://docs.microsoft.com/en-us/dotnet/api/overview/azure/app-auth-migration">this article</a> that “Azure.Identity can be considered as the spiritual successor of the App Authentication client library for .NET”. Using Azure.Identity library we create an instance of ManagedIdentityCredential class. This will automatically use the credentials of the managed identity configured for the client function app. We then call the GetTokenAsync method with the scopes as “ServerAppRegId/.default” to get an access token. The access token will have the server app registration’s ID in the aud claim and will have the Orders.Read entry in the roles claim.</p><p>The code for the same can be seen in the image below and <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Client/Helpers/AzureIdentity.cs">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TfAEdzK2pS5gEXCSf0ocIg.png" /></figure><h4>Local development auth</h4><p>During development if we want to test the above functionality (3 and 4) then, we have cannot use Managed Identity for authentication locally. Hence we have to use different methods of authentication.</p><p>More information on those methods can be found <a href="https://docs.microsoft.com/en-us/dotnet/api/overview/azure/service-to-service-authentication#local-development-authentication">here</a> and <a href="https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme#authenticate-the-client">here</a>.</p><blockquote>Videos related to the same by <a href="https://youtu.be/5Ln6xJ6F7vI?t=1312">Garry Trinder</a> and <a href="https://www.rahulpnath.com/blog/azure-managed-service-identity-and-local-development/">Rahul Nath</a> in their blog articles.</blockquote><p>In the <a href="https://github.com/anoopt/Azure-application-to-application-authentication">code that is shared</a> along with this blog, we will be using the credentials of the client app registration for authentication during local development. This can be seen <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Client/Helpers/AppAuthLib.cs#L22">here </a>and <a href="https://github.com/anoopt/Azure-application-to-application-authentication/blob/main/Client/Helpers/AzureIdentity.cs#L22">here</a>. We make use of client app registration’s ID and secret to authenticate. This needs to be the case only in DEV tenant. In PROD there will be no need to have client app registration when using Managed Identity.</p><h4>Further enhancements</h4><p>The code in the server function app at the moment checks only if the token has the correct roles claim. The code can be enhanced further <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration">to perform more checks </a>based on the security requirement. Those checks might be verifying if the aud claim in the request is the intended one or not etc.</p><h4>Summary</h4><p>We have seen 4 different ways of getting an access token for a daemon app (Client) to call an API (Server)using the different libraries provided by Microsoft. Thanks to Microsoft, all the hard work of computing the access token is done by the libraries. We have seen how easy it is to use the methods provided by those libraries.</p><p>If we are using an app registration to represent the client function app then we should use <strong>MSAL</strong> to get the access token and if we are using managed identity to represent the client function app then for the new development it is recommended to use <strong>Azure.Identity </strong>to get the access token.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/902/1*99L6y85Lok7eo6zzD_kWkA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=40a9473a2dde" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub codespaces + SPFx]]></title>
            <link>https://anoopt.medium.com/github-codespaces-spfx-fdd676baf538?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/fdd676baf538</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[codespace]]></category>
            <category><![CDATA[spfx]]></category>
            <category><![CDATA[hello-world]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Fri, 09 Oct 2020 13:55:04 GMT</pubDate>
            <atom:updated>2021-02-05T10:45:58.612Z</atom:updated>
            <content:encoded><![CDATA[<p>In this blog post we will have a look at how we can use GitHub codespaces to do development related to SPFx. The concepts that are mentioned in this blog post run in parallel with the details shared in <a href="https://www.schaeflein.net/using-visual-studio-codespaces-for-spfx-development/">this brilliant article</a> on Visual Studio codespaces by Paul Schaeflein.</p><p>In this 2 part series we will start by looking at:</p><ol><li>What GitHub codespaces are and how to create them. After that we create a simple SPFx webpart in GitHub codespaces.</li><li>How to push the code of the SPFx webpart from GitHub codespaces to a repository in Azure DevOps.</li></ol><h3>Part 1 — GitHub Codespaces</h3><h4>What are GitHub codespaces?</h4><p><a href="https://github.com/features/codespaces">GitHub codespaces</a> are cloud hosted development environments that can be created very quickly so that we can start coding on a project right away. In terms of SPFx development we can think of GitHub codespaces as a preconfigured development environment (in the cloud) which has all the SPFx pre-requisites installed.</p><blockquote>GitHub codespaces are currently in beta and access can be requested from <a href="https://github.com/features/codespaces">here</a>. During the beta stage each user can create a maximum of 2 codespaces.</blockquote><h4>Creating GitHub codespaces (need to request access first)</h4><p>In order to create GitHub codespaces, we need a GitHub repository. Navigating to a repository and clicking on “Code” button and selecting “Open with codespaces” provides us an option to create a new codespace.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/390/1*4AoewSYfNEqYFalDMNmxng.png" /><figcaption>Create a new GitHub codespace</figcaption></figure><p>The new codespace (i.e. a development environment in cloud) that gets created will be based on Linux and will have version 12.x of node. More information on what is included in that codespace can be found <a href="https://github.com/microsoft/vscode-dev-containers/tree/master/containers/codespaces-linux#github-codespaces-default-linux-universal">here</a>.</p><p>The newly created codespace will be automatically connected to the repository in GitHub. So if we add/remove files we can commit them back to the connected repository.</p><h4>Accessing codespaces</h4><p>We cannot RDP into this codespace, instead we connect to only the code we need in the codespace from VS code (either from the browser or from the editor on our machines). We need a VS code extension named “<a href="https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces">GitHub Codespaces</a>” to access codespaces.</p><h4>GitHub codespaces for SPFx</h4><p>When a GitHub codespace is created, by default it will have Node version 12.x. For SPFx development we need Node version 10.x. Also the codespace will not have the other components like yeoman, the SPFx generator etc which are needed for SPFx development.</p><p>There are different ways of making the codespace have the setup required for SPFx development i.e. <a href="https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/configuring-codespaces-for-your-project">personalizing the codespace</a> as per our requirement. We will be looking at the Docker approach.</p><p>When a new codespace is being created, it will look for something called as dotfiles (files and folders beginning with a dot). We can define the required SPFx configuration in these dotfiles. We can achieve this is by creating a folder named “<strong>.devcontainer</strong>” and adding 2 files in it named “<strong>Dockerfile</strong>” and “<strong>devcontainer.json</strong>”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/464/1*D2svnATM_vCkwqXL0OrINw.png" /><figcaption>.devcotainer folder (more info on the contents below)</figcaption></figure><p><strong><em>Note</em></strong>: The configuration specified in these files will be applied only to a new codespace at the time of creation. It will not apply to existing codespace. So if you have created a new codespace without these files then it would be good to delete it or run the pre-requisites manually.</p><h4>Docker file</h4><p>Please refer <a href="https://rencore.com/blog/try-sharepoint-framework-without-installing/">this article by Waldek Mastykarz</a> to know about Docker and SPFx. Since a Docker file already exists for SPFx, we will be using that for our initial setup with a minor update.</p><h4>devcontainer.json</h4><p>This file contains information about the default settings of a codespace. One of the setting we need is what ports need to be forwarded from the codespace. So we will be defining those in this file. For SPFx we need ports <strong>4321</strong>, <strong>5432 </strong>and <strong>35729</strong> to be forwarded from the codespace.</p><h4>.devcontainer folder</h4><p>Lets either create a new repository with only file in it (README.md) or open an existing SPFx repository. Let’s add a folder named “.devcontainer” in the repository at the root level.</p><p>In this folder lets add the following 2 files:</p><ol><li><a href="https://gist.github.com/anoopt/e69a71ebb42d592c614da90ed1e523e7">Dockerfile</a> (click to open content)</li><li><a href="https://gist.github.com/anoopt/3fcb409dbdab2b19f919aa454f13842a">devcontainer.json</a> (click to open content)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/464/1*D2svnATM_vCkwqXL0OrINw.png" /></figure><p>In the <a href="https://gist.github.com/anoopt/e69a71ebb42d592c614da90ed1e523e7">Dockerfile</a>, we have specified the Node version as 10.22.0 and have also specified all the npm packages that need to be installed for SPFx development.</p><p>In the <a href="https://gist.github.com/anoopt/3fcb409dbdab2b19f919aa454f13842a">devcontainer.json</a> we have specified that the Dockerfile needs to be used for creation of the codespace and we have specified the required ports that need to be forwarded.</p><h4>Creating a codespace (need to request access first)</h4><p>Now let’s create a codespace from this repository. For this all we have to do is click on “Code” button and select “Open with codespaces” and click “New codespace”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/798/1*JWzazJOGeQS54lHn3vQGuw.png" /></figure><p>This will start the process of creating the codespace which will involve looking at the “.devcontainer” folder for the configuration that we defined. We can see all the messages being logged related to installing the packages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/552/1*mw3EggraPHqodCZdJSMnFQ.png" /><figcaption>Codespace creation with SPFx Dockerfile</figcaption></figure><p>Once finished, we will see VS code opened in browser with the code in the repository. We can see the “.devcontainer” folder and other files present in the repository.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/352/1*syhCaK65xKOrUYRc7T_4hA.png" /><figcaption>Codespace with the files in the repository</figcaption></figure><p>If we open the terminal (Ctrl + ‘) and type “node -v” we can see that the version is 10.22.0 as specified in the “Dockerfile”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/553/1*kRRv4Ujba1l9GVPArJyCrQ.png" /><figcaption>Version of Node in the codespace</figcaption></figure><p>If we open the Remote explorer extension and have a look at the forwarded ports section, we can see the 3 ports forwarded that we defined in “devcontainer.json”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/452/1*32KfPV3WWXLZb_dBVldNhw.png" /><figcaption>Ports forwarded from the codespace</figcaption></figure><h4>SPFx development</h4><p>We can continue the next steps in the browser, however I prefer doing it in the VS code editor. So let’s open VS code and install the “<a href="https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces">GitHub codespaces</a>” extension if not installed. After signing into codespaces with our GitHub accounts we will be able to see the codespace that was created earlier.</p><p>Connect to the codespace by clicking on the connect button to load the code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/390/1*1nFOw_DXKFAnxD6Nw1A9fQ.png" /><figcaption>Connect to codespace from VS code</figcaption></figure><p>Now we are in the familiar territory. So let’s open the terminal in VS code and start developing SPFx solutions with our favourite command yo @microsoft/sharepoint .</p><p>Before testing the solution we need to make sure that the dev certificate is trusted. To do that, please follow <a href="https://www.schaeflein.net/spfx-in-codespaces-trust-the-dev-cert/">this article by Paul Schaeflein</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/814/1*9Tc0En8FLfi0D9PGLZpCTQ.png" /></figure><h4>node_modules</h4><p>One nice thing to think/feel about is that there is no “node_modules” folder of this project on our machines now!</p><h4>Azure DevOps</h4><p>If we want to store the above code in Azure DevOps instead of GitHub then in the next we will look at <a href="https://medium.com/@anoopt/github-codespaces-azure-devops-ee8f520981ab">how to push the code from GitHub codespaces to a repository in Azure DevOps</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fdd676baf538" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub codespaces + Azure DevOps]]></title>
            <link>https://anoopt.medium.com/github-codespaces-azure-devops-ee8f520981ab?source=rss-3cb1c1004f96------2</link>
            <guid isPermaLink="false">https://medium.com/p/ee8f520981ab</guid>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[spfx]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[codespace]]></category>
            <category><![CDATA[github]]></category>
            <dc:creator><![CDATA[Anoop]]></dc:creator>
            <pubDate>Fri, 09 Oct 2020 13:53:54 GMT</pubDate>
            <atom:updated>2020-10-09T13:55:38.293Z</atom:updated>
            <content:encoded><![CDATA[<p>In <a href="https://medium.com/@anoopt/github-codespaces-spfx-fdd676baf538">part 1</a> of this series we saw how to create a GitHub codespace for SPFx development. In this part we will have a look at how to push the code from GitHub codespaces to Azure DevOps. This will be helpful if we are using Azure DevOps to store our code.</p><p>The instructions mentioned here can be done for any code in GitHub codespaces. Also the instructions show one of the several ways of push the code to Azure DevOps. So let’s be sure that this is not the only way.</p><h4>Repository in Azure DevOps</h4><p>Let’s start by creating a new Git repository in Azure DevOps. There are many articles on the internet explaining how to do that.</p><p>Our intention is to make the GitHub codespace we created in part 1 to talk to the repository we created in Azure DevOps. There are several ways of doing this. We will go ahead with the SSH key approach which will be helpful in a way that there is no need to enter password for our git operations.</p><p>In newly created repository, click on “Clone” and then click on “SSH” and copy the URL representing the repository.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/484/1*ZfEcp_69ILBJhsxRw7q_ow.png" /></figure><h4>Setup in codespaces</h4><p>Now in VS code let’s connect to the GitHub codespace that we created in part 1 and open the terminal. Now the code over here is already connected to a repository in GitHub. We need to remove that connection and establish a new connection to our repo in Azure DevOps.</p><p>So in the terminal enter the following command to remove the .git folder and hence the connection</p><blockquote>rm -rf .git</blockquote><p>After that initialise a repository with the following command</p><blockquote>git init</blockquote><p>Let’s use the SSH URL copied earlier to add a remote for our Azure DevOps repo</p><blockquote>git remote add origin git@ssh.dev.azure.com:v3/ — project — / — repo —</blockquote><p>As mentioned earlier, instead of using password authentication for git operations, let’s use SSH keys to authenticate. More information on this can be found <a href="https://www.atlassian.com/git/tutorials/git-ssh">here</a>. To create SSH keys let’s run the following command:</p><blockquote>ssh-keygen -t rsa</blockquote><p>More parameters can be supplied by referring the guides <a href="https://linux.die.net/man/1/ssh-keygen">here</a> and <a href="https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops">here</a>.</p><p>As part of running the command, let’s not provide any file name or passphrase. So simply keep pressing return until the command finishes execution. Once completed, we can see the path of the file where the public key is saved.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/618/1*fC5UEvbZ16LZax3oUFGvJg.png" /></figure><p>We need grab that public key and add it in Azure DevOps i.e. associate the key with our Azure DevOps account. To get the public key run the following command</p><blockquote>code /home/spfx/.ssh/id_rsa.pub</blockquote><p>or if you are using VS code insiders</p><blockquote>code-insiders /home/spfx/.ssh/id_rsa.pub</blockquote><p>Copy the key without any line breaks and navigate to Azure DevOps and click on “SSH public keys” as shown in the screenshot below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*yKUqCx7TDGir61PSsAJ7Kg.png" /></figure><p>In the SSH keys window, click on “New key”, provide a relevant description and paste the copied key and save it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/463/1*pw9LXJ77rvTqYTYo2EBNnA.png" /></figure><p>Back in VS code test if this operation was successful by entering the following command</p><blockquote>git fetch</blockquote><p>If all is ok the next steps are to push the code to the repository. These are standard steps like we always do for other projects which include the standard `git commit` etc either via the terminal or via the source control extension.</p><h4>Conclusion</h4><p>After all the operations, our code will be in Azure DevOps.</p><p>When we connect to this GitHub codespace again, everything is setup and we can continue working on our SPFx solutions and keep committing the changes to the repo in Azure DevOps.</p><p>Once the project is complete, we can delete the GitHub codespace and move onto the next project by following instructions in Part 1 and this article.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee8f520981ab" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>