Development – Inquisitive M365 https://thomasdaly.net Yet another SharePoint / Office 365 blog Wed, 25 Mar 2026 03:39:23 +0000 en-US hourly 1 116451836 Why We Rebuilt a Power App as a Web App (And How AI Helped Us Ship It Fast) https://thomasdaly.net/2026/03/05/power-app-to-web-app-migration/ https://thomasdaly.net/2026/03/05/power-app-to-web-app-migration/#comments Fri, 06 Mar 2026 00:00:00 +0000 https://thomasdaly.net/?p=3413 Power App shattering into React components flowing into a modern web application

A mom-and-pop camera parts store came to us with a problem. They'd been running their inventory consignment tracking on a Power App built on top of SharePoint lists, and it was falling apart. Not in a dramatic way — more like death by a thousand cuts. Slow screens, security workarounds, lists bumping up against row limits, and a UI that fought them every time they needed to add a feature.

The app tracked consignment memos — when parts went out on consignment to other dealers, what was sent, what came back, what was still outstanding. It's the kind of workflow that sounds simple until you realize it touches inventory, customers, sales staff, PDF generation, email notifications, and reporting. The Power App had gotten them through the first couple of years, but they'd hit a ceiling.

We rebuilt it as a custom web application. And the part that made it feasible for a small team on a reasonable budget was AI-assisted development — not as a gimmick, but as a genuine force multiplier for the grunt work.


What Went Wrong with the Power App

Power Apps are great for getting something running quickly. You connect to a SharePoint list or Dataverse table, drag some controls onto a screen, write a few formulas, and you've got a working app in a day. For simple workflows with a handful of users, that's genuinely powerful.

But this app had grown well past that sweet spot. Here's what broke down:

Speed

The app was painfully slow. Every screen load involved multiple lookups against SharePoint lists, and Power Apps doesn't give you much control over how or when those queries execute. Users would tap a button and wait several seconds for the next screen to render. For a tool people use dozens of times a day, that friction adds up fast.

Delegation was a constant headache. Power Apps delegates certain operations to the data source, but many common operations — like certain filter combinations or sorting on calculated fields — can't be delegated. That means the app silently pulls only the first 500 or 2,000 rows and works with that subset, which led to missing records in search results. Users would search for a memo they knew existed and get nothing back.

The Large List Problem

SharePoint lists have a 5,000-item list view threshold. Once you cross it, queries that aren't indexed start failing or returning incomplete results. The consignment memo list had blown past this limit months ago. We'd added indexes on the most-queried columns, but Power Apps' delegation model meant some queries still couldn't take advantage of them.

The workarounds were ugly — splitting data across multiple lists, pre-filtering with flows, caching subsets locally. Each workaround added complexity and new failure modes.

Too Many Controls

Power Apps renders every control on a screen, even ones that aren't visible. As the screens grew more complex — conditional sections, expandable details, dynamic form fields — performance degraded. We'd hit screens with 200+ controls, and the app would visibly struggle to render them.

The recommended fix is to split screens into smaller pieces, but that creates a disjointed user experience and makes state management even harder. You end up passing data between screens through global variables and collections, which is fragile and hard to debug.

Security Limitations

Power Apps' security model is tied to the underlying data source. With SharePoint, that means item-level permissions or breaking inheritance — neither of which scales well. The business needed role-based access: admins see everything, sales staff see only their stores, viewers get read-only access. Implementing this cleanly in Power Apps required a tangle of conditional visibility rules and duplicate screens, and it was never quite right.

Hard to Hand Off

This is the one that doesn't get talked about enough. Power Apps are notoriously difficult to hand off to another developer. There's no proper source control. The "code" is a mix of Excel-like formulas scattered across control properties. There's no way to do a meaningful code review or diff. Documentation is whatever comments you've added to your formulas — and let's be honest, most Power App formulas don't have comments.

When the original developer moves on, the next person has to reverse-engineer the app by clicking through every screen and reading every control's property panel. For a complex app, that can take weeks just to understand what it does, let alone modify it safely.

Hard to Extend

Every new feature request felt like a negotiation with the platform. Need to generate a PDF? You're piping data to a Power Automate flow that calls a third-party connector or a custom API. Need to send styled HTML emails on a schedule? Another flow, another connector, another set of failure points. Need a proper grid with sorting, filtering, and inline editing? You're stacking galleries and buttons and toggle controls in ways they weren't designed for.

The app had become a Rube Goldberg machine. It worked, but adding anything new meant understanding all the existing moving parts and hoping nothing broke.


The Decision to Rebuild

We didn't take this lightly. Rewriting a working application is risky — you're spending money to get back to where you already are before you can move forward. But the writing was on the wall:

  1. The Power App couldn't scale further. Every new feature was getting harder and slower to build.
  2. Performance was hurting the business. Staff were avoiding the app when they could and falling back to spreadsheets.
  3. Maintenance risk was growing. The original developer's availability was limited, and nobody else could work on it.
  4. The requirements were well understood. After two years of using the Power App, the business knew exactly what they needed. No discovery phase, no guesswork.

That last point is underrated. The Power App had essentially served as a working prototype — an expensive one, but it meant we weren't building from a blank page. We had screens, workflows, and edge cases already mapped out.


How We Built the Replacement

We chose Next.js with TypeScript, Ant Design for the UI, Azure AD for authentication, and Azure SQL Database for storage. The app runs on Azure App Service with Azure Functions handling background jobs like scheduled email notifications.

Requirements and Mockups First

Before writing any code, we documented every feature the Power App had — and every feature they wished it had. We walked through the existing app screen by screen, cataloging functionality, business rules, and pain points. This gave us a clear requirements document and a set of UI mockups to build against.

This step matters more than people think. When you're using AI to help generate code (more on that in a minute), the quality of what you get out is directly proportional to the quality of what you put in. Vague requirements produce vague code. Detailed requirements with mockups produce components that are 80-90% right on the first pass.

AI-Assisted Development

Here's where it gets interesting. This was a solo developer project with a tight timeline. Building a full-featured web application — authentication, role-based access, CRUD operations, PDF generation, email automation, responsive UI — would normally take months of dedicated work.

