How to rename a SharePoint Online site and re-use the URL

In this post we will explore how to rename SharePoint Online sites and re-use the original URL.

  1. Intro
  2. How to use an old site URL
  3. More information

Intro

Have you ever changed the name of a SharePoint Online site and tried to re-use the old URL for a new site? If you have you have probably encountered an issue where SharePoint admin center will tell you that URL is still in use. Well this is because the URL is, in fact still in use in the form of a redirect.

Old site URL is unavailable after renaming a SharePoint site.

In 2019, Microsoft introduced the site rename feature within the SharePoint admin center, which also updated site URLs too. Once a URL is updated for a given site, or moved to a different geo location, or as part of a site swap – a redirect is automatically created to ensure any links that were pointing to the prior URL continue to work.

How to use an old site URL

To use on old site URL you need to run the following PowerShell command. Note: You will need the SharePoint administrator role to run this command:

  • Open the SharePoint Online Management Shell and run the following command:
Connect-SPOService -Url https://[TENANT]-admin.sharepoint.com

Remove-SPOSite -Identity https://contoso.sharepoint.com/sites/OldSiteName
  • Confirm you want to delete the redirect

Check your URL has been deleted by trying to browse the URL, if you get a 404 error it has been successful.

More information

The Remove-SPO site command is actually defined as “Sends a SharePoint Online site collection to the SharePoint Online Recycle Bin” in the Microsoft documentation reference guide.

