PnP PowerShell and more...

Here I occasionally post about Microsoft 365 Patterns and Practices in general and PnP PowerShell more specifically.

Cross Platform PnP PowerShell

2018-03-14 6 min read PowerShell SharePoint

For a long time I played with the thought: wouldn’t it be nice if we could use PnP PowerShell on other platforms than just Windows? I realize that the majority of you are most likely running Windows, but there is a (albeit minor) part of you that prefer to work on a Mac, and most likely even a smaller part runs on Linux. However, with the growth and functionality of Azure, another possible location to run your cmdlets show up: in a Docker image (which is based upon Linux for instance).

Until last year, it was not possible to do this, but then Microsoft released PowerShell as open source and that version was not based upon the .NET Framework, but based upon .NET Core: a version of the framework that actually runs as is on a Mac and on Linux.

I started to look into building a binary module for that version of the PnP Cmdlets, but pretty much ran into a blocker: CSOM only works on .NET Framework 4.X. So I dropped that approach, and experimented with a version that uses the same cmdlets towards the user, but uses the REST api behind the scenes. And that worked pretty well! It didn’t allow me the same level of authentication options, nor the flexibility the ’normal’ version of the cmdlets provide, but it was very functional.

REST based cmdlets

I started converting the cmdlets, one by one, to use the REST api. I started with the most important one, which provides the ‘glue’ to the other cmdlets: Connect-PnPOnline. That cmdlet, in its current shape, provides tons of ways to authenticate towards your environment: credentials based, access token based, APP Only based, certificate based, etc. If you never looked into the source code of that one, do so, it’s a complex cmdlet.

Using the REST api we’re a bit more limited when it comes to authentication. Or rather, I should say, without CSOM we’re more limited in what we can do. So I reverted back to the DeviceLogin approach. It works as follows (high level):

  • the application (in this case, the Connect-PnPOnline cmdlet) requests a devicelogin code from Azure.
  • That code is returned, and people will have to manually navigate to https://aka.ms/devicelogin.
  • The person then has to paste in that code. In the background the cmdlet will poll a specific address, and if the user correctly authenticated, and consented on the access, the address would return an access and refresh token.
  • This access token is then used in every subsequent request. If the access token expired we used the refresh token to acquire a new access token.

It worked, but it was sort of a ‘disconnected’ experience, it for instance didn’t allow you to run in an unattended manner, unless we stored the refresh token in some secure store. I also made it so to automatically launch the browser, but nevertheless, if the user closed the browser before providing consent, things where not working anymore.

We actually added the device login approach to every version of the Connect-PnPOnline cmdlet included the one for the .NET Framework, so the one that is out there for you to use right now. Use Connect-PnPOnline -PnPO365ManagementShell to authenticate with the device login approach.

So the task to convert all cmdlets to use the REST API started. And most could be converted, with the exception of the most used cmdlets: Get-PnPProvisioningTemplate and Apply-PnPProvisioningTemplate. Those couldn’t be converted to REST because they heavily rely on the functionality of the PnP Provisioning Engine behind the scenes. And that engine was written in CSOM.

Port the PnP Provisioning Engine to REST?

While parts of the engine could be ported, we have one major blocker: taxonomy/managed metadata. There is no SharePoint / Graph REST API for taxonomy. So while we theoretically could convert the engine (it would be an insane amount of work though) we would end up with a more limited version.

So, REST provided me with some options to port the cmdlets but the major blocker was the provisioning engine, which is for most people the reason to use the PnP PowerShell cmdlets.

CSOM on .NET Standard

When I mentioned my work, and demoed it to my fellow PnP Team Members, Bert stood up and said (it was on a Friday): “Give me the weekend, and I’ll get back to you on Monday”. He did. And he supplied me with a CSOM version that was recompiled for .NET Standard. Amazing work! Having that build I was able, with changes (mainly to the identity management part), to compile the PnP Core Library (which is where the PnP Provisioning Engine resides) to .NET Standard. And with CSOM and the PnP Core Library available on .NET Standard it was a simple task for me to compile the PnP PowerShell cmdlets for .NET Standard too.

.NET Standard or .NET Core?

We went for .NET Standard, which is a format that is readible by both the .NET Framework and the .NET Core Framework. The result is that the assembly can be used in both .NET Core and the .NET Framework. So with one library you can serve both the Windows PowerShell and PowerShell Core. And of course, you could use the .NET Standard version of CSOM also to build your own applications on a Mac, Linux, Android, iOS, etc. The sky is the limit.

The result

PSMac01

How further?

We started the discussion with the SharePoint Engineering team and explained to them how we did the port. We are now discussing if, how and in what way (current as of March 2018) this will be distributed. It’s too early currently to share the progress right now, but I have good hopes some solution will be found. The moment we have a legal and supported way (notice that we decompiled, modified and recompiled code that is copyrighted by MS) of distributing this we will provide you a module that can be shared through the normal channels like the PowerShell Gallery.

Until that time: check out the source code of both the PnP Core Library and PnP PowerShell to see how we did it. You’ll notice compiler directives here and there (#if NETSTANDARD2_0) that includes or excludes certain lines of a code from the compiler. That’s how we maintain one library that compiles for both platforms. We have 2 solution files for each project: one for the .NET Framework and one for .NET Standard, so make sure to open up the correct solution file if you want to look at the .NET Standard version of the project.