Our AI tool of choice was Claude on the Max plan ($100/month). That's it — one subscription, one developer, and a clear set of requirements. We used it extensively throughout the build. Not to replace the developer, but to accelerate the tedious parts:

Scaffolding and boilerplate. Setting up API routes, database queries, TypeScript types, form validation — the kind of code that's necessary but not creative. With clear requirements, we could describe what an API endpoint needed to do and get a working implementation that just needed review and testing.

UI components. Given a mockup and a component library (Ant Design), generating the initial component code was fast. "Build a form page with these fields, these validation rules, and this layout" produces something usable quickly. The developer's job shifted from writing every line to reviewing, adjusting, and integrating.

Database migrations. Translating a data model into SQL migration scripts, complete with indexes and constraints. Describing the schema in plain English and getting back working DDL.

Repetitive patterns. Once we'd established how one CRUD feature worked (API route, service layer, component, types), generating the next five features followed the same pattern. The AI was good at applying established conventions consistently.

Security hardening. We ran the codebase through security review — JWT validation, XSS protection, SQL injection prevention, input sanitization, security headers. The AI identified vulnerabilities and generated fixes, which we then verified.

What AI didn't do well: architectural decisions, complex business logic edge cases, performance optimization, and anything requiring deep understanding of how the pieces fit together. Those still required an experienced developer thinking carefully.

The net effect was that the core application was rebuilt in a few weeks. But here's the part that doesn't make the highlight reel: we spent significantly more time on validation than on building. The goal wasn't just feature parity — it was parity or better on every workflow. That meant going through every screen, every edge case, every business rule from the Power App and verifying the new app handled it at least as well, if not better.

QA Process

Building fast with AI makes a rigorous QA process non-negotiable. The build phase was the easy part — the validation phase was where the real time went. When code is generated rather than hand-typed, you can't rely on the "I wrote it so I understand it" safety net. We implemented:

  • Code review on every generated component. Nothing went in without the developer reading and understanding it line by line.
  • Manual testing against the requirements doc. Every feature was tested against its original requirement, not just "does it compile."
  • Security review. We did a dedicated security pass, treating the app as if it were written by an untrusted junior developer. We found and fixed JWT validation gaps, missing input sanitization, console logging of sensitive data, and transaction handling issues.
  • User acceptance testing. The actual users (the store staff) tested the app against their real workflows before go-live. Their feedback drove the final round of adjustments.
  • Side-by-side comparison. We ran both the Power App and the web app in parallel for two weeks, ensuring the new app handled every scenario the old one did.

What We Gained

The difference was night and day:

Speed. Pages load in under a second. Search is instant, even across tens of thousands of records. No delegation limits, no 500-row caps, no phantom missing results.

Polish. This was the surprise win. With a proper component library and consistent UI patterns, the web app ended up far more polished than the Power App ever was. We could add modal dialogs for editing contacts, sales staff, and store details — all inline, without navigating away from the current screen. Form layouts were consistent across every page. Loading states, validation feedback, error messages — all uniform. In Power Apps, that level of UI consistency is a constant battle against the platform. In a web app with a design system like Ant Design, it's the default.

Security. Proper JWT-based authentication with Azure AD, role-based access control at the API level, parameterized queries, XSS protection, and security headers. The kind of security that's table stakes for a web app but nearly impossible to retrofit onto a Power App.

Maintainability. It's a standard Next.js/TypeScript codebase in a git repository. Any developer who knows React can pick it up. There are pull requests, code reviews, diffs, and a clear project structure. The README explains how to run it. The CLAUDE.md documents the architecture for AI-assisted future development.

Extensibility. Need a new feature? It's a new API route and a new component, following established patterns. Need to change the PDF layout? It's a pdfmake template in code, not a Power Automate flow calling a third-party connector. Need scheduled emails? It's an Azure Function with an HTML template. Need a new modal for managing a related entity? Copy an existing one, adjust the fields, done.

CI/CD. We set up deployment pipelines for both dev and production environments. Push to a branch, it deploys to dev. Merge to main, it goes to production. The Power App had… "publish."


When Power Apps Still Makes Sense

This isn't a "Power Apps is bad" article. Power Apps is the right tool when:

  • Your data fits comfortably in SharePoint or Dataverse limits
  • You have a small number of users (under ~20)
  • The app is simple — a few screens, straightforward data entry, basic reporting
  • You need something running this week, not this quarter
  • The builder will also be the maintainer for the foreseeable future

The camera parts store's app had simply outgrown all of those conditions. It had thousands of records, complex business rules, multiple user roles, PDF generation, automated emails, and needed to be maintainable by someone other than the original builder.


The AI Development Takeaway

The real story here isn't "Power Apps bad, custom code good." It's that AI-assisted development has changed the build-vs-buy calculation. Two years ago, rebuilding this app as a custom web application would have been hard to justify for a small business. The development cost would have been too high relative to the pain of living with the Power App's limitations.

But with Claude Max at $100/month and a single experienced developer, we built a production-quality application in a few weeks. The real investment was in validation — weeks more of meticulous testing to ensure parity or better on every workflow. The developer's role shifted from writing every line of code to architecting the solution, defining the requirements clearly, reviewing generated code, and handling the genuinely complex parts that require human judgment.

That's the pattern I expect to see more of: businesses that started with low-code tools outgrowing them, and AI-assisted development making the jump to custom code practical on a small-business budget. The key ingredients are clear requirements (ideally from having already run the low-code version), an experienced developer who can architect and review, and a disciplined QA process that doesn't trust generated code blindly. The build is the fun part. The validation is where you earn the trust.

The camera parts store is now running on an app that's faster, more secure, easier to extend, and maintainable by any competent developer. Total AI tooling cost: a few months of a $100 subscription. That's not magic — it's the practical reality of knowing what to build and having better tools to build it with.

]]>
https://thomasdaly.net/2026/03/05/power-app-to-web-app-migration/feed/ 1 3413
Adding Font Awesome to SPFx React project https://thomasdaly.net/2022/01/20/adding-font-awesome-to-spfx-react-project/ https://thomasdaly.net/2022/01/20/adding-font-awesome-to-spfx-react-project/#comments Thu, 20 Jan 2022 18:39:00 +0000 https://thomasdaly.net/?p=2688 Introduction