Microsoft documentation says that a redirect template (Template type: REDIRECTSITE#0) is applied when the site URL is updated, which contains special headers and logic to redirect your browser requests to the new site.


How to deploy a flow across all document libraries in SharePoint

In this post we look at a specific use case for needing a Power Automate flow to be available across all of SharePoint, some of the current limitations of Power Automate and how to overcome them using Encodian Trigr.

Intro

Most organisations will have a central location where they store policies, procedures and guidance documents. For most, this would form part of their corporate intranet solution on SharePoint/ Teams, but you also might find yourself getting asked by users “how do I get my policies from my team/ collaborative locations into the the corporate policy library?” – not such an easy answer!

The problem

Policies generally can be created by individuals and small groups of people, within the Teams/ SharePoint sites that they have access to for collaboration. For these people, having a way to publish relevant documents from their own collaboration sites to a central policy location in a consistent way makes a great deal of sense and you may think Power Automate is the way to achieve it…however this is not the case! Power Automate flows have a 1:1 relationship with the lists or libraries they are triggered from, so there is no easy way to do this with out-the-box capabilities.

Power Automate is also unable to manually trigger a flow when selecting multiple documents, something else that my scenario requires. This is where Encodian Trigr can really bridge the gap. Trigr makes Power Automate flows available across SharePoint, allowing users to access and run flows from within any library or list.

The solution

In my example, I needed to have a flow available across all SharePoint/ Team sites, which would allow all users to select one-to-many documents and publish to a central policy upload center for approval – and here is how I used Encodian Trigr to achieve it:

Install Trigr

  • There is a comprehensive guide on how to deploy the Encodian Trigr app available here. I followed these steps to get Trigr setup in my environment.

Create the flow

  • Navigate to https://make.powerautomate.com/
  • Under My flows > + New flow > Automated cloud flow
  • Give your flow a name
  • Search and select the Encodian ‘When a user runs a Trigr‘ Power Automate trigger action
  • Click create
Create an automated cloud flow and select the ‘When a user runs a Trigr’ Power Automate trigger action.

NOTE: This was my first time using Encodian Trigr, so I was prompted to provide a connection name and an API key. For the connection name I just entered ‘Encodian connection for flow’, and the API key will be in your welcome email, or available via this link: https://account.encodian.com/trigr/apikey.

  • You will now see the ‘When a user runs a Trigr‘ trigger action. Enter a title & description for your flow
Add a title and description to your ‘When a user runs a Trigr’ trigger action.

The ‘When a user runs a Trigr’ Power Automate trigger action provides all of the properties relevant to the files which were selected by the user when the Trigr action was started.

For the next step we are going to use the get file content action. Due to a current limitation of Power Automate, dynamic values are not immediately accessible by the SharePoint action. To get around this, we will just enter hard-coded values at first, then replace them with dynamic values once we have finished configuring the flow.

Get file content

  • Add a get file content action
  • Hard-code the site address of the site where the files are saved that you wish to publish
  • Hard-code the file identifier as just the ID number for now

Send an email

  • Add a send an email action with the the following confirguration:
    • To: User Email Address dynamic content from the ‘when a user runs a Trigr’ trigger action’
    • Subject: Anything you wish
    • Body: Anything you wish
Add a send an email action if required to notify the user who triggered the flow they have submitted a policy document.

Update get file content

Now we will go back to our get file content action and update the hard-coded values with dynamic content from the ‘when a user runs a Trigr’ trigger action’:

  • Open the get file content action
  • Update Site Address to Site Address dynamic content from the ‘when a user runs a Trigr’ trigger action’
  • Update File Identifier to File Identifiers

NOTE: This will create put all your actions in an apply to each loop, which is what we want if we want to be able to process multiple documents. The File Identifier dynamic content will be replaced with the current item from the apply to each loop.

Update the get file content action to include the current item from the apply to each loop.

Copy file

  • Add a copy file action after the update get file content action and configure with the following properties:
    • Current site address: site address dynamic content from the ‘when a user runs a Trigr’ trigger action
    • File to copy: Current item apply to each dynamic content
    • Destination site address: select/ hard-code the destination site address (in my case the policy site)
    • Destination folder: select/ hard-code the destination library/ folder
    • If another file is already there: replace
Add a copy file action to copy the files the flow has been ran against into the policy center.

Create the Trigr action

So far we have installed trigr in our environment, created the flow which will be used and now we need to create the Trigr action in the Encodian account portal:

  • Select the flow that you created earlier and configure with the following settings:
    • Title: This will auto-populate from the data set within the Power Automate Flow, update if required.
    • Description: This will auto-populate from the data set within the Power Automate Flow, update if required.
    • Run Message: Enter a message which will be displayed to the user once the action has been started.
    • Appear in: Tick whether you want your flow to appear in all libraries/ lists or both. You can also decide whether you wish to deploy the flow to all sites or a selection.
    • Press Create

That’s it! Your Trigr action will now be created and your flow will be available across your SharePoint environment.


Microsoft Search demystified! Provide configurable search options in SharePoint, Teams & OneDrive

Microsoft Search is the unified search experience that is prevalent across Microsoft 365. Microsoft Search gives users contextual search results depending on the app they search from, be it form Word or Excel, through to SharePoint, Teams & OneDrive.

Intro

If your familiar with SharePoint search you might think this is a doddle to set up. Know your crawled properties from content sources? Managed metadata from managed properties? Well, this might not help you for modern search as not all contextual app based search results are created equally, specifically Teams and SharePoint…

Within the settings of the M365 admin center there is section called search & intelligence where you can gain access to the following configurable options within Microsoft Search:

  • Acronyms: Admin or system curated acronyms/ abbreviations used by an organisation or team
  • Bookmarks: Helps users find important information with just a search. Each bookmark includes a title, URL,  keywords and a category.
  • Floor plans: Floor plans in Microsoft Search help users find people, rooms, and spaces within a building.
  • Locations: Helps your users find addresses and locate your organization’s buildings by providing an accurate location.
  • Power BI: Support for Power BI dashboards and reports to make it easier for users to find data & analytics
  • Q&A: Creating a Q&A is similar to creating bookmarks. Answer user’s questions instead of just providing a link to a webpage.
  • Result types & verticals: Result types define how search results are shown on the page based on conditions and rules. Search verticals are tabs on the search results page that show results of a specific type or from a select source.

The problem

For all of the above options within Microsoft 365, there is a varied amount of consistency as to how each option displays within specific apps, or in some cases do not display at all. For SharePoint all of the above search options do not display in the default search results of a given site. SharePoint communication or team sites search scope is to search just the site by default. Hub sites will search the hub, plus any associated sites to the hub.

None of the above search options display from within Teams, either via the desktop app or via the browser. There also isn’t a link to organisation, or a way to see all files etc. from the search experience within Teams.

Microsoft documentation says that Acronyms, Bookmarks, Floor plans & Location only show answers on Bing, SharePoint, and Office 365. Power BI search results display in the Windows search box, SharePoint, Office 365, and Bing.

Solutions and workarounds

I only really have a solution as such for SharePoint on this one, as for Teams and OneDrive there is very little you can do to change the search result options as far as I know.

For SharePoint, the only way currently to get these options to show is to change the scope of your site. There are three search scope parameters:

  • 0 – default scope, effectively site
  • 1 – tenant
  • 2 – hub
  • 3 – site

To change the scope of your site search, you need PowerShell/ PnP. You’ll also need to be a SharePoint admin to run it, but if you are you can run this cmdlet to change the scope to tenant wide:

Connect-PnPOnline -Url https://contosodemosg.sharepoint.com/sites/Strategy -UseWebLogin
# this will prompt you to sign into your site. Use the site owner credentials to sign in

$web = Get-PnPWeb
$web.SearchScope = 1
# 1 for Tenant, 2 for Hub, 3 for Site, 0 for default behavior

$web.Update()
Invoke-PnPQuery

Once your cmdlet has run successfully, you’ll notice the change in the site targeted as when a search is initiated it will default to the “organisation” wide search results by default, showing any of the configurable options you’ve set up within Microsoft Search.

SharePoint site configured to have a tenant-wide search scope, showing a custom configured location within the search results page.

For OneDrive on web, the search experience is subtly different than SharePoint or Teams. When you search there are the following options via a drop-down: My files, All files, or Whole organisation. All files is the default setting, but you can select the scope of your search before hitting enter, or change the scope after your results are returned.

In OneDrive for web, there is a toggle within the search bar that lets you select the search scope.

Things to consider

#1 Intranet search results will not be scoped

If you are building an intranet solution or already have one in place, changing the search scope will bring in search results from Teams, SharePoint & OneDrive, so it will not be ring fenced to just “intranet” content.

#2 All sites which require the scope change will need updating

You will need to change the scope for all the sites you wish to search tenant wide. So if you have an intranet solution, all sites will need this change applying.

#3 Ensure you have the correct admin roles

When I was creating Bookmarks/ Answers/ Locations etc. I originally gave my account the Search Administrator role. This displayed more config options within search & intelligence, but when it came to publishing, it threw up the dreaded “something went wrong” error. To be able to publish, I needed to also apply the Search Editor role.


How to create an address book in SharePoint Online

In this post we take a look at a few of the different ways an address books can be created in SharePoint Online.

  1. Intro
  2. Example 1 – dynamic list filtering
    1. Create the alphabet list
    2. Create the contacts list
    3. Create the address book page
    4. Bonus! Populate the contacts list from M365 user data
  3. Example 2 – Custom SharePoint Framework address book web parts
    1. People Directory SPFx sample
    2. Organization Directory SPFx sample

Intro

The idea for this post came from reader George’s comment asking to do a demo of how an address book can be created in SharePoint. From my research there is already quite a bit out there on how to do this already, but here are my two ways to create an address book.

Example 1 – dynamic list filtering

This example is a pure out of the box, SharePoint solution for creating an address book feature. For this example we will use dynamic filtering between two lists to show a certain set of values when an option is selected.

To start we will need to create two lists:

  1. An alphabet list that will act as our index, storing all the letters of the alphabet as list items.
  2. A contacts list that will be the list that contains all the address book data.

Create the alphabet list

  • Start by creating a new custom list > call the list Alphabet.
  • Create new items in the list for each letter of the alphabet. I used ‘edit in grid mode’ to speed this process up
Create an Alphabet list to store the A-Z letters as list items.
  • Change the view to a gallery view
  • From here, format the list view using either the example JSON list view formatting below, or create your own to style the A-Z list to display each letter as a button
{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/tile-formatting.schema.json",
  "height": 45,
  "width": 53,
  "hideSelection": false,
  "fillHorizontally": true,
  "formatter": {
    "elmType": "div",
    "attributes": {
      "class": "sp-card-container"
    },
    "children": [
      {
        "elmType": "button",
        "attributes": {
          "class": "sp-card-defaultClickButton",
          "role": "presentation"
        },
        "customRowAction": {
          "action": "defaultClick"
        }
      },
      {
        "elmType": "div",
        "attributes": {
          "class": "ms-bgColor-white sp-css-borderColor-neutralLight sp-card-borderHighlight sp-card-subContainer"
        },
        "children": [
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-lastTextColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$Title]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content sp-card-highlightedContent"
                },
                "txtContent": "=[$Title]",
                "style": {
                  "text-align": "left",
                  "font-size": "0.8em",
                  "font-weight": "bold"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}
  • Save your view under a new name, I called mine “alphabet filter view”. The end result will look like the below:
Example gallery list view formatting to display A-Z as buttons.

NOTE: There was an original example for this I found online many months ago, but I cant for the life of me find it. If anyone knows where this example came from, leave a message in the comments and I’ll credit it in this post.

Create the contacts list

Next, we need to create the contacts list. If you already have your user data held somewhere else, you can always create your new contacts list from Excel.

  • Once the contacts list is created, create a new lookup column with the following configuration:
    • Give your lookup column a name. I called mine “AZLookup”
    • Require that this column contains information = No
    • Enforce unique values = No
    • Get information from: Alphabet
    • In this column: Title
    • Press Save
Create a lookup column to connect to the Alphabet list.
  • Now you need to update each item in the contacts list, to ensure each item has an A-Z value in the lookup column. Again, for this I used “edit in grid view” to speed things up.
Update the contacts list to include values in the lookup column.
  • Switch to the gallery view and format the list using either the example JSON list view formatting below, or create your own to style the contacts list in a way that suits your needs.
{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/tile-formatting.schema.json",
  "height": 166,
  "width": 300,
  "hideSelection": false,
  "fillHorizontally": true,
  "formatter": {
    "elmType": "div",
    "attributes": {
      "class": "sp-card-container"
    },
    "children": [
      {
        "elmType": "button",
        "attributes": {
          "class": "sp-card-defaultClickButton",
          "role": "presentation"
        },
        "customRowAction": {
          "action": "defaultClick"
        }
      },
      {
        "elmType": "div",
        "attributes": {
          "class": "ms-bgColor-white sp-css-borderColor-neutralLight sp-card-borderHighlight sp-card-subContainer"
        },
        "children": [
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-previewColumnContainer"
            },
            "children": [
              {
                "elmType": "div",
                "style": {
                  "display": "flex"
                },
                "children": [
                  {
                    "elmType": "p",
                    "attributes": {
                      "class": "sp-card-userEmptyText"
                    },
                    "txtContent": "=if(length([$DisplayName]) == 0, '–', '')"
                  },
                  {
                    "forEach": "personIterator in [$DisplayName]",
                    "elmType": "a",
                    "attributes": {
                      "class": "=if(loopIndex('personIterator') >= 5, 'sp-card-userContainer', 'sp-card-userContainer sp-card-keyboard-focusable')"
                    },
                    "style": {
                      "display": "=if(loopIndex('personIterator') >= 5, 'none', '')"
                    },
                    "children": [
                      {
                        "elmType": "img",
                        "defaultHoverField": "[$personIterator]",
                        "attributes": {
                          "src": "=getUserImage([$personIterator.email], 'S')",
                          "title": "[$personIterator.title]",
                          "class": "sp-card-userThumbnail"
                        },
                        "style": {
                          "display": "=if(length([$DisplayName]) > 5 && loopIndex('personIterator') >= 4, 'none', '')"
                        }
                      },
                      {
                        "elmType": "div",
                        "attributes": {
                          "class": "ms-bgColor-neutralLight ms-fontColor-neutralSecondary sp-card-userOthers"
                        },
                        "style": {
                          "display": "=if(length([$DisplayName]) > 5 && loopIndex('personIterator') == 4, '', 'none')"
                        },
                        "customCardProps": {
                          "formatter": {
                            "elmType": "div",
                            "attributes": {
                              "class": "sp-card-personCallout"
                            },
                            "children": [
                              {
                                "forEach": "personIterator in [$DisplayName]",
                                "elmType": "div",
                                "attributes": {
                                  "class": "sp-card-userContainer sp-card-userCustomCard"
                                },
                                "style": {
                                  "display": "=if(loopIndex('personIterator') < 4, 'none', '')"
                                },
                                "children": [
                                  {
                                    "elmType": "img",
                                    "defaultHoverField": "[$personIterator]",
                                    "attributes": {
                                      "src": "=getUserImage([$personIterator.email], 'S')",
                                      "title": "[$personIterator.title]",
                                      "class": "sp-card-userThumbnail"
                                    }
                                  }
                                ]
                              }
                            ]
                          },
                          "openOnEvent": "hover"
                        },
                        "children": [
                          {
                            "elmType": "span",
                            "txtContent": "='+' + toString(length([$DisplayName]) - (4))"
                          }
                        ]
                      }
                    ]
                  },
                  {
                    "elmType": "div",
                    "attributes": {
                      "class": "sp-card-userTitle"
                    },
                    "style": {
                      "display": "=if(length([$DisplayName]) == 1, '', 'none')"
                    },
                    "defaultHoverField": "[$personIterator]",
                    "txtContent": "[$DisplayName.title]"
                  }
                ]
              }
            ]
          },
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-displayColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$FirstName]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content sp-card-highlightedContent",
                  "role": "heading",
                  "aria-level": "3"
                },
                "txtContent": "=if ([$FirstName] == '', '–', [$FirstName])"
              }
            ]
          },
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-displayColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$Surname]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content "
                },
                "txtContent": "=if ([$Surname] == '', '–', [$Surname])"
              }
            ]
          },
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-lastTextColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$Email]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content "
                },
                "txtContent": "=if ([$Email] == '', '–', [$Email])"
              }
            ]
          }
        ]
      }
    ]
  }
}
  • Save the view and give it a new name. I called mine “Contact cards”. The result will look like the below:
Contacts list with list view formatting applied.

Create the address book page

Now are lists are created and formatted, it’s time to create a new page, add our web parts and start dynamic filtering!

  • Create a new page, select any template you like
  • In a new one column section, add a new list web part > select the Alphabet list
  • Edit the web part and make the following changes:
    • Change the view to Alphabet Filter View
    • Set the size to small – about 5 items
    • Hide the command bar
    • Hide the see all button
  • Press Apply
  • Either in the same or new one column section, add a new list web part > select the Contacts list
  • Edit the web part and make the following changes:
    • Change the view to Contact Cards
    • Set the size to autosize – fit to number of items
    • Hide the command bar
    • Hide the see all button
    • Turn dynamic filtering on
    • Column in Contacts to filter = AZLookup
    • List or library containing the filter value: Alphabet
    • Column containing the filter values properties: Title
  • Press Apply

You will now be able to dynamically filter your contacts list using the A-Z buttons on the address book page!

Bonus! Populate the contacts list from M365 user data

When creating this demo I thought to myself “wouldn’t it be great if you could populate the contacts list from data already in Microsoft 365”. I thought it was a good idea so I created a Power Automate flow to get all the user data from M365 and add it to the contacts list.

Power automate flow to populate the contacts list with user data from Microsoft 365.

