PnP WatchParty for Microsoft's 'LearnTogether Building apps with Microsoft Graph' event

This article is contributed. See the original author and article here.


The Microsoft 365 PnP Team would love to invite you to the Watch Party that we are hosting for the Learn Together: Building apps with Microsoft Graph event.


 


The event is a 2 hour live stream and is offered live twice to accommodate the global audience:






April 14, 2021 2:00 pm AEST 4:00 pm AEST (Asia Pacific Region)










April 14, 2021 8:30 am PDT 10:30 am PDT (North America & Europe Regions)

 

For the North America & Europe Regions live stream, we will host a Teams meeting to connect and collaborate while we are watching the live stream on our own devices. There will be games, popcorn, G-raphs ? and much more.

 

My dear Sharing Is Caring friend David Warner made a short video to explain it in his own words!

 


 

Find more information on the Learn Together event here: https://aka.ms/learntogether-graph

 

Register for our WatchParty here: aka.ms/pnpwatchparty

 

We love to see you join us!

 

If you have any questions, please reach out here in the comments or find David Warner, Hugo Bernier or me Luise Freese on twitter!

 

Sharing Is Caring ?





Power Apps: source code editing for Canvas Apps

Power Apps: source code editing for Canvas Apps

This article is contributed. See the original author and article here.

Why?


Ever since I saw the post Source code files for Canvas apps from Microsoft, I thought this might come in handy for some of my Canvas Apps. As a non-coding Citizen Developer, I just thought that day would be more in the future. A day when this feature would not be experimental anymore but general available :beaming_face_with_smiling_eyes:.


 


One of my customers created a Power App with too many screens, too many data sources and too many controls referencing each other. The result was a Power Apps Studio with a Page Unresponsive error when when opening specific screens:


PowerApps_Page_Unresponsive


This left us with no way to clean up the latest version of the Canvas App through the Studio anymore :cross_mark::face_without_mouth::cross_mark:.


What?


This post adds information on how to edit Power Apps source code for Canvas Apps. It will also show you that GitHub it is not per se needed.


How?


I still recommend to read the Microsoft post first. There is a lot of potential in using GitHub as your code repository. There are also some Youtube video’s on how to perform most of the steps explained in further detail. This post just focuses on how to use the tooling without a GitHub setup.


1) First step is to download the .msapp file of your Canvas App. The .msapp file is like a bundled package of multiple kind of files (.json, .xml and .yaml), making up your whole Power App :woman_technologist:.


You can download your .msapp file by opening the Power App in the online Studio. Then selecte Save as and select This computer as your destination. A popup should appear that offers you to download the .msapp file:
Save your .msapp file to a local destinationSave your .msapp file to a local destination




Another option is to Export the whole Power App package from the Power Apps as a ZIP file.


Export your Power App as a packaged ZIP fileExport your Power App as a packaged ZIP file


In this option you need to unzip the downloaded file and find the .msapp files in a sub folder.




2) I then move the .msapp file to a folder within my Downloads folder making the file path:


C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterConfigScreen.msapp

3) Make sure that you install the required .NET Core SDK version as mentioned in the Microsoft post. Then download the Power Apps Language Tooling locally to your Downloads folder:


Download the Power Apps Language Tooling tool from GitHub locallyDownload the Power Apps Language Tooling tool from GitHub locally




Unzip this file and save the unzipped folder in a local place of your computer. I chose to store it next to my .msapp file so the directories would be near each other:



C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-master

4) Then just run the build.cmd file of the unzipped folder as Administrator to be sure:


Run the build.cmd as an AdministratorRun the build.cmd as an Administrator




After a successful run, the bin folder will have a number of files in the directory. The following path is the one to remember for later:




C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterPowerApps-Language-Tooling-masterbinDebugPASopa

Please note that your directory path may be different at the beginning, but from the last PowerApps-Langauage-Tooling-master section it should be the same. The PASopa file is where the magic happens.


5) Now open the default Windows Command Prompt and Run as adminisrator:


CommanPromptAsAdministrator




Run Command Prompt as administrator




6) Have a folder prepared where you want to store the unpacked .msapp files. I chose a folder next to the .msapp file again:


C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterConfigScreenSourceFiles

7) Change the directory path in the Command Prompt to the directory where the PASopa file is located using this command


cd C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterPowerApps-Language-Tooling-masterbinDebugPASopa



CommanPromptAsAdministrator_ChangeDirectory


Result of the command to change directory path




8) Execute the following command to unpack the .msapp files to the desired folder:


pasopa -unpack C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterConfigScreen.msapp C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterConfigScreenSourceFiles

Note that the first directory is that of the .msapp file. The second directory is that of the folder where we want to unpack the separate files. Warnings may occur because of checksums built in by the developers. However in the end, the folder with the unpacked .msapp file will have a default content structure:


FileExplorer_Unpacked_msapp_File


With a tool like Notepad++ or my personal favorite Visual Studio Code, you can edit the individual files. This is how you edit Power Apps source code of Canvas Apps. I prefer Visual Studio Code because you can open up a whole folder in one go:


Visual Studio Code opening a whole Folder and Sub Folders to edit individual filesVisual Studio Code opening a whole Folder and Sub Folders to edit individual files




9) Now you can remove frozen screens, rename controls in bulk, update variables in bulk or any other action which in Power Apps Studio would be a hassle.



When this is all done, you can just repack the files with a command like:


pasopa -pack  ConfigScreen.msapp C:UsersDjangoLohnDownloadsPowerApps-Language-Tooling-masterConfigScreenSourceFiles 

If you are more observant than I was, you will have noticed that this command does not give an output directory… so where did the new repacked .msapp file go?? It gives you the new .msapp file in the PASopa directory:


FileExplorer_Repacked_msapp_Files


The repacked ZIP file can be imported in the online Studio. This import will then update the existing Power App to a new version. Never thought I would say this as if I know how to develop code but –> happy coding :flexed_biceps::nerd_face::thumbs_up:! 


 


Originally published at https://knowhere365.space/power-apps-source-code-edit-for-canvas-apps/ 

Cloud Adoption Framework Enterprise-Scale Series and Livestream

Cloud Adoption Framework Enterprise-Scale Series and Livestream

This article is contributed. See the original author and article here.

If you are starting with your cloud journey, there are many things you need to think of to set up your cloud environment and your Azure cloud architecture . Things like networking, subscription management, security, governance, and many more, can be fairly complex. That is where the Cloud Adoption Framework for Azure can deliver proven guidance to help you with your Azure cloud journey. And with Enterprise-scale landing zones, you even get an architectural approach and a reference implementation that enables effective construction and operationalization of landing zones on Azure, at scale. I had the chance to collaborate with Sarah Lean (Azure Cloud Advocate) on an enterprise-scale series of blog posts to show how our fictional company Tailwind Traders can leverage the Cloud Adoption Framework and Azure Landing Zones to accelerate their cloud journey.

 

You can find our Enterprise-Scale landing zones blog series on the Azure.com blog:

 

Microsoft Azure Cloud Adoption Framework Enterprise-ScaleMicrosoft Azure Cloud Adoption Framework Enterprise-Scale

 

 

The Microsoft Cloud Adoption Framework for Azure is proven guidance that’s designed to help you create and implement the business and technology strategies necessary for your organization to succeed in the cloud. It provides best practices, documentation, and tools that cloud architects, IT professionals, and business decision makers need to successfully achieve short-term and long-term objectives.
By using the Microsoft Cloud Adoption Framework for Azure best practices, organizations can better align their business and technical strategies to ensure success.

Microsoft Docs

 

Next to our Enterprise-Scale landing zones blog series, we will also host a livestream on April 7.

 

 

We will be exploring Tailwind Traders and their cloud adoption journey using enterprise-scale architecture in future blog posts. However, if you’d like to learn more about enterprise-scale landing zones then please join Sarah Lean and Thomas Maurer on the 7th April at 8am PST/3pm GMT on LearnTV where we will be doing a Q&A and deployment of an Enterprise-Scale Landing Zone live!  

 
 
 

Configuring On-Premise data gateway with new user in Azure AD tenant

This article is contributed. See the original author and article here.