I love the FontAwesome library and use it on many projects. I was a huge fan of FontAwesome 4.0 and had a little struggle moving over to using 5.0 + 6.0 in my projects. I was use to the old way of including the link to the CSS files and using the class names. You can still do that if you wish but this article shows using the React component approach. It’s simple once you’ve done it a few times.

Install React Font Awesome

npm i –save @fortawesome/react-fontawesome

Install the Core Packages [required]

npm i --save @fortawesome/fontawesome-svg-core

Install the Icon Packages, use only the ones you need. If you only use solid icons then choose that. If you are not sure right now then you can include them all and remove them later.

npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-brands-svg-icons

Go Find a Fonthttps://fontawesome.com/icons

Keep in mind that you only have the free library so you can only use those unless you buy the pro license

If you pick an icon and it says ‘Start Using this Pro Icon’ then you need to choose something else or purchase a license

You can filter the free ones by clicking this on the left.

I’ve selected the fish icon, next you need to take note of the type ‘Solid’ or ‘Regular’

Now import the FontAwesomeIcon component and the icon you want from the correct package

This fish icon is in the Solid package.

If I wanted to use something from the regular package like this icon

This font is in ‘Regular’ package you would import it from that package instead

There is intellisense inside visual studio to help get the correct name of the icon

Now the result

If you wanted to use the brand icons you would follow the same approach. I believe that all the brands icons are free.

I love FontAwesome library of icons and use them in many projects. The PRO version unlocks so many more fonts and customizations that it’s worth buying if you plan to use them and want to support this project.

]]>
https://thomasdaly.net/2022/01/20/adding-font-awesome-to-spfx-react-project/feed/ 2 2688
Alerts Header – SPFx project https://thomasdaly.net/2021/10/10/alerts-header-spfx-project/ https://thomasdaly.net/2021/10/10/alerts-header-spfx-project/#comments Sun, 10 Oct 2021 21:24:00 +0000 https://thomasdaly.net/?p=2660

Introduction