The flow uses the search for users (V2) action that, if no search terms are added, will bring back all the users within Microsoft 365 within the flow. Once that action has retrieved all the users, the create item action, wrapped within an apply to each creates a new item within the contacts list for each user the search for users action has found, populating the columns in the contacts list with dynamic content from the previous action.

NOTE: There is a pagination setting that will need updating if you have more than the threshold limit of 1000 users.

Pagination setting within the search for users (V2) action.

This flow was really just to prove the concept, so it runs on a manual basis. If you were to want to do something like this I would suggest having a one time “import of all the user profile data into the contacts list, then a separate, flow that runs on a longer schedule that only runs if certain conditions are met (for example: if the email address doesn’t already exist in the contact list).

Example 2 – Custom SharePoint Framework address book web parts

There are several SharePoint framework (SPFx) sample solutions available from GitHub that work as address books, connecting to user data in Microsoft 365 and allowing you to browse and search for users. Not all samples are offered as downloadable solutions packages, sometimes you will need to package & deploy them manually. I’ve got a post here that covers everything you need to do 🙂

People Directory SPFx sample

This sample was made available as part of the PnP starter kit (formerly the SharePoint starter kit), which I installed and have been using in my tenant since 2019 without issue. This web part lets you browse an A-Z, as well as being able to search for users.

