Trigger an Azure web job from Microsoft Flow in Office 365

I spent some time figuring this out at the Office365/SharePoint hackathon Arctic SharePoint Challenge so thought I would share, as I didn’t find anyone having done exactly this before.

My use case is that you have a web job in Azure that runs a PowerShell-script that polls a list in a SharePoint site in Office 365 looking for new items. After new items arre added, the job picks them up, creates site collections and applies pnp templates. This works well as a scheduled job, but it would be nicer if we could trigger the job automatically so we don’t have to wait for the scheduled start time. Of course, this approach should work for any type of web job, as long as it has the web hook endpoint.

It turns out that you can trigger the web job with Flow! The steps are as follows:

  1. First you need the authentication tokens. Go to your App Service where you have the web job running, and click on Get publish profile 
  2. In that file, look for the publishprofile with publishMethod=”MSDeploy”. You need the userName and userPwd. E.g. in my case it was userName=”$ASPC2017″ userPWD=”LBxk5ttrvZTgAM7msxDGRZA0hy9Wws3gdNeuK33hacB52SSaAQRslmuzshzi”
  3. Then you need the web hook url. You find that by selecting your web job and clicking on properties.
  4. Go to Flow (e.g. via the list from where you want to trigger the web job) and create a new flow.
  5. The first flow step is to add a SharePoint action for “when an item is created”, and give the url to your site and select the list you want to use as input.
  6. The second flow step is to add an HTTP-action. Choose method POST, add the Uri to the web hook url. You don’t need to set headers and body. Choose Basic authentication and use the username and password from step 2.
  7. The final flow should look like the following 

That’s it! After an item is added to the list, your web job will hopefully trigger and start running. Hope this is helpful to someone.

JavaScript error itemPermMasks.customFromJson is undefined in ribbon.js

I had a weird issue in my dev. env. where after I added a search results web part to a web part page I got a weird error in the browser console (itemPermMasks.customFromJson is undefined in sp.ribbon.js), and some functionality stopped working – to be precise:

  • Ribbon buttons stopped working
  • JavaScript code that relied on SP.SOD.* stopped working

Google didn’t really give me anything of value to resolve the issue, and I couldn’t understand how any of the JavaScript could cause the error either. 

The solution – or rather, the workaround, turned out to be quite simple: Change the search results web part from rendering results synchroneously to asynchroneusly. This makes SharePoint load the scripts the web part depends on differently, and luckily this was enough to solve the issue in my case.

The 100% way of automatically updating Result Types (with PowerShell)

This will be a short and sweet post about how to update result types after display templates have changed. It’s a common scenario to update a display template with changes to managed properties. If your result types are using the display templates, you need to go to the result types and click update. Quite a cumbersome tasks if you are deploying changes to many display templates/result types.

Fortunately my superstar colleage Mikael Svenson has written about this before. This post is another way of doing the same as he is doing, but with PowerShell instead of server side code.

The process is quite simple:

  1. Get the result type names from search configuration file
  2. Get the display template used in the result type
  3. Get the managed properties used in the display template
  4. Update the result type with the display template properties

The relevant PowerShell snippet for this is as follows

