Tag: DevOps

Deploy SPFx app package to SharePoint from Azure DevOps with modern authentication

As you can see from my last posts I got heavily involved in dealing with SharePoint modern authentication in the recent past. To repeat once again:

Intro

If your tenant has turned off legacy authentication you cannot simply authenticate with PowerShell and UserCredentials anymore. To check this run the following script:

Connect-SPOService -Url "https://<Your-Tenant>-admin.sharepoint.com"

$tenantsettings = Get-SPOTenant

$tenantsettings.LegacyAuthProtocolsEnabled

And potentially modify this setting (turn on: $true)

Set-SPOTenant -LegacyAuthProtocolsEnabled $true

In one of my last posts I showed how modern authentication is handled in a PowerShell script, especially in an Azure Automation environment where you can store and retrieve the necessary self-signed certificate as an Azure Automation asset.

Now another challenge: Inside an Azure DevOps Release pipeline you have another one or two PowerShell tasks. In my scenario two, one for uploading the app package to the app catalog, a second one to upload the assets to a SharePoint library, that is your Office 365 public CDN.

In Azure DevOps Release (and Build) pipelines you have no capability to simply store a certificate as an asset. Have it in the source code might not be an option to not distribute it to any developer’s environment. I only found the way to store variables (strings!), if anyone else has a better idea, speak up please 🙂

Fortunately this is also a working scenario as you can connect to SharePoint with another variant of the Connect-PnPOnline cmdlet providing a PEMCertificate and a PEMPrivateKey, both represented by large strings. According to my last post both can be simply extracted from a certificate once it is created by the New-PnPAzureCertificate cmdlet.

$Password = "******"
$secPassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
$cert = New-PnPAzureCertificate -Out "AzureAutomationSPOAccess.pfx" - `
 -ValidYears 10 `
 -CertificatePassword $secPassword
 -CommonName "AzureAutomationSPOAccess" `
 -Country "DE" `
 -State "Bavaria"
$PEMCert = $cert.Certificate
$PEMSecret = $cert.PrivateKey 
Write-Host $PEMCert
Write-Host $PEMSecret

On top you also have to output the KeyCredentials and combine them with an Azure AD App registration.

Release pipeline basic configuration

In the past I mentioned Elio Struyf as a good resource for SPFx Azure DevOps Build&Release Pipelines but today I would also like to mention the blogpost of Giuliano de Luca which lists up all the necessary steps and options in a fantastic way.