People Directory SPFx sample.

This web part can be downloaded from GitHub and installed into your tenant.

Link to the sample: https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-people-directory

Organization Directory SPFx sample

This sample allows you to browse an A-Z, as well as being able to search for users and sort the results by job title, first name, last name etc. When a user is found, a profile hover card is displayed is the same as the rest of Microsoft 365.

Organization Directory SPFx sample.

This web part needs to be packaged into a solution before installing into your tenant.

Link to sample: https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-directory


Two ways to remove search results in SharePoint

In this post we will take a look at two different ways to remove search results in classic and modern SharePoint.

Introduction

There may be occasions in SharePoint where you are required to remove an item, or items from the search results. When we are talking about search results, we are referring to the items that are returned when a user initiates a search in SharePoint.

Search results in modern SharePoint.

Click on one of the buttons below to find out more about how you can remove search results in both classic and modern SharePoint:

How to convert SharePoint pages into PDF files

In this post we step through how you can use Power Automate to convert modern SharePoint pages into PDF files and save them to a document library.

Intro

Recently I got asked to come up with a way to turn SharePoint pages into PDF files for use in an offline scenario. The converted SharePoint pages didn’t need to be formatted as it was only the body content of a SharePoint page that was needed. Also part of the brief was that when the SharePoint page is updated, the corresponding PDF file also updates.