Issue: Unable to configure and use On-Premise data gateway when user is having personal account (*.gmail.com/*.yahoo.com etc) or work/school account belongs to multiple tenants by design.


 


Workaround  We can follow the below steps to install, configure and create on-premise data gateway on on-premise as well as on Azure with below steps. 


 


Creating user in Azure AD tenant:



  • Create new user in the destined Azure Active directory. You can refer this doc CreateNewUser .

  • Provide contributor access at subscription or Resource Group (where you want to create the Azure data gateway) level

  • Sign-out from the portal and sign-in with new user created above . It will ask you to change password, update the password and check if you are able to login to portal.


Install  and Configure On-Premise data gateway on On-Premise machine:



  • Now, you can download and install the on-premise data gateway on your On-Premise machine. You can refer to the link for downloading and installing gateway logic-apps-gateway-install .

  • Once the gateway is successfully installed, it pops up for the sign-in to configure the gateway.

  • Use the new user created in the Azure Active directory to sign-in and configure gateway. Follow the steps mentioned n above link. install-data-gateway 


Verify Gateway Cluster in Power Platform center:



  •  You can verify whether the gateway clustered configured is available in the power platform portal (log in with new Azure AD user) Manage DataGateways.

  • You need to select region in which you configured gateway in portal, option is available on the top right side.


Creating On-Premise data gateway in Azure:



  •  Sign-in to azure portal with new Azure AD user used to configure the data gateway on on-premise machine

  • Create new Azure On-Premise data gateway resource in respective resource group where user has access. you can follow this doc on how to create azure On-premise data gateway resource. Create-azure-gateway-resource 


Note:



  • This can be an extra effort for managing the new user we created in Azure AD tenant.

Logic App utility to perform bulk operations on workflow runs

This article is contributed. See the original author and article here.

 This PowerShell utility can be used to manage the Logic App runs such as cancelling or resubmitting the runs. 


 


 It supports the below operations.


 



  • BulkCancel – Cancel running instances



  • BulkResubmitFailedRuns – Resubmits failed runs



  • BulkResubmitCancelledRuns – Resubmits cancelled runs



  • BulkResubmitSucceededRuns – Resubmits Succeeded runs


Steps to follow for executing the script:



  • Copy the PowerShell script attached to desired folder from here LogicAppUtility.ps1

  • Set up Azure Service Principal – Contributor access on the Subscription. You can refer below doc for creating Azure app registration Create Azure SPN  

  • Open PowerShell with ‘Run As administrator Privileges’

  • Run the below command to bypass the execution policy  and accept -Y

  •  Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

  • Change the directory to the PowerShell script copied folder in first step.

  •  cd  ‘PowerShellScriptFolderPath’

  • Execute the command to perform the different mentioned operations on Logic App.    


.LogicAppUtility.ps1 -ClientId ‘Enter ClientId‘ -TenantId ‘Enter TenantId‘ -Secret ‘Enter Secret‘ -SubscriptionId ‘Enter Subscription Id‘ -ResourceGroupName ‘Enter resource Group Name‘ -LogicAppName ‘Enter Logic App Name‘ -Operation ‘Operation name‘ -StartTime ‘Enter start datetime on UTC‘ -EndTime ‘Enter end datetime in UTC 



  •  Validate the text file gets created in the same folder for list of run ids that are executed on specified operation.


Parameters definition:            


































































S.No



Parameter Name



Mandatory



Comments



1



Client Id



Yes



The application /Client Id of your App Service Principal



2



Tenant Id



Yes



Tenant Id of Azure AD or App Service Principal



3



Client Secret



Yes



Secret of your App Service Principal



4



Subscription Id



Yes



Subscription Id where Logic App present



5



Resource Group Name



Yes



Resource group Name in which Logic App is present



6



Logic App Name



Yes



Name of your Logic App



7



Operation



Yes



Allowed Values:


BulkCancel – Cancel running instances


BulkResubmitFailedRuns – Resubmits failed runs


BulkResubmitCancelledRuns – Resubmits cancelled runs


BulkResubmitSucceededRuns – Resubmits Succeeded runs



8



StartTime



No



If present all above operations will be performed on the runs started from the specified time.


The Timestamp must be in UTC. 


Ex: 2020-11-02T16:33:00.000Z



9



EndTime



No



You can include the EndTime along with StartTime if you want to perform above operations between specific timestamps.


Note:


It is invalid without StartTime.



 


Script: Download Link 


 


Note:



  • Not recommended to run directly on the Production environment

  • It is tested with limited test cases and volume of runs, Validate in test environments and then perform in Production

Cumulative Update #10 for SQL Server 2019 RTM

This article is contributed. See the original author and article here.

The 10th cumulative update release for SQL Server 2019 RTM is now available for download at the Microsoft Downloads site. Please note that registration is no longer required to download Cumulative updates.
To learn more about the release or servicing model, please visit:



Starting with SQL Server 2017, we adopted a new modern servicing model. Please refer to our blog for more details on Modern Servicing Model for SQL Server


#VisualGreenTech Challenge – EarthDay 2021

#VisualGreenTech Challenge – EarthDay 2021

This article is contributed. See the original author and article here.

Screen Shot 2021-04-06 at 3.00.37 PM.png



About #EarthDay


Did you know that April 22 is Earth Day
In 2020, over 100 million people worldwide celebrated the 50th anniversary of Earth Day, driving awareness and actionable learning around environmental issues with activities focused on education, conservation, citizen science, cleanup and more. 

 In 2021, the #EarthDay theme is Restore Our Earth with a focus on five topics:



  1. The Canopy Project – a conservation and restoration effort to plant trees and rehabilitate areas in need of reforestation. 

  2. Foodprints For the Future –  an effort focused on fighting climate change with diet change. 

  3. The Great Global Cleanup – an effort focused on reducing our waste footprints by working to clean up our environment. 

  4. Climate and Environmental Literacy – combining grassroots community efforts with national initiatives and civic engagement. 

  5. Global Earth Challenge – a citizen science initiative to engage millions of people in collecting and understanding environmental data. 


Sustainability is an area of significant importance to Microsoft and we wanted to do something to help create awareness and empower actionable learning around relevant topics for technologists.  


 
Awareness:  #GreenTech Advocacy


In a January 2020 announcement, Microsoft laid out ambitious company-level goals for sustainability including: being carbon negative by 2030, removing historical carbon emissions by 2050, and establishing a $1B climate innovation fund to invest in research in context. One year later, the Environmental Sustainability Report (read the three-part blog coverage) reviewed progress, focusing on four pillars:



  • Be carbon negative – remove more carbon dioxide than we emit each year. 

  • Be water positive – put more water back into the environment than we consume. 

  • Be zero waste — encourage redesign of resource lifecycles to eliminate or reduce waste. 

  • Support healthy ecosystems – collect and analyze data to understand ecosystems, stop decline. 


This is progress at organization scale. But you might be asking yourself — “how can I educate myself on the issues and contribute to, or advocate for, sustainable living and engineering practices in my community and workplace?” We’re glad you asked!

Here are a few resources to get you started on your learning journey:



Awareness is great but actionable learning is better! So we came up with a fun challenge for April, just in time for #EarthDay!

Action: #VisualGreenTech Challenge


rae_lyon_0-1617743081570.png


Here is how this works: 



  • What we do: Share a learning prompt (pinned daily under @nitya) with a resource link to help you explore that topic.

  • What you do: Check out the resource and respond to the prompt using a visual (sketchnote or doodle) with insights.

  • Share the visual on Twitter and tag it #EarthDay #VisualGreenTech

  • We’ll collate all submissions and feature them in a segment on the #HelloWorldLive show on EarthDay (Apr 22)


Submit responses to as many prompts as you like, as many times as you want to. The goal is to create awareness around those topics and share our own learnings and perspectives in context.  Want a sneak peek at the prompts in advance? check out the gallery here.


But wait. There’s one more thing!


 


Share a Sketch. Plant A Tree! 


Along with featuring all submitted visual Earth Day images, we will also work with Ecosia to plant 1,000 trees in honor of all #VisualGreenTech challenge participants to help tackle climate change.  Yes, that’s right! Let’s give back to the earth and learn more about sustainability in the process.


Screen Shot 2021-04-06 at 3.00.37 PM.png


Have questions? Leave us a comment on this post! And let’s make a difference to this planet. 

Introducing the Azure Static Web Apps CLI

Introducing the Azure Static Web Apps CLI

This article is contributed. See the original author and article here.

static-web-apps-banner.png


 


 


Azure Static Web Apps seamlessly integrates globally distributed hosting for your static content, serverless APIs powered by Azure Functions, as well as features like authentication, custom routing, and route-based authorization.


 


With the new Static Web Apps CLI, you can now run your entire full-stack web app locally in your development environment and access many of the platform’s capabilities without deploying your app. The CLI hosts your frontend and API on a single localhost endpoint and emulates authentication, custom routing, and authorization rules.


 


Static Web Apps CLI can serve static content from a folder. It also works great with local development servers from any frontend framework, including React, Angular, Next.js, and Blazor WebAssembly.


 


Getting started


 


Install the Static Web Apps CLI from npm:


 


npm install -g @azure/static-web-apps-cli

 


To serve static content from a folder, run:


 


swa start ./my-app

 


Your app is accessible at http://localhost:4280. If you have any custom route logic or settings configured in a staticwebapp.config.json file, it’ll apply them automatically.


 


Run and test your full-stack app


 


Use a framework dev server


 


Most frontend frameworks provide a local dev server that allows you to iterate on your app quickly using features such as hot module reloading. Static Web Apps CLI can proxy traffic to a dev server that’s already running.


 


How you start your app’s dev server depends on your framework. Here are some examples:


 


# Create React App
npm start

 


# Next.js
npm run dev

 


# Blazor WebAssembly
dotnet watch run

 


Then, in a separate terminal, start the CLI and provide your framework dev server’s local address:


 


swa start http://localhost:3000

 


Requests to http://localhost:4280 are proxied to your dev server. Using that endpoint, you can test out how your app interacts with Static Web Apps features like authentication and API functions.


 


Simulate authentication


 


Azure Static Web Apps provides integrated authentication using providers such as GitHub, Twitter, and Azure Active Directory. The Static Web Apps CLI simulates the same authentication flow so you can test your authentication/authorization logic locally.


 


When your app requests a Static Web Apps authentication login URI, such as /.auth/login/github, the CLI displays a page that allows you to log in as any identity by specifying their information. This works with all supported identity providers without any additional configuration.


 


Static Web Apps CLI local authentication pageStatic Web Apps CLI local authentication page


 


You can use this to easily test your app using different identities and roles. The /.auth/me endpoint returns information about the current user, and API function calls include a claims principal header—they work just like they do when your app is deployed to Azure! Learn more about how to access user information from our documentation.


 


Run API functions


 


If your app has an Azure Functions API, you can include its location when you start the Static Web Apps CLI:


 


swa start http://localhost:5000 –api ./api

 


Behind the scenes, as the CLI launches, it also starts the API app using the Azure Functions Core Tools. You can access your API functions at http://localhost:4280/api/*. Because the frontend app and serverless functions are served from the same origin, you don’t have to worry about CORS (cross-origin resource sharing) when you call your APIs.


 


What’s next?


 


Static Web Apps CLI is currently in preview. We’re only getting started and have lots more planned!


 


Together with the Azure Static Web Apps VS Code extension, the CLI will play an important role in our local development experience. We plan on offering an integrated debugging experience in VS Code that lets you start your entire stack and attach debuggers to both your frontend and backend apps.


 


While we are initially focusing on running apps locally, we plan on expanding the CLI with more commands such as creating Static Web Apps projects.


 


Try it today


 


Read the Azure Static Web Apps local development documentation to learn more and get started.


 


If you have feedback or would like to contribute, check out the Azure Static Web Apps CLI on GitHub.


 


 

Change your own profile picture as a Guest in Microsoft Teams

Change your own profile picture as a Guest in Microsoft Teams

This article is contributed. See the original author and article here.

I love Microsoft Teams and I also love controlling my online appearance, but sadly both aren’t big friends when you are a Guest in another tenant. There still isn’t a good/easy way of controlling your own profile picture, which is sad because you are reduced to a not-very-personal coloured circle with initials:

image.png


Fellow MVP Kazushi Kamegawa shared a method of changing your profile picture in a private forum, but it stopped working due to some UI changes in the Azure Portal. Luckily, with some more “hackery”, we can still make it work!


The tldr version:



  1. Tenant switch to the tenant where you want to change your profile picture

  2. Figure out the ID of your user account in that tenant

  3. Open your user profile page in the Azure Portal using a direct link

  4. Edit the profile, upload a profile picture

  5. Wait for about a week to have the profile picture synced into Teams


Switch tenants


If you found this blogpost, it’s probably safe to assume that you know how to switch tenants. For the purpose of this guide, it is VERY important you do this in your browser because otherwise authentication will get confused ;).



  1. Open your browser, go to https://teams.microsoft.com and sign in with your credentials

  2. Top right of the Teams UI, switch to the tenant where you want to change the profile picture of your Guest account


image-1.png

Get ID of user account


I don’t know of any place in the UI that shows the id of your Guest account in a tenant, so I had to find a way using the Developer Tools of Chrome/Edge.



  1. After switching to the Guest tenant, hit the F12 key to open the Developer Tools

  2. Select the Applications tab

  3. In the left part underneath Storage and  Local storage, select https://teams.microsoft.com

  4. In the right part, do a search for ts.tenantList. It should only show one result, select it.

  5. It shows all tenants you are part of as a Guest, open up the one you are currently in. You’ll need the value next to the userId property. In my screenshot, it is the GUID that starts with f35707ec-…


image-3.png
 

User profile page in Azure Portal


Now that you have your user account ID, you can open up your profile page in the Azure AD of the Guest tenant!



  1. In the same browser window, open https://portal.azure.com

  2. Top right of the portal, make sure you are in the tenant where you are a Guest. Most probably that is not the case, so do a tenant switch in the Azure Portal too.

  3. In the same browser window, open up this link: https://portal.azure.com/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Profile/userId/<userId>. Make sure to replace <userId> with the id you copied in the previous step.

  4. You should now see your profile page in the Azure AD where you are a Guest.


image-4.png
 

Edit profile and upload picture


Finally time to upload your profile picture!



  1. Top of the profile, click the Edit button

  2. With your profile in edit mode, you can browse for a photo on your computer and upload it to your profile!


image-5.png
 

Wait and enjoy success


It takes a while for your profile picture to show in Teams, and it might even show for a short while and go away again. But if you have enough patience, after about a week or so, your profile picture should show consistently across the Teams UI both for you and for others!


image-6.png

Until Microsoft Teams or Microsoft 365 gives us an easier way to change our profile picture, this is the best way to do it self service. The days of the anonymous circle with initials are over, time to show your personality also in your Guest tenants!


Improving the Page Properties web part

This article is contributed. See the original author and article here.

Ever get annoyed with the page properties web part put out by Microsoft?&nbsp; If you’ve got some OCD issues (like me) then it may not take very long.&nbsp; At ThreeWill, we help clients with their digital workplaces and improving the way their users can obtain information and makes sense of it all.&nbsp; Oftentimes, the Page Properties web part can be useful here, as we very often add valuable metadata to pages in a digital workplace, which we often tie to page templates as well.&nbsp; News might roll up based on these page properties, which can assist in finding information in many ways.&nbsp; But its often handy to display this metadata in a clean way on a page as well.&nbsp; The standard Page Properties web part seeks to do just that.&nbsp; And, for the most part, it does a fine job with it.&nbsp; But it has a few deficiencies.&nbsp; The most annoying thing to me, when setting up digital workplaces was that it only supports a white background.&nbsp; But there are other small things, like the limitations with pretty standard field types.&nbsp; I like the idea of taking advantage of metadata columns for pages, but being able to use it visually is equally important. &nbsp;I finally decided to do something about it and build a new version of this web part.&nbsp; &nbsp;So with this in mind, let’s lay out our goals with this new web part.&nbsp; We will call it the Advanced Page Properties web part.
&nbsp;
Feature Goals
Attempt to replicate the functionality of Page Properties with the following improvements:

Support for theme variants
Updated to standard capsule look for list options
Support for image fields
Support for hyperlink fields
Support for currency
Improved support for dates
In other words, we’re shooting for this:

&nbsp;
Property Pane
For a part like this, it’s all about getting the property page figured out first.&nbsp; We want this to feel familiar too and not stray too much from the original design, unless it helps.
&nbsp;
Let’s start by recognizing our chief property that the web part needs: selectedProperties.&nbsp; This array will hold the internal names of the fields that a user has selected for display in our web part.&nbsp; We intend on passing this property down to our React component.&nbsp; Here’s a look at our property object:
&nbsp;
&nbsp;export interface IAdvancedPagePropertiesWebPartProps {
title: string;
selectedProperties: string[];
}
&nbsp;
&nbsp;
&nbsp;
In our AdvancedPagePropertiesWebPart, we want to hold all possible properties for drop downs in a single array.&nbsp;
&nbsp;
&nbsp;private availableProperties: IPropertyPaneDropdownOption[] = [];
&nbsp;
&nbsp;
&nbsp;
Next, we need the following method to obtain the right types of properties for display:
&nbsp;
&nbsp; private async getPageProperties(): Promise<VOID> {
Log.Write(“Getting Site Page fields…”);
const list = sp.web.lists.getByTitle(“Site Pages”);
const fi = await list.fields();

this.availableProperties = [];
Log.Write(`${fi.length.toString()} fields retrieved!`);
fi.forEach((f) =&gt; {
if (!f.FromBaseType &amp;&amp; !f.Hidden &amp;&amp; !f.Sealed &amp;&amp; f.SchemaXml.indexOf(“ShowInListSettings=”FALSE””) === -1
&amp;&amp; f.TypeAsString !== “Boolean” &amp;&amp; f.TypeAsString !== “Note” &amp;&amp; f.TypeAsString !== “User”) {
this.availableProperties.push({ key: f.InternalName, text: f.Title });
Log.Write(f.TypeAsString);
}
});
}
&nbsp;
&nbsp;
&nbsp;
We are using the PnP JS library for gathering the fields in the Site Pages library.&nbsp; Figuring out the right types of filters to gather was a bit of trial-and-error.&nbsp; We are excluding anything that’s inherited from a base type or is hidden in any way.&nbsp; We are also excluding 3 standard types so far: boolean, note and user.&nbsp; Note doesn’t make sense to display.&nbsp; Boolean can definitely work, but needs a good display convention.&nbsp; User was the only tricky object, which is the reason it isn’t done yet.
&nbsp;
We call the above method prior to loading up the property pane.
&nbsp;
&nbsp; protected async onPropertyPaneConfigurationStart(): Promise<VOID> {
Log.Write(`onPropertyPaneConfigurationStart`);
await this.getPageProperties();
this.context.propertyPane.refresh();
}
&nbsp;
&nbsp;
&nbsp;
We need handlers for adding and deleting a property and selecting a property from a dropdown.&nbsp; These methods make necessary changes to the selectedProperties array.
&nbsp;
&nbsp; protected onAddButtonClick (value: any) {
this.properties.selectedProperties.push(this.availableProperties[0].key.toString());
}

protected onDeleteButtonClick (value: any) {
Log.Write(value.toString());
var removed = this.properties.selectedProperties.splice(value, 1);
Log.Write(`${removed[0]} removed.`);
}

protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
if (propertyPath.indexOf(“selectedProperty”) &gt;= 0) {
Log.Write(‘Selected Property identified’);
let index: number = _.toInteger(propertyPath.replace(“selectedProperty”, “”));
this.properties.selectedProperties[index] = newValue;
}
}
&nbsp;
&nbsp;
&nbsp;
Finally, with all of our pieces in place, we can render our property pane with all it’s needed functionality.
&nbsp;
&nbsp; protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
Log.Write(`getPropertyPaneConfiguration`);

// Initialize with the Title entry
var propDrops: IPropertyPaneField<ANY>[] = [];
propDrops.push(PropertyPaneTextField(‘title’, {
label: strings.TitleFieldLabel
}));
propDrops.push(PropertyPaneHorizontalRule());
// Determine how many page property dropdowns we currently have
this.properties.selectedProperties.forEach((prop, index) =&gt; {
propDrops.push(PropertyPaneDropdown(`selectedProperty${index.toString()}`,
{
label: strings.SelectedPropertiesFieldLabel,
options: this.availableProperties,
selectedKey: prop,
}));
// Every drop down gets its own delete button
propDrops.push(PropertyPaneButton(`deleteButton${index.toString()}`,
{
text: strings.PropPaneDeleteButtonText,
buttonType: PropertyPaneButtonType.Command,
icon: “RecycleBin”,
onClick: this.onDeleteButtonClick.bind(this, index)
}));
propDrops.push(PropertyPaneHorizontalRule());
});
// Always have the Add button
propDrops.push(PropertyPaneButton(‘addButton’,
{
text: strings.PropPaneAddButtonText,
buttonType: PropertyPaneButtonType.Command,
icon: “CirclePlus”,
onClick: this.onAddButtonClick.bind(this)
}));

return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.SelectionGroupName,
groupFields: propDrops
}
]
}
]
};
}
&nbsp;
&nbsp;
&nbsp;
Our Component and Displaying our fields/values
Our React component needs to properly react to the list of selected properties changing.&nbsp; It also needs to react to our theme changing.&nbsp; I leveraged this awesome post from Hugo Bernier for the theming, so I will not cover that in-depth, although you will see how it’s being leveraged in the code snippets below.&nbsp; Here are the properties we plan to start with and respond to:
&nbsp;
&nbsp;export interface IAdvancedPagePropertiesProps {
context: WebPartContext;
title: string;
selectedProperties: string[];
themeVariant: IReadonlyTheme | undefined;
}
&nbsp;
&nbsp;
&nbsp;
We will track the state of our selected properties and their values with hooks.&nbsp; We want to trigger off of changes to our properties, so we will setup a reference to their current state.&nbsp; We will also establish our themeVariant and context at the start of our component.
&nbsp;
&nbsp; // Main state object for the life of this component – pagePropValues
const [pagePropValues, setPagePropValues] = useState<PAGEPROPERTY>([]);
const propsRef = useRef(props);

const { semanticColors }: IReadonlyTheme = props.themeVariant;

propsRef.current = props;

sp.setup({ spfxContext: props.context });
&nbsp;
&nbsp;
&nbsp;
So we are tracking the state of pagePropValues, which is an array of type PageProperty.&nbsp; What is PageProperty?
&nbsp;
&nbsp;import { IFieldInfo } from “@pnp/sp/fields”;

export interface PageProperty {
info: IFieldInfo;
values: any[];
}
&nbsp;
&nbsp;
&nbsp;
Our effect is looking to see when changes are made to the properties, then is peforming our core logic to refresh properties and values.
&nbsp;
&nbsp; /**
* @description Effects to fire whenever the properties change
*/
useEffect(() =&gt; {
refreshProperties();

return () =&gt; {
// No cleanup at this moment
};
}, [propsRef.current]);
&nbsp;
&nbsp;
&nbsp;
The core method is refreshProperties.&nbsp; It has 2 main calls it needs to make, whenever selected properties has changed: Establish any known metadata for each property that will assist in display and obtain all actual values for this property and the specific page id that we are viewing.
&nbsp;
&nbsp; /**
* refreshProperties
* @description Gets the actual values for any selected properties, along with critical field metadata and ultimately re-sets the pagePropValues state
*/
async function refreshProperties () {
var newSetOfValues: PageProperty[] = [];

if (props.selectedProperties !== undefined &amp;&amp; props.selectedProperties !== null) {
Log.Write(`${props.selectedProperties.length.toString()} properties used.`);

// Get the value(s) for the field from the list item itself
var allValues: any = {};
if (props.context.pageContext.listItem !== undefined &amp;&amp; props.context.pageContext.listItem !== null) {
allValues = await sp.web.lists.getByTitle(“Site Pages”).items.getById(props.context.pageContext.listItem.id).select(…props.selectedProperties).get();
console.log(allValues);
}

for (let i = 0; i &lt; props.selectedProperties.length; i++) {
const prop = props.selectedProperties[i];

Log.Write(`Selected Property: ${prop}`);

// Get field information, in case anything is needed in conjunction with value types
const field = await sp.web.lists.getByTitle(“Site Pages”).fields.getByInternalNameOrTitle(prop)();

// Establish the values array
var values: any[] = [];
if (allValues.hasOwnProperty(prop)) {
switch (field.TypeAsString) {
case “TaxonomyFieldTypeMulti”:
case “MultiChoice”:
values = _.clone(allValues[prop]);
break;
case “Thumbnail”:
values.push(JSON.parse(allValues[prop]));
break;

default:
// Default behavior is to treat it like a string
values.push(allValues[prop]);
break;
}
}

// Push the final setup of a PageProperty object
newSetOfValues.push({ info: field, values: […values] });
}

setPagePropValues({…newSetOfValues});
}
}
&nbsp;
&nbsp;
&nbsp;
As we loop through all of the properties that have been selected, we make calls with PnP JS to get all of the metadata per field and all of the values per field.&nbsp; The call to get all of the values can return with any number of data types, so we need to be prepared for that.&nbsp; This is why it is of type any[] to start.&nbsp; But this is also why we have a switch statement for certain outlier situations, where the line to set the array of any need to be done a little differently than the default.&nbsp; Our 3 known cases of needing to do something different are&nbsp;TaxonomyFieldTypeMulti, MultiChoice and Thumbnail.
&nbsp;
React and Display
Our function component returns the following:
&nbsp;
&nbsp; return (
<DIV classname=”{`${styles.advancedPageProperties}”>
{RenderTitle()}
{RenderPageProperties()}
</DIV>
);
&nbsp;
&nbsp;
&nbsp;
RenderTitle is pretty straightforward.
&nbsp;
&nbsp; /**
* RenderTitle
* @description Focuses on the 1 row layer, being the Title that has been chosen for the page
* @returns
*/
const RenderTitle = () =&gt; {
if (props.title !== ”) {
return <DIV classname=”{styles.title}”>{props.title}</DIV>;
} else {
return null;
}
};
&nbsp;
&nbsp;
&nbsp;
RenderPageProperties is the first of a 2-dimensional loop, where we want to display a section for each page property that was select, just like the original.
&nbsp;
&nbsp; **
* RenderPageProperties
* @description Focuses on the 2nd row layer, which is the property names that have been chosen to be displayed (uses Title as the display name)
* @returns
*/
const RenderPageProperties = () =&gt; {
if (pagePropValues !== undefined &amp;&amp; pagePropValues !== null) {
var retVal = _.map(pagePropValues, (prop) =&gt; {
return (
&lt;&gt;
<DIV classname=”{styles.propNameRow}”>{prop.info.Title}<SPAN style=”{{display:”> – {prop.info.TypeAsString}</SPAN></DIV>
<DIV classname=”{styles.propValsRow}”>
{RenderPagePropValue(prop)}
</DIV>

);
});
return retVal;
} else {
return <I>Nothing to display</I>;
}
};
&nbsp;
&nbsp;
&nbsp;
This method then calls our final display method, RenderPagePropValue, which peforms our 2nd layer of array display, mapping all of the values and providing the correct display, based on the field type of the selected property.&nbsp; This is the heart of the display, where various type conversions and logic are done real-time as we display the values, including trying to achieve a slightly more modern SharePoint look using capsules for array labels.
&nbsp;
&nbsp; /**
* RenderPagePropValue
* @description Focuses on the 3rd and final row layer, which is the actual values tied to any property displayed for the page
* @param prop
* @returns
*/
const RenderPagePropValue = (prop: PageProperty) =&gt; {
console.log(prop);
var retVal = _.map(prop.values, (val) =&gt; {
if (val !== null) {
switch (prop.info.TypeAsString) {
case “URL”:
return (
<SPAN classname=”{styles.urlValue}”><A href=”{val.Url}” target=”_blank” style=”{{color:” semanticcolors.link=””>{val.Description}</A></SPAN>
);
case “Thumbnail”:
return (
<SPAN><IMG classname=”{styles.imgValue}” src=”{val.serverRelativeUrl}” /></SPAN>
);
case “Number”:
return (
<SPAN classname=”{styles.plainValue}”>{(prop.info[“ShowAsPercentage”] === true ? Number(val).toLocaleString(undefined,{style: ‘percent’, minimumFractionDigits:0}) : (prop.info[“CommaSeparator”] === true ? val.toLocaleString(‘en’) : val.toString()))}</SPAN>
);
case “Currency”:
return (
<SPAN classname=”{styles.plainValue}”>{(prop.info[“CommaSeparator”] === true ? new Intl.NumberFormat(‘en-US’, { style: ‘currency’, currency: ‘USD’ }).format(val) : Intl.NumberFormat(‘en-US’, { style: ‘currency’, currency: ‘USD’, useGrouping: false }).format(val))}</SPAN>
);
case “DateTime”:
//,””,,
switch (prop.info[“DateFormat”]) {
case “StandardUS”:
return (
<SPAN classname=”{styles.plainValue}”>{new Date(val).toLocaleDateString()}</SPAN>
);
case “ISO8601”:
const d = new Date(val);
return (
<SPAN classname=”{styles.plainValue}”>{`${d.getFullYear().toString()}-${d.getMonth()}-${d.getDate()}`}</SPAN>
);
case “DayOfWeek”:
return (
<SPAN classname=”{styles.plainValue}”>{new Date(val).toLocaleDateString(“en-US”, { weekday: ‘long’, month: ‘long’, day: ‘numeric’, year: ‘numeric’ })}</SPAN>
);
case “MonthSpelled”:
return (
<SPAN classname=”{styles.plainValue}”>{new Date(val).toLocaleDateString(“en-US”, { month: ‘long’, day: ‘numeric’, year: ‘numeric’ })}</SPAN>
);
default:
return (
<SPAN classname=”{styles.plainValue}”>{new Date(val).toLocaleDateString()}</SPAN>
);
}
case “TaxonomyFieldTypeMulti”:
case “TaxonomyFieldType”:
return (
<SPAN classname=”{styles.standardCapsule}” style=”{{backgroundColor:” semanticcolors.accentbuttonbackground=””>{val.Label}</SPAN>
);
default:
return (
<SPAN classname=”{styles.standardCapsule}” style=”{{backgroundColor:” semanticcolors.accentbuttonbackground=””>{val}</SPAN>
);
}
} else {
return (<SPAN classname=”{styles.plainValue}”>N/A</SPAN>);
}
});
return retVal;
};
&nbsp;
&nbsp;
&nbsp;
&nbsp;
So that’s all of the necessary code.&nbsp; Here’s what the finished product looks like, compared to the original page properties web part.

&nbsp;
&nbsp;
This web part is now officially apart of the PnP Web Parts repository and can be found here.&nbsp; I would love to hear about improvements you’d like to see and obviously you are more than welcome to contribute.&nbsp; I already have a bit of a list of things I’d love to see it do.
&nbsp;
Other ideas for improvements:

Capsules to be linkable to either a search result page or a filtered view of site pages (we always get clien requests for this)
Support for People fields (this is the only thing lacking from the original)
Support for Boolean fields (just need the right idea for proper display, really)
Styling per property (ie. colorizing per property or something to that effect)
&nbsp;
Conclusion
Hopefully, I’ve gotten you excited about Page Properties again and you’ve learned a little along the way around how the current Page Properties part might be doing what it does under the hood.&nbsp; Please consider contributing and feel free to reach out to me anytime.&nbsp; Thanks for your time!
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;</PAGEPROPERTY></ANY></VOID></VOID>