This new project is a nice little feature that you can easily add to your SharePoint site to enable site messages or alerts. Having the ability to display site alerts is one of the top 10 most requested features to enhance SharePoint. This customization is built as an SPFx application customizer, with a little bit of UI hacking. This project can be used as-is or as a starting point for you to extend and create your own combinations of notifications, icons and colors.

  • Git Repo
  • Pre-Build SPPKG
  • Overview

    The Alerts Header solution contains an SPFx application customizer and a list schema for the alerts messages. The list will automatically be created for you when the app is added to your site. Currently the app displays messages from the site it’s in but could be easily modified to pull notifications from another site. Initially this was designed to be deployed the the entire intranet and activated on each site. Each site had the option to display ‘local’ alerts, alerts contained within the site and remote alerts, alerts contained at the root site collection “/”. The remote alert functionality has been commented out for simplicity in the Git Hub repo but could be reenabled with just a little effort.

    Implementation

    The List

    The solution installs an ‘Alerts’ list in your site. This list contains a number of required fields to properly display the alerts.

    Title – The first part of the text that is displayed

    Description – The second part of the text that is displayed. The description will fill the reminder of the space and truncate with ‘…’

    Alert Type – The icon that is displayed. Warning, Actionable or Info as show below from top to bottom.

    Link – The navigation url and the text that will be displayed

    Start Date / Time – The date and time that the alert will be displayed

    End Date / Time – The date and time that the alert will no longer be displayed

    Items will immediately start to show up once they are added to the list and hit the duration between the start and end date.

    The Web Part

    This view below shows the alerts application customizer with all three types of alerts and active.

    Icons – The icons used are from the Font Awesome 5.0 free library. You could easily update the code to change out the icons.

    Expand / Collapse – If the alert has a long description it will be truncated once it fills the available screen space. The up / down arrow will allow the user to expand and collapse the alert to read the full description.

    Dismiss / Close – The alert has and close icon at the far right to dismiss the alert. The alert will no longer show during that session. A new session will display the alert again. New Tabs are equivalent to new sessions.

    SharePoint UI Hack – I hinted earlier that I’ve included a bonus UI Hack for free, haha. Take a peek at the picture below. The normal location of an application customizer is at the very top of the page. I did not want the alerts to show up there so I opted to move them to the bottom of the header. This is a non supported customization according to Microsoft. I’m pointing this out for transparency.

    Responsive / Mobile Friendly – The web part was designed with the intention of working across all devices and screen sizes. The functionality is somewhat different on the mobile. Once the item is expanded it can only be closed. I’ve do it this way to intentionally close them out to regain the screen real estate back on the small devices.

    Customizations

    The web part comes with a few notification types. You can add more types directly to the list or to the list schema. After you add new Alert Types you would then need to do a few more updates

    IAlerts.types.ts – Add your new Alert Types to the enumeration

    AltertItem.tsx – Add new conditions for the new Alert Types and the icon you want to use

    AlertItem.module.scss – Copy / Paste an existing Alert Type combination and change the colors to meet your needs.

    Conclusion

    Site Alerts are one of the top 10 features requested from clients and customers to build. I’m happy to be able to post this feature to share even though it’s not 100% Microsoft compliant. The tiny UI hack positions this feature in the location where I felt it belongs. I hope and wish that Microsoft would give us more Zones in which we could target in the application customizers.

    ]]>
    https://thomasdaly.net/2021/10/10/alerts-header-spfx-project/feed/ 15 2660
    SharePoint – SPFX Back to Top https://thomasdaly.net/2021/08/14/sharepoint-spfx-back-to-top/ https://thomasdaly.net/2021/08/14/sharepoint-spfx-back-to-top/#comments Sat, 14 Aug 2021 04:35:03 +0000 https://thomasdaly.net/?p=2605 Introduction

    This post will demonstrate another SharePoint UI hack that is typically seen on many modern sites today. Back to top is a button that will position itself on the right hand bottom corner of the screen once you scroll a bit down. It’s very useful on long pages where your users might want to scroll up quickly and not get mouse scroll rash on their fingers in the process. This is another client request that I hear infrequently but I found this as something that the general population can implement and learn from.

    Before you continue, I must mention that SharePoint UI hacks are unsupported and frowned upon by Microsoft as we cannot rely on the UI [html, classes and DOM] to remain the same over time. With that said you are free to implement and learn more about SPFX and the current DOM given this example. When Microsoft implements a change this would potentially stop working and that is all. Post an issue on github and I would be happy to review and make a change.

    Overview

    I’m not going to go into the nitty gritty details of the code in this article, you can look at the code for that. This customization is based on an SPFX application customizer. This application customizer injects a button when the users scrolls past a certain point. The button would then jump the user back to the top of the scroll region.

    The scroll region might change at some point for SharePoint, rendering this customization ineffective.

    Implementation

    In the SPFX application customizer there is some code to detect the scroll event. There is also some code to detect the page navigation event as it’s somewhat tricky. When users navigate away from a page application customizers are not re-rendered. There is also some code to detect a navigation event and refresh the scroll area so that this SharePoint UI hack continues to work.

    The current scroll region is defined in the BackToTop.tsx. The current scroll region that is the target can be seen below.

    The customization is also theme aware and will respect the primary color of the theme.

    Installation

    As with many github solution you can always clone the repo and make any changes you desire to the solution, check out the REPO. Alternatively you can take the latest build and install it into your app catalog.

    Once you install the solution into your app catalog it will be available as an App on your site. It’s as simple as that.

    Summary

    I hope you enjoy this, yet another SharePoint UI Hack. It’s a great little enhancement if you have long content and want to scroll back up to the top of the page quickly. Feel free to use the github code to inspect how it’s implemented, make changes or enhancements or just implement it as the package is available for you. I’ll mention again that SharePoint UI hacks are not supported by Microsoft so don’t ask them for support. If you implement this you should always remove it before troubleshooting other issues. Generally it’s safe to apply as it won’t adjust or change anything permanently within your site, but take note that if there is a change with the SharePoint UI it could stop working as some point in the future. Please feel free to reach out or comment if your feedback or experience.

    ]]>
    https://thomasdaly.net/2021/08/14/sharepoint-spfx-back-to-top/feed/ 6 2605
    Create Site Collection App Catalog https://thomasdaly.net/2021/05/30/create-site-collection-app-catalog/ https://thomasdaly.net/2021/05/30/create-site-collection-app-catalog/#respond Sun, 30 May 2021 03:09:09 +0000 https://thomasdaly.net/?p=2596 I’m posting this quick tip on how to create a site collection based app catalog with PowerShell. I used this over and over when I’m developing and I think it will be helpful for many people to have a quick script to be able to execute.

    I’m using the SharePoint Online Management Shell for this script. You simply need to replace the domain of your tenant and execute.

    #configuration
    $domain = "YOURDOMAIN"; #{domain}.sharepoint.com
    $targetSite = "your site"; #https://{domain}.sharepoint.com/sites/{targetSite}
    #script
    $siteUrl =  "https://$domain.sharepoint.com/sites/$targetSite"
    $adminSiteUrl = "https://$domain-admin.sharepoint.com"
    Connect-SPOService -url $adminSiteUrl
    $site = Get-SPOSite $siteUrl
    Add-SPOSiteCollectionAppCatalog -Site $site

    Enjoy!

    ]]>
    https://thomasdaly.net/2021/05/30/create-site-collection-app-catalog/feed/ 0 2596
    PnP PowerShell V4 Provisioning with Azure Functions V3 and Power Automate / Flow https://thomasdaly.net/2021/04/09/pnp-powershell-v4-provisioning-with-azure-functions-v3-and-power-automate/ https://thomasdaly.net/2021/04/09/pnp-powershell-v4-provisioning-with-azure-functions-v3-and-power-automate/#respond Fri, 09 Apr 2021 17:56:16 +0000 https://thomasdaly.net/?p=2357 Introduction

    Trying out the new way to Provision via PnP PowerShell V4 and Azure Functions V3 was a little challenging at first. There are some of the difference and issues that I encountered which I could not get to work correctly. I happen to be one of the early ones that need to do this because Azure had made a change to their interface which prevented using PnP PowerShell V3 and the Microsoft documentation was not updated. Since that time and now Microsoft has updated their online documentation with examples of the exact steps. I’ve decided to post the process I used to get everything working but I still suggest reading the official Microsoft article as well.

    Install PnP.PowerShell

    1. Open PowerShell 7 (if not installed, please install from here – https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-7.1)
    2. Run the following cmdlet to install the PnP.PowerShell module

    Install-Module -Name "PnP.PowerShell" -RequiredVersion "1.5.0"

    Set up app-only access to your tenant

    1. Open PowerShell 7 and run the following cmdlets to register an Azure App

    Select the appropriate tenant name before running.

    Register-PnPAzureADApp -ApplicationName "PnPProvisioningDemo" -Tenant "<tenant-name>.onmicrosoft.com" -DeviceLogin -OutPath .

    Follow the instructions to complete the Azure AD App registration. You will need to open a browser and enter the url above and supply the code. You will also be asked to log in.

    This will now take 60 seconds to create the certificate in the out path that was specified in the above command

    • After the cmdlet is executed. It will create a PFX file. Keep this file saved, as we will have to upload it to Azure function.
    • Login to https://aad.portal.azure.com/
    • Click -> Azure Active Directory -> App Registration
    • Search for ‘PnPProvisioningDemo’ and the select the App Registration which got created. Copy the Application (client) ID from here.
    • Click API Permission
    • Click API Permissions, and Grant admin consent for {tenant}
    • In the small popup click Yes to approve this app those permissions.

    Create the Azure function

    • Type in ‘Function’ and select ‘Function App’
    • In the Project Details, click on Create New, for the Resource Group
    • Type in ‘PnPProvisioningDemo’ for the resource group name, click OK

    • Enter in a Function App name – this will need to be unique, Runtime Stack, Version and select a Region in which you want the resources inr its created, navigate to your new function App
    • From the dropdown menu, select host.json file and add the following entry and save it.
    • Click ‘Review + create’
    • Click ‘Create’

    After a few moments all of the assets will be created.

    • Click ‘Go To Resource’
    • Next click on ‘App Files’
    • I typically bump the timeout to 10 mins for the Azure Function from the default 5 minutes.
    • In the App Files > host.json, add this line. Make sure to use a ‘,’ on the previous line
    "functionTimeout": "00:10:00"
    • Click Save to lock in the changes
    • Click on the dropdown and select requirements.psd1
    • You can remove the ‘Az’ = ‘5.*’ as it won’t be used
    • Click Save to lock in the changes
    • Create a new Azure Function Functions > Add:
    • Create a new HTTP Trigger function, by selecting the options as shown below
    • Scroll down, name the function InvokePnPSiteTemplate
    • Set Authorization level, Anonymous
    • Click Save

    Upload the code for your Azure Function

    • Go to the Function App main screen and select Advanced Tools
    • Click “Go”, this will open Kudu
    • Select “PowerShell” from the Debug Console menu at the top.
    • Navigate to site\wwwroot\InvokePnPSiteTemplate

    This is where it gets complicated. Hold on to your hats.

    Extract the AzureFunCode.zip file provided as a sample.

    • Add your PnP template into the pnp-template.xml file
    • Update run.ps1
      • set $filePath to the correct drive [C or D] that you see in the azure portal
    • set $AzAppId to match your Azure App Id
      • set $certFileName to match your exported certificate
      • set $tenant to match your tenant prefix
    • Drag & drop the files to the site\wwwroot\InvokePnPSiteTemplate directory

    NOTE: If you want to use a different version of PnP PowerShell then you can generate the PnP.PowerShell files for upload from the command line. Use the folder that is exported to upload.

    Create a new folder and then execute the following commands

    Save-Module PnP.PowerShell . 
    1. After the code is updated. Navigate back to Function screen on Azure portal and click on ‘Get Function Url’ and copy the URL

    Create the Flow

    1. Go to the Power Automate site, sign in, and choose Create -> Automated Cloud Flow
    2. Click Skip at this screen
    1. Click ‘untitled’ and give the Flow a name, PnPProvisioningDemo
    1. Search for Request, and select Request – When a HTTP Request is received.
    1. Enter the following JSON as your request body:
    {
        "type": "object",
        "properties": {
            "webUrl": {
                "type": "string"
            },
            "parameters": {
                "type": "object",
                "properties": {
                    "event": {
                        "type": "string"
                    },
                    "product": {
                        "type": "string"
                    }
                }
            }
        }
    }
    • Select + New Step and choose Add an action.
    • In the search box type ‘http’ and click HTTP
    • Configure the properties as shown below.
    • You also need the Azure Function, HTTP endpoint. This is in the Azure Function, Overview screen, click Get Function Url
    • Copy this http endpoint to then paste into the flow
    • Choose Save Flow. This will generate the URL that you will copy in the next step.
    • Back in the Flow, Paste the URI in this box
    • Save the Flow
    • Once save is completed, click back on the first step and copy the Flow HTTP Post Url. Save this for later

    Create the Site Design

    In this section we are going to create a SharePoint Site Design, based on a Team Site template.

    1. Open SharePoint Online Management Shell and connect to your tenant using Connect-SPOService
    Connect-SPOService -Url https://[yourtenant]-admin.sharepoint.com
    • Update the URL property in the code below. Set the url property to the value you copied when you created the flow
    {
      "$schema": "schema.json",
      "actions": [
       {
          "verb": "triggerFlow",
          "url": "{UPDATE THE FLOW URL HERE}",
          "name": "Apply PnP Template",
          "parameters": {}
        }
      ],
      "bindata": {},
      "version": 1
    }
    
    • Select the JSON again and copy it, this will put it on your ‘clipboard
    • Open PowerShell and enter the following to copy the script into a variable and create the site script:
    $script = Get-Clipboard -Raw
    #before the next step verify you have data in $script by typing this
    $script
    #if that is empty try the copy and run the first line again.
    Add-SPOSiteScript -Title "Contoso Team Config" -Content $script
    Get-SPOSiteScript
    • You will see a list of one or more site scripts, including the site script you just created. Select the ID of the site script that you created and copy it to the clipboard.
    • Use the following command to create the site design:
    Add-SPOSiteDesign -Title "Contoso Team" -SiteScripts {Paste the ID of the Site Script here} -WebTemplate "64"

    Conclusion

    That completes all the necessary steps to get the basics up and running. Hope this was helpful walkthrough to make your first time getting started a little easier. The screens are constantly changing in Office 365 but the basics steps should remain the same. Leave a comment below if you get stuck or have a suggestion.

    ]]>
    https://thomasdaly.net/2021/04/09/pnp-powershell-v4-provisioning-with-azure-functions-v3-and-power-automate/feed/ 0 2357
    PnP PowerShell V4 and Azure Functions V3 – First Look https://thomasdaly.net/2020/12/19/pnp-powershell-v4-and-azure-functions-v3-first-look/ https://thomasdaly.net/2020/12/19/pnp-powershell-v4-and-azure-functions-v3-first-look/#respond Sat, 19 Dec 2020 02:06:59 +0000 https://thomasdaly.net/?p=2258 I’m writing this blog to share my experiences with working the latest version of PnP PowerShell V4 this .NET Core version and Azure Functions V3.

    Background

    Up until a few weeks ago I was able to write PnP PowerShell provisioning code and run it in an Azure Function V1 to do almost anything I wanted in my SharePoint site. I was receiving warnings that Azure was going to be deprecating the V1 functions but not pay much attention. This forced me to try using the new way of using the Azure V3 function which run PowerShell .NET Core and the new PnP PowerShell libraries built on .NET Core.

    Azure has removed all ways through the UI to use experiment languages. You can set the PowerShell Azure function to run in V1 mode, however the toggle switch to enable experimental languages has been removed. Rendering this article is no longer valid – https://docs.microsoft.com/en-us/sharepoint/dev/declarative-customization/site-design-pnp-provisioning#create-the-azure-function [UPDATE: 11/25/2020 – Microsoft has updated that article]

    The now older version of PnP PowerShell is referred to as PnP PowerShell V3 which is built on the CSOM for .NET 4.5 Libraries. This runs in PowerShell up V5

    The newer PnP PowerShell is referred to as PnP PowerShell V4 which is built on the CSOM for .NET Standard 2.0. This run in PowerShell V7.

    You can read more about this here – https://github.com/pnp/PnP-PowerShell. A tremendous amount of work has been done to make the libraries available for .NET Core. It’s time we all start using the new V4 version and help the project by submitting defects and contributing.

    My Experience

    I was faced with many issues which I will outline. While this was my first time experimenting with the new PnP PowerShell and the Azure Functions V3 it was not an easy one. I wrote this article before changes to official Microsoft documentations updates and had to learn by trial and error which can be tedious.

    1. On Demand Loading – the function now use a requirements.ps1 file that contains a manifest of the libraries that your function will using. This is how you load the PnP PowerShell libraries. The files will be downloaded on demand when your function is initialized and will stay in memory until your function goes to sleep.
    2. Problems Connecting / Authentication – I believe there is some issues with Authentication as I spent many hours trying various combinations to work. I would recommend using a certificate as that is the only way I was able to get the functions to work correctly. I think it’s possible some pieces of PnP PowerShell V4 are not working 100% in Azure Functions. I was using 0.2.15-nightly
    3. Different Commands – not a huge deal but you need to be aware that some of the commandlets have changed and should update them accordingly. It’s well documented just be aware.
    4. Pre-Release Version – the PnP PowerShell is still currently pre-release and scheduled to be GA in Jan 2021. We can’t do anything about this, if you need this functionality you will be using the pre-release version. It shouldn’t matter too much if the functionality works with the version of PnP PowerShell that you built it on then you are fine. Somethings may or may not work – always send feedback in the github repo’s for issues.
    5. Timeouts – the on demand loading seemed to timeout my functions [default 5 mins max on Azure Function runtime] this was a problem with the libraries taking 3-4 minutes to load and my code only had 1 minute to finish.

    In the host.json file I had increased the timeout to 10 minutes to account for this. At the time of this article I have not tried to upload and host the libraries in my function [more on this in another article]

    updating host.json file

    Summary

    Since writing this article I have made more progress in this area. I will post a follow up in the next week with a walk through of setting up a site script to kick off a Power Automate Flow, that runs the Azure Function to make some modifications to a SharePoint site. I’ve had a lot of fun playing around and experimenting with different flows and can now appreciate this method along with the power behind it. I am excited to see the progress here and looking forward to future updates from the PnP PowerShell team!

    ]]>
    https://thomasdaly.net/2020/12/19/pnp-powershell-v4-and-azure-functions-v3-first-look/feed/ 0 2258
    Global Microsoft 365 (M365) Developer Bootcamp NY, NJ & PA https://thomasdaly.net/2020/11/05/global-microsoft-365-m365-developer-bootcamp-ny-nj-pa/ https://thomasdaly.net/2020/11/05/global-microsoft-365-m365-developer-bootcamp-ny-nj-pa/#respond Thu, 05 Nov 2020 16:49:34 +0000 https://thomasdaly.net/?p=2157 Global Microsoft 365 (M365) Developer Bootcamps are happening in major cities all over the world from October 1st through November 30, 2020. These are typically free, one day events held in person at your nearest Microsoft office or even a local companies office. The M365 Developer Bootcamps are designed to be an interactive, deep dive event focusing on developing within the M365 platform. Check out the official website to see if you are having one in your area.

    Each event is run by a local organizer who typically has a small team of other co-organizers. Typically the organizer is someone you might already know that runs your local M365/O365 User Group. They may be a consultant, an employee for a company, self employed, or maybe even Microsoft employee. The point being is that it’s regular people that run these events and not Microsoft. Microsoft plays a huge rule in organizing the support structure behind the event helping with awareness, the global website, supply the facility, subject matter experts, and securing global sponsors for schwag or most importantly lunch. Microsoft is the backbone behind the events while your local community champions obtain speakers, produce schedules, coordinate food/refreshments, generate agenda or content.

    M365 is an ever growing platform of software applications. These events help beginners bridge the gap to entry into building applications using the technologies involved. Each event will have it’s own agenda but would focus on one or many of the follow: Azure, SharePoint, Power Automate, Power Apps, Power BI or MS Teams.

    This will be our 4th year running our local bootcamp. The first two years we ran the events in New Jersey and New York. This last year I’ve connect with the team out in Pennsylvania and we’ve all joined forces to run the event in NJ, NY and PA for 2019 and 2020. This year’s event will be 100% virtual and open for any one to join us. Initially the intention was to keep it to 30 people hands on but the registration numbers grew to over 700. This is beyond our expectations but we will still deliver the same content in a deep dive demonstration format with the correct subject matter experts for each of the technologies.

    Our NJ, NY, PA bootcamp will be 3 dates each with their own agenda. Register Now

    Nov 7, 2020 – MS Teams Governance

    Nov 14, 2020 – Power Platform [Power Apps, Power Automate, Power BI]

    Nov 21, 2020 – SharePoint Framework [SPFx]

    Shout out to our organizer team. Michael Mukalian [Microsoft], Jason Rivera, Manpreet Singh, James Novak.

    ]]>
    https://thomasdaly.net/2020/11/05/global-microsoft-365-m365-developer-bootcamp-ny-nj-pa/feed/ 0 2157
    Using JSOM to update Taxonomy Fields in SPFx https://thomasdaly.net/2020/10/29/using-jsom-to-update-taxonomy-fields-in-spfx/ https://thomasdaly.net/2020/10/29/using-jsom-to-update-taxonomy-fields-in-spfx/#respond Thu, 29 Oct 2020 08:00:37 +0000 https://thomasdaly.net/?p=2119 Introduction

    Last week I was working on a project that involved setting some managed meta data fields, both the single value and multi values fields. I typically use PnP-JS to streamline the calls to SharePoint however I found there are issues with PNP-Js and managed meta data fields. It’s been said that MMD fields are unable to be set with REST calls (PnP-Js uses REST). There are mixed reviews out there regarding whether it works or not. For me it did not work. My particular scenario involved update 6 single value fields and 1 multi value fields at the same time. While investigating I reach out to a friend Beau Cameron to verify my code as he wrote 2 great articles for single value fields and multi value fields. The results were to flaky using REST so the decision was made to use JSOM. In this article I will share the end code as there were a few things I want to document for future reference.

    Using JSOM in SPFx

    Before you can start writing the code you must first get your project setup in order to work with the JSOM libraries.

    It’s a good idea to read up on the official guidance as this method follows it but I prefer to load the libraries differently. The Microsoft article loads the libraries in the configuration as external resource but you must specific the full url of your site and I prefer not to lock that value of the site url into a configuration file that would prevent me from using this in another tenant without rebuilding.

    Install the Typings

    install project dependencies

    npm install @types/microsoft-ajax @types/sharepoint --save-dev
    

    In the code editor, open the ./tsconfig.json file, and in the types property, after the webpack-env entry, add references to microsoft-ajax and sharepoint

    {
      "compilerOptions": {
        // ...
        "types": [
          "es6-promise",
          "es6-collections",
          "webpack-env",
          "microsoft-ajax",
          "sharepoint"
        ]
      }
    }

    Register the Scripts

    In the file you wish to use the JSOM library, require the scripts.

    require('sp-init'); 
    require('microsoft-ajax'); 
    require('sp-runtime'); 
    require('sharepoint');

    Loading the JSOM libraries

    In the previous section I mentioned that I prefer to dynamically load the libraries to avoid hard coding the site url into the configuration files. In the code below is how I am loading the libraries dynamically.

    Note: In the state I have 2 values

    1. loadingScripts: boolean, intially set to true
    2. Errors: any, initially set to []

    This way you can wait until the scripts are loading to rendering fields that require them and I’m also collecting the errors for later consumption.

    public componentDidMount() {
        this.loadScripts();
      }
    
    
      public getSiteCollectionUrl(): string {
        let baseUrl = window.location.protocol + "//" + window.location.host;
        const pathname = window.location.pathname;
        const siteCollectionDetector = "/sites/";
        if (pathname.indexOf(siteCollectionDetector) >= 0) {
          baseUrl += pathname.substring(
            0,
            pathname.indexOf("/", siteCollectionDetector.length)
          );
        }
        return baseUrl;
      }
    
      private loadScripts() {
        const siteColUrl = this.getSiteCollectionUrl();
        try {
          SPComponentLoader.loadScript(siteColUrl + "/_layouts/15/init.js", {
            globalExportsName: "$_global_init",
          })
            .then(
              (): Promise<{}> => {
                return SPComponentLoader.loadScript(
                  siteColUrl + "/_layouts/15/MicrosoftAjax.js",
                  {
                    globalExportsName: "Sys",
                  }
                );
              }
            )
            .then(
              (): Promise<{}> => {
                return SPComponentLoader.loadScript(
                  siteColUrl + "/_layouts/15/SP.Runtime.js",
                  {
                    globalExportsName: "SP",
                  }
                );
              }
            )
            .then(
              (): Promise<{}> => {
                return SPComponentLoader.loadScript(
                  siteColUrl + "/_layouts/15/SP.js",
                  {
                    globalExportsName: "SP",
                  }
                );
              }
            )
            .then(
              (): Promise<{}> => {
                return SPComponentLoader.loadScript(
                  siteColUrl + "/_layouts/15/SP.taxonomy.js",
                  {
                    globalExportsName: "SP",
                  }
                );
              }
            )
            .then((): void => {
              this.setState({ loadingScripts: false });
            })
            .catch((reason: any) => {
              this.setState({
                loadingScripts: false,
                errors: [...this.state.errors, reason],
              });
            });
        } catch (error) {
          this.setState({
            loadingScripts: false,
            errors: [...this.state.errors, error],
          });
        }
      }
    

    State values & Taxonomy Controls

    To work with the taxonomy fields I will be using the Taxonomy Picker from the PnP React Controls

                  <TaxonomyPicker
                    allowMultipleSelections={false}
                    termsetNameOrID="By Whom"
                    label="By Whom"
                    values={this.state.byWhom}
                    context={this.props.context}
                    onValueChanged={(pickerTerms) => {
                      this.setState({ byWhom: pickerTerms });
                    }}
                    loading={this.state.loading}
                  />

    Setting up the JSOM call

    This function I I am preparing the JSOM context getting the list and item and will be updating a number of MMD fields. My state values for the MMD are IPickerTerms from PnP React Controls.

    Warning: You can see I have a function called getItem that will retrieve the list item using CAML query. I must do this in order to get a ‘fresh’ version of the item. If you use the JSOM getItemById you will receive a cached copy and performing an update on it will throw an error that it is unable to make a change.  I also read that you can avoid that error by calling executeQueryAsync right after getItemById so it’s ‘fresh’ but I went this way.

    private updateItemMetaData = async (siteUrl: string, listName: string, itemId: number) => {
        console.log(siteUrl, listName, itemId);
        return new Promise(async (resolve, reject) => {
          let context = new SP.ClientContext(siteUrl);
          let list = context.get_web().get_lists().getByTitle(listName);
          console.log("ctx list", list);
          let item = await this.getItem(context, list, itemId); 
          console.log("ctx item", item);
          this.setTaxFieldProperty(context, item, list, "ByWhom", this.state.byWhom, true);
          this.setTaxFieldProperty(context, item, list, "SubCategory", this.state.subCategory, true);
          this.setTaxFieldProperty(context, item, list, "DocumentStatus", this.state.documentStatus, true);
          this.setTaxFieldProperty(context, item, list, "SubmittalAction", this.state.submittalAction, true);
          this.setTaxFieldProperty(context, item, list, "Phase", this.state.gaPhase, true);
          this.setTaxFieldProperty(context, item, list, "Agencies", this.state.agencies, true);
          this.setTaxFieldProperty(context, item, list, "Trades", this.state.trades, false);
          item.set_item("ActionDate", this.state.actionDate);
          item.update();
          context.load(item);
          context.executeQueryAsync(
            () => {
              console.log("success", item.get_id());
              resolve();
            },
            (sender, args) => {
              console.log("fail", args.get_message());
              resolve();
            }
          );
        });
      };
    
     private getItem = async (context: SP.ClientContext, list: SP.List, itemId: number): Promise<SP.ListItem> => {
        return new Promise((resolve, reject) => {
          const query = `<View Scope='Recursive'><Query><Where><Eq><FieldRef Name=\"ID\" /><Value Type=\"Integer\">${itemId}</Value></Eq></Where></Query></View>`;
          let camlQuery = new SP.CamlQuery();
          camlQuery.set_viewXml(query);
          let allItems = list.getItems(camlQuery);
          context.load(allItems, 'Include(Id)');
          context.executeQueryAsync(
            () => {
              console.log("success", allItems);
              resolve(allItems.get_item(0));
            },
            (sender, args) => {
              console.log("fail", args.get_message());
              resolve();
            }
          );
        });
      }
    

    The Update Function

    In this section we will get the field from the list and update it correctly if it’s a single value or a multi value. Each type must be handled differently. For single value we can set the term as an object but for the multi value we must create a string of the terms. Creating the string of terms is done in the getTermString function.

    private setTaxFieldProperty = (
        context: SP.ClientContext,
        item: SP.ListItem,
        list: SP.List,
        fieldName: string,
        fieldValues: IPickerTerms,
        isSingle: boolean
      ) => {
        let field = list.get_fields().getByInternalNameOrTitle(fieldName);
        let taxField = context.castTo(field, SP.Taxonomy.TaxonomyField) as SP.Taxonomy.TaxonomyField;
        if (isSingle) {
          if (fieldValues && fieldValues.length >= 1) {
            let termValue = new SP.Taxonomy.TaxonomyFieldValue();
            termValue.set_label(fieldValues[0].name);
            termValue.set_termGuid(new SP.Guid(fieldValues[0].key));
            termValue.set_wssId(-1);
            taxField.setFieldValueByValue(item, termValue);
          }
          else {
            taxField.validateSetValue(item, null);
          }
        }
        else {
          if(fieldValues && fieldValues.length >= 1) {
            let termString = this.getTermString(fieldValues);
            console.log(termString);
            let termValueCollection = new SP.Taxonomy.TaxonomyFieldValueCollection(context, termString, taxField);
            taxField.setFieldValueByValueCollection(item, termValueCollection);
          }
          else {
            taxField.validateSetValue(item, null);
          }
        }
      };
    
      private getTermString(pickerTerms) {
        let termString = "";
        pickerTerms.forEach((term) => {
          termString += `-1;#${term["name"]}|${term["key"]};#`;
        });
        return termString.slice(0, -2);
      }

    Summary

    This proof of concept was somewhat challenging to work with the taxonomy fields. So much so that I had to blog it at least for my reference on how I dealt with it. The code above is not meant to be a full solution just a snippet of the key pieces to reliable working with the taxonomy fields. If you are interested in the full look at the code you can check it out here, https://github.com/tom-daly/demos/tree/master/docprocessor. It goes without saying that it’s test/demo code.

    ]]>
    https://thomasdaly.net/2020/10/29/using-jsom-to-update-taxonomy-fields-in-spfx/feed/ 0 2119
    Making SPFx Web Parts Searchable – Lessons Learned https://thomasdaly.net/2020/06/19/making-spfx-web-parts-searchable-lessons-learned/ https://thomasdaly.net/2020/06/19/making-spfx-web-parts-searchable-lessons-learned/#comments Fri, 19 Jun 2020 08:00:00 +0000 https://thomasdaly.net/?p=1892 I recently built a handful of amazing content entry web parts for Office 365 using SharePoint Framework (SPFx). The client I was working with wanted various combinations of responsive columns, rich text editor, links, and pictures. This ultimately led to building out a few custom web parts to meet their needs.

    About a months later, as we started focusing on search, I noticed a huge problem. The contents of my web parts were not searchable! We couldn’t find anything. This was the first time I had to ensure the content was indexed on a modern page. It’s not the same as classic – lesson learned.

    Step 1 – Mark Your SPFx Web Part Properties

    I came across the official Microsoft guidance which tells you to mark your properties. I will not go into details of the article, you can read the full article here.

    As you can clearly see you need to mark the web parts as image, html, string, or link. If you have a combination of those then use html.

    Microsoft’s Example

    Yet another issue. What about web parts that have arrays, objects, arrays of objects or props that leverage lists as a data source?

    How to handle an arrays / objects / both … simplify

    Here is an example from one web part. I have a web part property which is an Array of Objects, that have multiple properties. According to the guidance, how do you mark it?

    There is no immediate way to tag this properties where it would get indexed correctly and make the content searchable. So how do you solve this? The list below are the main points.

    1. Create a new property ‘searchableText’
    2. Mark the new property ‘isHtmlString’
    3. Create an update function

    Step 1 and 2 are very straightforward. These are used inside the main .ts file of the web part. It will not be passed into the child components.

    Create New Property
    Mark New Property

    The 3rd step is to pass a function to child component. When the items are changed inside the web part, it will loop over the items and create an html string of the text & links to update searchableText. Once that is complete we will be able to search on the content of the web part properly.

    The purpose of this example is give you an idea of converting your objects / arrays into a String. It’s a little extra work but making it worthwhile when users can search the contents of your web parts. I have yet to figure out a way to make list based web part content searchable on the page. [I do not want to hit the list items directly in many cases] That might be a blog for another time.

    ]]>
    https://thomasdaly.net/2020/06/19/making-spfx-web-parts-searchable-lessons-learned/feed/ 6 1892