There are several posts online that cover very topic this that I’ll reference at the end, but they didn’t quite do exactly what I wanted – so here’s my take on how to convert SharePoint pages into PDF files!

What you’ll need

  • A modern SharePoint site pages library (these come with every SharePoint site!)
  • A OneDrive location to temporarily store the SharePoint page outputs
  • Power Automate to build the automation
  • A document library to store the output PDF files

A note on the site pages library

In my example I didn’t want all the site pages to be converted into PDF files, so I added a choice column to ‘tag’ all the pages that should be converted. I set the default value of the choice column to be ‘Site Page’, so that the only pages that get converted are the ones I’m interested in. This is reflected in the flow below with the condition step.

Add a choice column to ‘tag’ the pages you wish to convert to PDF.

Building the flow

The trigger action for our flow is when a file is created or modified (properties only). This allows us to re-run the flow when SharePoint pages are updated to also update the PDF files.

  • Select the site you are using to create the SharePoint pages in site address (If you don’t see it listed just press enter custom value and paste the URL in)
  • Select the Site Pages library under library name

Next, I’ve added a condition to only convert pages that have been tagged ‘Runbook’ to PDF.

Condition: if Document type value is equal to ‘Runbook’.
  • Note: make sure you select the Value dynamic content for your choice column, rather than the choice column itself as that will break your flow.

If yes, next is a send an HTTP request to SharePoint step. Here I’m using a REST API call to get the body content of the SharePoint page.

Use a send an HTTP request to SharePoint step to get the body content of your page.
  • Set the site address to the site in question
  • Set method to GET
  • Enter the following in Uri:_api/web/lists/GetByTitle('Site%20Pages')/items('ID')/CanvasContent1
  • Replace ‘ID’ with the dynamic content ID from the when a file is created or modified step