function UpdateResultItemTypes([string]$Url, [string]$PathToSearchConfig) {
    Write-Host "Updating result type items at $Url"
    $site = Get-SPSite $Url
    [void] [Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server.Search") 
    
    $sspApp = Get-SPEnterpriseSSA
    if ($sspApp -eq $null) {
        Write-Error "Unable to get an instance of the Search Service application or proxy"
        return;
    }
    $fedManager = New-Object Microsoft.Office.Server.Search.Administration.Query.FederationManager($sspApp)
    $searchOwner = New-Object Microsoft.Office.Server.Search.Administration.SearchObjectOwner([Microsoft.Office.Server.Search.Administration.SearchObjectLevel]::SPSite, $site.RootWeb)

    Get-ResultItemTypeNames -PathToSearchConfig $PathToSearchConfig | % {
        $resultItemName = $_
        Write-Debug "Updating result item $resultItemName"
        $resultType = Get-SPEnterpriseSearchResultItemType -Owner $searchOwner -SearchApplication $sspApp | ? {$_.Name -eq $resultItemName}
        if ($resultType -ne $null) {
            $updatedProperties = GetResultItemUpdatedProperties -site $site -resultType $resultType
            if ($updatedProperties -ne $null) {
                Set-SPEnterpriseSearchResultItemType -Identity $resultType -SearchApplication $sspApp -Owner $searchOwner -DisplayProperties $updatedProperties
            }
        }
    }
}

function GetResultItemUpdatedProperties([Microsoft.SharePoint.SPSite]$site, $resultType) {
    $masterPageGallery = [Microsoft.SharePoint.SPList]$site.GetCatalog([Microsoft.SharePoint.SPListTemplateType]::MasterPageCatalog)
    $displayTemplateName = [System.IO.Path]::GetFileName($resultType.DisplayTemplateUrl)

    $displayTemplate = $masterPageGallery.Items | ? {$_.Name -eq $displayTemplateName}

    if ($displayTemplate -ne $null) {
        $properties = $displayTemplate["ManagedPropertyMapping"]

        [string]$propFormatted = ""
        $propArray = $properties.Split(",")
        $propArray | % {
            $pair = $_.Replace("'", "").Split(":")
            $propFormatted += $pair[$pair.length - 1] + ","
        }
        return $propFormatted.TrimEnd(",")
    }
    return $null
}

Content Filters – Search configuration made easy

In a recent project I’ve worked in we came up with the concept of Content Filters. In this post I will explain the general idea.

image

The rationale for creating the Content Filters is that to end users, configuring search results web parts can be hard. Even for SharePoint superusers and developers, you have to know a lot about how SharePoint search works, names of managed properties and fields etc. to be able to create advanced searches. We wanted to have a way for users to create powerful searches without having to configure search results web parts themselves.

image

We are storing queries into fields of the page, and then we have search results web parts provisioned on the page getting their queries from result sources which again are just picking up the query fields of the page. The result source query looks simply like this: {\Page.ContentFilterQuery1}

Since we have full control of the query, we can do cool things like letting the users write KQL, using fields on the page in different ways, include/exclude children terms for managed metadata searches, and more.

IntraDocumentStatus:Approved OR IntraDocumentStatus:Reviewed OR IntraDocumentStatus:"Pending Approval" OR IntraDocumentStatus:"Pending Reapproval" ContentTypeId:0x0101008C4E7537DBB64A9CB91C5D8112D44EA50113* owstaxIdIntraProductServiceMulti:#034ef7a04-73bf-4420-9e18-e8086d951de8

Code snippet: Example of a query picked up by the search web parts

We build the configuration model as a JSON object that is stored in a separate field on the page.

[{"_title":"Brochures","_model":{"_contentTypes":[{"Value":{"Id":"0x0101008C4E7537DBB64A9CB91C5D8112D44EA50113"}}],"_statuses":[{"Value":"Approved"},{"Value":"Reviewed"},{"Value":"Pending Approval"},{"Value":"Pending Reapproval"}],"_userInputQuery":"","_metadata":{}}},{"_title":"Media Content","_model":{"_contentTypes":[{"Value":{"Id":"0x0120D520A80800DF779D84BABF499C8C3CF9496D811B5001"}},{"Value":{"Id":"0x0101009148F5A04DDD49CBA7127AADA5FB792B00AADE34325A8B49CDA8BB4DB53328F21400C3AA00D5796C48639629BA0249C75A6D01"}},{"Value":{"Id":"0x0101009148F5A04DDD49CBA7127AADA5FB792B006973ACD696DC4858A76371B2FB2F439A008304A57B34E448A1B7D6A0C913EE1F8901"}}],"_statuses":[{}],"_userInputQuery":"","_metadata":{}}},{"_title":"Alerts","_model":{"_contentTypes":[{"Value":{"Id":"0x0101008C4E7537DBB64A9CB91C5D8112D44EA501010601"}}],"_statuses":[{"Value":"Approved"},{"Value":"Reviewed"},{"Value":"Pending Approval"},{"Value":"Pending Reapproval"}],"_userInputQuery":"","_metadata":{}}},{"_title":"Bulletins","_model":{"_contentTypes":[{"Value":{"Id":"0x0101008C4E7537DBB64A9CB91C5D8112D44EA501010602"}}],"_statuses":[{"Value":"Approved"},{"Value":"Reviewed"},{"Value":"Pending Approval"},{"Value":"Pending Reapproval"}],"_userInputQuery":"","_metadata":{}}},{"_title":"Lessons Learned","_model":{"_contentTypes":[{"Value":{"Id":"0x0101008C4E7537DBB64A9CB91C5D8112D44EA501010605"}}],"_statuses":[{"Value":"Approved"},{"Value":"Reviewed"},{"Value":"Pending Approval"},{"Value":"Pending Reapproval"}],"_userInputQuery":"","_metadata":{}}},{"_title":"Patents","_model":{"_contentTypes":[{"Value":{"Id":"0x010034F720BB1B564010999F8BAC472F7BEC0102"}}],"_userInputQuery":"","_metadata":{}}}]

Code snippet: Example of configuration object

Technically, the content filters are being created using JavaScript logic, building an array of content filter configurations. The logic is backed by a configuration file with names of managed properties, field names etc. that is being used to render the content filter form fields and “backend”. The frontend is using angular.js. The queries and fields are being saved to the fields on the page, without the end user knowing about it.

A very valid alternative to using search result web parts to pick up the queries is to roll your own search results. The benefit of this is that you can use the configuration object itself to perform the queries, and grab the results using the REST api. It’s an overall simpler approach and has less moving parts. The drawback of the approach is that you have to roll your own display templates for the results. With the result web parts approach we get the advantage of the SharePoint search toolstack, with result items, query rules, display templates etc.

image

Image above: The content filter web parts in display mode (with custom display template)

Search web parts show the same results after web parts are added to a page

Problem: After web parts were added to pages, visitors (users with read-only access) saw the same content in all search web parts.

Solution: The solution to this problems was to set the QueryGroupName property to unique GUID’s for all web part instances.

In my current project (SharePoint 2013, publishing site) we have a few page layout with quite a lot of search results web parts, both Search Results Web Parts and Content by Query Web Parts. In our provisioning, we use the same web part definition to provision all the web parts of the same type. This was working fine to our knowledge for a long while, until we granted visitors access to the site.

From time to time we upgrade the pages by removing all web parts and adding the updated web parts from the page layout. After these upgrades, we discovered that visitors saw the same content in all web parts that used the same web part definition for provisioning. We could fix the problem by having an user with owner access visit the page – then something wired up correctly in the background that fixed the web parts for all users. Of course this was not really a fix: In our solution we have hundreds of pages, and we upgrade all pages roughly once a month (after each sprint).

The QueryGroupName property of the web part

We found that the culprit was the QueryGroupName property of the web parts, which is a “native” property of the search web parts themselves, but it is also a property in the DataProviderJSON property of the web part defintion.

We first attempted to use the value “Default” as the property value, which was suggested by a few. Unfortunately this didn’t work. We also tried to not set the property at all, but leave it out of the web part definition, in hope that SharePoint could set a value automatically. This didn’t work either.

In all the errors above, ULS reported with the following error message for visitors (full stack trace left out):

System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)), StackTrace:   
 at Microsoft.SharePoint.WebPartPages.SPWebPartManager.SaveChangesCore(SPLayoutProperties layoutProperties, Boolean httpGet, Boolean saveCompressed, Boolean skipRightsCheck, Boolean skipSafeAgainstScriptCheck, WebPartTypeInfo& newTypeId, Byte[]& newAllUsersProperties, Byte[]& newPerUserProperties, String[]& newLinks)    

Setting new GUID’s with a token

The way we provision page layouts and web parts allows us to tap into the web part definitions before we add them to the page. We could therefore introduce a token “|NewGuid|” as the value of the QueryGroupName property, and on provisioning we replaced this token with a new, random GUID. This made all the web parts work again for all users.

Picture below: From the updated web part definition

webpartproperties_newguid

We used this simple line of code to update the web part definition before we added it to the page:

webpartDefinitionXml.Replace("|NewGuid|", Guid.NewGuid().ToString());