In my simple scenario I picked most of the basics from him but I prefer to use a Release Pipeline only in staging environments (as you have to package for every stage again (changes in write-manifest.json for instance). But to simplify we will cover only one stage:

Simple Azure DevOps SPFx Release Pipeline (1 Stage)
Azure DevOps Release Pipeline – The Tasks

The first tasks are quite obvious. At first we follow a recommendation from Elio to use Node version 8 which significantly improves performance. Afterwards we run npm install, bundle and package our solution.

Here I want to give you a special hint on dealing with larger projects:

There I tend to use larger Git repositories, meaning not every SPFx solution has it’s own like the typical standard deployment demo scenario. That leads to the point where the standard file structure is not valid anymore. When entering the System.DefaultWorkingDirectory followed by your Artifact folder (“MM” in my example, see 1st screenshot) we come to different solution directories below:

For those solution directories (assume today you want to deploy Webpart1 but tomorrow Webpart2 and you all have them in one Git repository and all are handled by one configured Release pipeline) I created a Release variable localPath that can be set at releasetime.

Azure DevOps Release variables

This leds us to the point where we can relate to this in the npm install or our gulp tasks when it comes to a local file path:

npm install
gulp bundle task (gulp package-solution quite similar)

So depending on which webpart we want to deploy according to our variables above we would hand in different “packageFile” (we use it later!) and different “localPath” values and the task will find it’s package.json (for npm install) as well as it’s gulpfile.js

PowerShell Tasks and modern authentication

But now let’s come to the main point of this post. Let’s create the two PowerShell tasks to upload our app package and the assets to the library that represents our Office 365 public CDN. At first let me clarify which task to use as there are more outside (in the past for instance I used one from the marketplace). I now switched to the official Microsoft task.

Azure DevOps Task Template PowerShell (by Microsoft)

The first task is to upload the package to the app catalog. It is quite simple as beyond the script we do not really need to configure anything else. I skipped to give a working directory (I use cd in script instead) or provide environment variables here. Maybe worth to investigate in future.

Azure DevOps PowerShell task Deploy to App catalog

As the complete script did not fit into the screenshot here once again:

Write-Host ("Adding package " + $($env:packageFile) + " to the AppCatalog at https://" + $($env:tenant) + ".sharepoint.com/" + $($env:catalogsite))
cd $env:System_DefaultWorkingDirectory/MM/$env:localPath/sharepoint/solution/
Install-PackageProvider -Name NuGet -Force -Scope "CurrentUser" 
Install-Module SharePointPnPPowerShellOnline -Scope "CurrentUser" -Verbose -AllowClobber -Force
Import-Module SharePointPnPPowerShellOnline -Scope "Local" -WarningAction SilentlyContinue

Write-Host ("Connecting to Tenant " + $($env:tenant) + ".onmicrosoft.com with AppID " + $($env:SPOAppID))

Connect-PnPOnline –Url https://$(tenant).sharepoint.com/$(catalogsite) -Tenant "$(tenant).onmicrosoft.com" -ClientId $(SPOAppID) -PEMCertificate "$(PEMCertificate)" -PEMPrivateKey "$(PEMPrivateKey)"

Write-Output 'Connected to SPO'

Add-PnPApp -Path $env:packageFile -Publish -Overwrite
Write-Host Finished upload app package $($env:packageFile)

For debug reasons I left a couple of Write-Host but they are not necessary of course. Anyway the first shows quite well how to reference our release variables from above ($env:<ReleaseVariableName>).

After the first output we switch our current directory into our current solution and the folder where the .sppkg file resides.

Afterwards we install our PnP-PowerShell module with 3 lines of code.

But then it comes to the ‘magic’ point of this post, the modern authentication with PnP Online:

Connect-PnPOnline 
    –Url https://$(tenant).sharepoint.com/$(catalogsite) 
    -Tenant "$(tenant).onmicrosoft.com" 
    -ClientId $(SPOAppID) 
    -PEMCertificate "$(PEMCertificate)" 
    -PEMPrivateKey "$(PEMPrivateKey)"

We build our Url from known and obvious parts combined with our release variables, the Tenant as well.

We then hand in the ID of our App Registration and finally
our PEMCertificate and PEMPrivateKey string retrieved from our variable.

Finally I simplified the things again and added a simple Add-PnPApp cmdlet assuming SkipFeatureDeployment is always false. Giuliano de Luca shows a cool optional handling for this in his code repository.

The next task is quite the same:


Azure DevOps PowerShell task Upload to Office 365 public CDN
Write-Host ("Adding package " + $($env:packageFile)+ " to the AppCatalog at https://"+ $($env:tenant) + ".sharepoint.com/" + $($env:catalogsite))
cd $env:System_DefaultWorkingDirectory/MM/$env:localPath/

$cdnConfig = Get-Content -Raw -Path ("config\copy-assets.json") | ConvertFrom-Json
$bundlePath = $cdnConfig.deployCdnPath
$files = Get-ChildItem $bundlePath\*.*

Write-Output "Connecting to CDN-Site $env:CDNSite"

Connect-PnPOnline –Url $env:CDNSite -Tenant "$(tenant).onmicrosoft.com" -ClientId $(SPOAppID) -PEMCertificate "$(PEMCertificate)" -PEMPrivateKey "$(PEMPrivateKey)"

foreach ($file in $files) {
    $fullPath = $file.DirectoryName + "\" + $file.Name
    Write-Output "Uploading file $fullPath to folder $env:CDNLib"
    $f = Add-PnPFile -Path $fullPath -Folder $env:CDNLib
}

We do not need to install PnP Powershell anymore but now only cd to a different directory. Then we grab a JSon file from our repository to get a value from there. An elegant way to omit some release variables.

We once again authenticate with our certificate values to SharePoint.

Finally we use the (retrieved from Json config file) path value where our bundles reside to grab all files from there. We then iterate them one by one and upload them to the library/folder inside our CDN site.

Markus is a SharePoint architect and technical consultant with focus on latest technology stack in Office 365 and SharePoint Online development. He loves the new SharePoint Framework as well as some backend stuff around Azure Automation or Azure Functions and also has a passion for Microsoft Graph.
He works for Avanade and is based in Munich.
Although if partially inspired by his daily work opinions are always personal.

AppSettings in SharePoint Framework (SPFx) and Staging

With SharePoint Framework 1.6.0 the great capability to use 3rd party Apis went GA. To use such a 3rd party Api you regularly need to use a Url. A simple scenario how to do that is described in Microsoft’s tutorial or the other one when you have a multi-tenant scenario (your Office 365 tenant is not the same like the managed Azure Active Directory, which can be the case especially in staging environments).

When it comes to handling of Urls in enterprise solutions you automatically get to the staging challenge. You might have a test stage where your test SPFx webpart might want to call a test azure function and an acceptance SPFx webpart might want to call the acceptance version of your azure function for instance.

So how to handle that?

I assume you already have established a Release Pipeline in your VSTS environment. If not Elio Struyf is your go-to resource. Furthermore there is a great post from Velin Georgiev who shows us how to use AppSettings inside your SPFx project. His post gave me the inspiration for this alternative solution I want to show here.

According to his post we simply need an appSettings file with following content:

Additionally as per Velin’s post we also need the corresponding d.ts file which in this case might look like this:

To use this in your code when calling the aadHttpClient it would look like the following:

We first import our appSettings. For the sake of brevity here I combined retrieving the aadHttpClient with the Azure Function call. In line 6 respectively line 9 we use the AppId / Url of our function retrieved from the appSettings. So far, so good.

But now comes the new stuff: How to update this on each new stage deployment? Therefore in my case I decided to put ALL stage settings in another json file which I call appSettings.all.json:

As you can guess now during deployment I will hand in the current stage name/abbreviation and copy from the “all” source to appSettings the right Url / AppID combination (we come to that in a minute). But at first I want to answer another question: Why do I put all my settings in the source code and not in a DevOps variable in VSTS for instance? Well, this parameters are no Fort-Knox secret like passwords, credentials e.g. They will occur in the JS code finally anyway when the user runs it. And in big solutions with lots of webparts you might have ONE release pipeline but a significant number of Azure Functions to call, maybe? That is my reason for this way.

Let’s go on with the final thing, that is a new gulp task to copy the right Url/ID for the related stage. The gulp task you find below is slightly similar to those from Elio in his VSTS posts where he updates the write-manifest.

So what does the task (which is quite simplified to get an easy understanding). It first gets the stage by an input parameter (run “gulp update-appSettings –stage DEV” for instance). Stages per our definition in the appSettings.all.json are DEV, TST, ACT & PRD.
Next is to retrieve the stage-corresponding Url and ID parameter (line 11,12,15,16) and finally writes them to the target appSettings.json file (Line 15,16) and stores that file (Line 17). That’s it.

Hope this helps you enhancing your DevOps release pipeline for SharePoint Framework (SPFx) in VSTS.