Note: The output of this step generates some additional stuff you probably won’t want in your PDF like this:

 "d": { "CanvasContent1": "}}

I used the parse JSON step to remove the unwanted mark up and just get the plain text from the body content.

  • I added the body dynamic content from the send an HTTP request to SharePoint step in the content field in the parse JSON step
  • I copied the the output body from send an HTTP request to SharePoint of a successful run in flow history and pasted it into the parse JSON step
Output body from send an HTTP request to SharePoint to paste into the parse JSON step from a successful flow run.
  • I then pressed generate from sample, which output the following:
{
    "type": "object",
    "properties": {
        "d": {
            "type": "object",
            "properties": {
                "CanvasContent1": {
                    "type": "string"
                }
            }
        }
    }
}

Parse JSON step with generated schema.

From this I then used a create file action to create a temporary HTML file in OneDrive (more on this later), with the following config:

  • Folder path: / (root of the OneDrive account)
  • File name: Name from when a file is created or modified step
  • File content: CanvasContent1 from the parse JSON step
Create file action to create temporary HTML page in OneDrive.

Next, a convert file step to convert the HTML page into a PDF file:

  • File: ID from the create file step
  • Target type: PDF

Now we can use a create file action to create a PDF in our output document library in SharePoint:

  • Set the site address to the site you want to store the PDF files in
  • Set the folder path to the document library, or navigate to the relevant folder within that library
  • Set file name to file name from the convert file step
  • Set file content to file content from the convert file step
The create file action creates the PDF file in the destination document library.

I then used an update file properties action to pass metadata from the site pages library to the destination document library – this step is optional. Finally, a delete file action to delete the temporary HTML file from the OneDrive we created earlier:

Delete file action to remove the temporary HTML file.

Here’s the flow in it’s entirety:

Issues & troubleshooting

Formatting issues with the send an HTTP request to SharePoint

As mentioned above, when just using the send an HTTP request to SharePoint action, the output contains mark up that isn’t going to make sense within the PDF. The parse JSON action cleans this up and just leaves the body content of the page.

Create file action creates corrupt PDF files

When testing this flow out I originally didn’t have the convert file action in place. In the file name I added ‘.PDF’, but every time the output PDF was corrupt and errored like this when trying to open:

The flow also failed on this step and the error said that “Conversion of this file to PDF is not supported. (InputFormatNotSupported / pdf)”. I decided to scrap this approach and create a HTML page and add in the convert file action which worked around this issue.

Overwriting existing PDF files causes flow to fail

During testing of this flow I also noticed that when triggering the flow based off updating a site page, the create file create file action would error with a status 400 error saying “A file with the name [file name] already exists”.

I’ve wrote a separate post on how to overwrite files using the create file action, but basically the answer was to turn off chunking within the actions settings.

References


This page is not using a valid page layout – how to fix

This post describes an issue when trying to view classic publishing pages in SharePoint Online and how to resolve.


The issue

After migrating publishing sites from SharePoint 2010 – SharePoint Online, I encountered an issue with classic SharePoint Server publishing pages. Post-migration, some publishing pages wouldn’t open and displayed this error message:

Fix publishing pages with custom page layouts created from template - HANDS  ON SharePoint
Now another issue with this was that quite often the publishing pages affected were the migrated sites’ homepage – rendering the whole site inoperable! Microsoft have an article on the issue here and state that the cause is that “The .aspx page is using an invalid page layout, or the current layout is corrupt”.

Resolution

The Microsoft documentation on how to resolve this is pretty good, but I found it didn’t quite get me to fix my issue. Here’s what I did:

  • Navigate to Site Content & Structure: /_layouts/sitemanager.aspx. NOTE: You need to be a SharePoint admin to access
  • Open the Pages library > find your broken page > click on the drop-down button and press edit page settings
  • Select the (Welcome Page) Blank Web Part Page layout
  • Press OK

When I had to do this for my broken pages, there was a warning message above the page layout that said “Current page layout is not valid. Please select a valid page layout from the drop down list below”. I just selected the (Welcome Page) Blank Web Part Page layout anyway, pressed OK and it worked.


How to overwrite files using the create file action in Power Automate

In this post we explore a common error experienced when trying to update existing files in SharePoint using the create file step in Power Automate – and how to resolve it.

The problem

I recently ran into an issue seemingly many others have encountered when trying to update an existing file in SharePoint using the create file step in Power Automate. When running my flow, I received the following error:

Bad Request error in Power Automate when trying to use the create file step to update an existing file in SharePoint.
  • Bad Request status: 400
  • Message: A file with the name [NAME OF FILE] already exists. It was last modified by [NAME OF USER] on [DATE]

The solution

The solution to this has already been shared a fair few times via the Power Automate blogs, but here it is:

  • Press the ellipsis … > settings within the create file step
Open the ellipsis and press settings within the create file step.
  • Scroll down until Content Transfer > set allow chunking to off
  • Save and re-run your flow

That’s it, now when I use the create file step to update existing files, it runs successfully!

Note: Chunking of content is used for splitting up large content for downloads/ uploads. You may need to consider this before turning off chunking in your flow. More information on chunking can be found here.