Azure Active Directory B2B Pending and Accepted User Reports

One of the benefits of Cloud Services is the continual enhancements that vendors provide based on feedback from their customers. One such item of feedback that Microsoft has heard often is the request to know what state a Guest user in Azure AD is in. In the last couple of days Microsoft exposed two additional attributes on the User objectClass in Azure AD;

  • externalUserState
  • externalUserStateChangeDateTime

B2B State Tweet.PNG

This means we can now query the Microsoft Graph for B2B users and understand if they have Accepted or are PendingAcceptance, and the datetime of the last change.

My customers have been wanting such information and would like to report on it. Here is an example PowerShell script I’ve put together that queries Microsoft Graph to locate B2B Users in the Accepted and PendingAcceptance states and outputs summary information into two HTML Reports. One for Accepted and one for PendingAcceptance.

Update the following script;

  • Line 2 for a Microsoft Azure PowerShell Module such as AzureAD that has the Microsoft.IdentityModel.Clients.ActiveDirectory.dll library in it
  • Line 5 for your Tenant name
  • Lines 15 and 16 for a Username and Password
  • Line 19 for where you want the Reports written to

Running the Script will then generate the Accepted and Pending HTML Reports.

Output Reports.PNG

Here is an example of the AcceptedB2BUsers HTML Report.

Accepted Report.png


With the addition of these two additional attributes to the Microsoft Graph we can now query for B2B users based on their State and using PowerShell quickly report on them.

Address Space maintenance with VNet Peering

I recently had a scenario where I wanted to add an address space to a Virtual Network and encountered an issue where it was not possible to modify the address space while VNet Peering was in use. This is likely due to the fact that the routes to the peered VNet that are applied through the peering only get updated at the time the peer is created and cannot be dynamically updated.

The following error detailed this.

Failed to save virtual network changes Failed to save address space changes to virtual network ‘vnet1’. Error: Address space of the virtual network /subscriptions/xxxxxxx/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1 cannot change when virtual network has peerings.


On the surface this isn’t a big deal, just delete the peering, modify the address space and re-create the peering. While the actual requirements to achieve the changes is straight forward the VNet I was working with was a Hub VNet, as such there were multiple VNet peerings in place to spoke VNets. Additionally, there was no documentation available on all of the configurations specific to the peering.

I wanted to export all of the peering configurations so that I had a backup and record of the configurations so that they could be re-applied the same way after the address space was added to the Hub network.

Exporting VNet Peering Configuration

The following snippet, will export all of the peering configurations for the VNet specified to a file named “vnetname-peerings.csv”. The configurations recorded for the peering include:

  • Name
  • ResourceGroupName
  • VirtualNetworkName
  • RemoteVirtualNetwork
  • AllowVirtualNetworkAccess
  • AllowForwardedTraffic
  • AllowGatewayTransit
  • UseRemoteGateways
  • RemoteGateways
  • RemoteVirtualNetworkAddressSpace

Important Note: Removing the VNet peering will disrupt communication between the VNets. You should plan for and accommodate this within your change control processes.

Now that that all of the peering configurations were exported, we can proceed to delete the peering and then make the required modifications to the address space.

Re-create VNet Peering Configuration

After the modifications were applied to the VNet, we are ready to re-create the peering configuration using the exported configuration. You can do this from the portal or PowerShell. In my example, I simply re-added it from the Portal using the values recorded from the exported CSV. If you wanted to use PowerShell the Add-AzureRmVirtualNetworkPeering command can be used, substituting the values that were exported into the CSV.

However, when I saved the configuration the following error was produced:

Failed to add virtual network peering ‘peer1’. Error: Cannot create or update peering /subscriptions/xxxxxxx/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/virtualNetworkPeerings/peer1 because remote peering /subscriptions/xxxxxxx/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet3/virtualNetworkPeerings/peer1 referencing parent virtual network /subscriptions/xxxxxxx/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1 is in Disconnected state. Update or re-create the remote peering to get it back to Initiated state. Peering gets Disconnected when remote vnet or remote peering is deleted and re-created.


The reason for this error is that the corresponding peering on the remote VNet was in a disconnected state.

As the error suggests you have 2 options to resolve this, update or re-create the peering to get it back into the initiated state. Once this is done we can then create the peering in the parent VNet. You could use the script snippet above to backup the peering configuration and then delete and re-create it. However the easiest option is just to update the peering using the following command:

Get-AzureRmVirtualNetworkPeering -VirtualNetworkName vnet3 -ResourceGroupName rg2 | Set-AzureRmVirtualNetworkPeering

Once this is done, if you check the remote peering you will see that it is back in the initiated state and ready for the remote peering to establish the connection.

We can now finish by creating the peering configuration and both sides of the peering will now show as connected.

Options to consider for SharePoint Framework solutions deployment

There are various options to package and deploy a SharePoint Framework solution and as part of packaging and deployment process, the developers have to identify a best approach for their team. Sometimes it becomes a nightmare to plan the right approach for your solution, if you haven’t weighed the options properly.

Working at multiple implementations of SPFx solution for sometime now, I have been able to get an idea of various options and approach for them. Hence in this blog, we will at these options and look at merits and limitations for each.

At a high level, below are the main components that are deployed as part of SPFx package deployment:

  1. The minified js file for all code
  2. The webpart manifest file
  3. Webpart compiled file
  4. The package (.pckg) file with all package information

Deployment Options

Please check this blog for an overview of the steps for building and packaging a SPFx solution. The packaged solution (.sppkg) file can then be deployed to a App catalog site. The assets of the package (point 1-3 of above) could be deployed by any of the four below options. We will look at the merits and limitations for each.

1. Deploy to Azure CDN or Office 365 CDN

The assets could be deployed to an Azure CDN. The deployment script is already a part of SPFx solution and could be done from within the solution. More detailed steps for setting this up are here.

Note: Please remember to enable CORS on the storage account before deployment of the package.

If CORS is not enabled before CDN profile is used, you might have delete and recreate the Storage account.


  • Easy deployment using gulp
  • Faster access to assets and web part resources because of CDN hosting
  • Add geographical restrictions (if needed)


  • Dependency on Azure Subscription
  • Proper set up steps required for setting up Azure CDN. In some cases if the CDN if not set properly, then the deployment has to be done again.
  • Since the assets are deployed to a CDN endpoint, so if assets need restricted access then this mayn’t be recommended

2. Deploy to Office 365 Public CDN

For this option, you will need to enable and set up Office 365 CDN in your tenancy before deployment. For more details of setting this up, check the link here.


  • Faster access to assets and web part resources because of CDN hosting
  • No Azure subscription requirement
  • Content is accessible from SharePoint Interface


  • Manual copy of assets files to CDN enabled SharePoint library
  • Office 365 CDN is a tenant setting and has to be enabled for the whole tenancy
  • Since the assets are deployed to a CDN endpoint, so if assets needs restricted access then this mayn’t be recommended
  • Accidental deletion could cause issues

3. Deploy to SharePoint document library

This is also an option to copy for the compiled assets to be copied to a SharePoint document library anywhere in the tenancy. Setting this up is quite simple, first set the setting “includeClientSideAssets”: false in package-solution.json file and then set the CDN details in write-manifests.json  file.


  • No need of additional Azure hosting or enabling Office 365 CDN
  • Access to Assets files from SharePoint interface


  • Manual Copy of assets file to SharePoint library
  • Accidental deletion could cause issues

4. Deploy to ClientAssets in App Catalog

From SPFx version 1.4, it is possible to include assets as part of the package file and deploy it to the hidden ClientAssets library residing in App Catalog. It is set in the package-solution.json file “includeClientSideAssets”: true.


  • No extra steps needed to package and deploy assets


  • Increases the payload of the package file
  • Risk for Tenant admins to deploy script files to the Tenant App Catalog.


In this blog, we saw the various options for SPFx deployment, and merits and limitations of each approach.

Happy Coding !!!


Managing SailPoint IdentityNow Applications via API with PowerShell

The SailPoint IdentityNow Request Center comes pre-populated with 130 Applications (as shown below) that by default are visible to users in the Dashboard and can be requested via the Request Center. Whilst this is great the majority are not often applicable and you need to configure each individual application to remove visibility and requestablity. You could of course ask your IdentityNow Support representative to do this for you, or you could manage it yourself. Lets go with option B and I’ll show you how.

Application List.PNG

To disable visibility of an Application, and to also remove it from being requested through the Request Center there are two options that need to be toggled off. Enabled For Users, and Visible in the Request Center. 

Application Settings 1.PNG

Say you want to remove all from being visible and requestable. You will need to open each app, toggle the slider and the radio button and select save. That’s a minimum of 4 mouse clicks and some mouse scrolling x 130, or do it via the IdentityNow API in < 60 seconds. Option B please.

Retrieving Applications

The URI to return all IdentityNow Applications is


Before you can call that URI you will need to be authenticated to IdentityNow. Follow this post and make sure you have the headers in the WebSession configured with the Bearer Access Token.

Then using PowerShell you can return all Applications with;

$appList = Invoke-RestMethod -Uri $appListURI -Method Get -WebSession $IDN

If you want to find a single app, find it by name using Select-Object | Where-Object

$myApp = $appList | Select-Object | Where-Object {$ -eq "New York Times"}

The Application PowerShell Object for the New York Times looks like;

id : 24184
appId : 11
serviceId : 32896
serviceAppId : 24184
name : New York Times
description : American daily newspaper
appCenterEnabled : False
provisionRequestEnabled : False
controlType : PERSONAL
mobile : True
privateApp : False
scriptName : C:1-new-york-times
status : ACTIVE
icon :
health : @{status=HEALTHY; lastChanged=1539766560496; since=0; healthy=True}
enableSso : True
ssoMethod : PASSWORD
hasLinks : True
hasAutomations : True
primaryLink :
primaryMobileLink :
stepUpAuthData :
stepUpAuthType : NONE
usageAnalytics : False
usageCertRequired : False
usageCertText :
launchpadEnabled : False
passwordManaged : False
owner :
dateCreated : 1522393052000
lastUpdated : 1539766536000
defaultAccessProfile :
service : New York Times
selectedSsoMethod : PASSWORD
supportedSsoMethods : 2
authenticationCookie : []
directoryPassword_supported : false
none_supported : true
passwordReplay_supported : true
proxy_supported : false
saml_supported : false
wsfed_supported : false
accountServiceId : -1
launcherCount : 0
accountServiceName :
accountServiceExternalId :
accountServiceMatchAllAccounts : True
externalId :
passwordServiceId : -1

Removing Applications from User Visibility

Let’s remove all Applications from user visibility (and the Dashboard). The process is simply to retrieve all Applications, then update each one to toggle off the options for visibility. The following script does just that.

After updating each app the Request Center is empty. Much quicker than hundreds of mouse clicks.

Request Center Empty.PNG


With the ability to retrieve Applications and update them via the API repetitive configuration becomes super quick.

WorkdayAPI PowerShell Module

Obtaining Workday HR Supervisory Hierarchy, Provisioning Flags and Photos with PowerShell

A few weeks back I posted this regarding using PowerShell and the Granfeldt PowerShell Management Agent to interface Microsoft Identity Manager with Workday HR. The core of this functionality is the WorkdayAPI PowerShell Module which I forked from Nathan and added additional functionality.

New WorkdayAPI PowerShell Module Cmdlets

This post details additional functionality I’ve added to the WorkdayAPI PowerShell Module. Updates include the following additional cmdlets;


Implementations of Workday obviously vary from organisation to organisation. Workday HR being an authoritative source of identity means it is also often the initiator of processes for Identity Management. For one of our customers base entitlements are derived from Workday Job Profile and may specify an Active Directory Account and an Office 365 License.

The Get-WorkdayWorkerProvData cmdlet is invoked from when calling Get_WorkdayWorkerAdv with the -IncludeWork flag. The result now returns ProvisioningGroup information as part of the returned PowerShell Object. As shown below Workday has indicated my Identity requires Active Directory and Office 365 (email).

Workday Provisioning Group.PNG

If you just want to return the Account Provisioning information you can with Get-WorkdayWorkerProvData.
Get-WorkdayWorkerProvData -WorkerId 181123 -WorkerType Employee_ID
Provisioning_Group Status   Last_Changed
------------------ ------   ------------
Office 365 (email) Assigned 1/05/2017 9:31:21 PM
Active Directory   Assigned 1/05/2017 9:57:37 PM


The Get-WorkdayWorkerMgmtData cmdlet is invoked from when calling Get_WorkdayWorkerAdv with the -IncludeWork flag. The result now returns MgmtData information as part of the returned PowerShell Object. The collection is an ordered list of the Supervisory Hierarchy for the object.

Mgmt Data.PNG

Expanding the collection we can see the Supervisory Hierarchy. Note: the top of the hierarchy is listed twice as in this organisation the top reports to himself. 

Mgmt Heirarchy 2.PNG

If you just want to return the Management Hierarchy you can with Get-WorkdayWorkerMgmtData.

Get-WorkdayWorkerMgmtData -WorkerId 1234 -WorkerType Employee_ID


The Get-WorkdayWorkerPhoto cmdlet is invoked from when calling Get_WorkdayWorkerAdv with the -IncludePhoto and -PhotoPath flags. The result will then output the photo to the path provided in the -PhotoPath option.

If you just want to export the Photo for a single user you can with Get-WorkdayWorkerPhoto.

Get-WorkdayWorkerPhoto -WorkerId 1234 -WorkerType Employee_ID -PhotoPath 'C:\temp\workday'


Using the WorkdayAPI PowerShell Module we can now access information to drive the provisioning process as well as understand identities placement in the Supervisory hierarchy. We can also obtain their Workday Profile photo and sync that to other places if required.

Managing SailPoint IdentityNow Roles via API and PowerShell

Managing SailPoint IdentityNow Role Groups typically involves leveraging the SailPoint IdentityNow Portal for the creation and on-going management. That’s because the API for Roles is not published or documented.

What happens then if you have many to create, update/manage? How does the IdentityNow Portal use the non-published undocumented API’s to create and manage them? I’ve worked it out and am documenting it here in the interim until the API’s get versioned and published.

Note: There is a chance that the Roles API may change, 
so use at your own risk. 

Roles API Authentication

First up the Roles API URI looks like this.

The /cc/ part is a good indication that the API isn’t documented, but also that the Authentication mechanism to interact with it is using a JWT Bearer Token. I covered how to obtain a JWT Bearer Token specifically for interacting with these API’s in this post here. I’m not going to cover that here so read that post to get up to speed with that process.

Associated with managing the criteria of Roles we will also need to reference IdentityNow Sources. We can do that via a non-published API too.

Listing all IdentityNow Roles

Similar to Governance Groups that I detailed in this post Roles can be returned on an individual basis (if you know the ID of the Role which you won’t). So the simplest way is to query and return them all (no, the Search (BETA) doesn’t support Roles either). The API to list all roles is:

Doing this then via PowerShell to return all roles looks like this:

Roles = Invoke-RestMethod -Uri "" -WebSession $IDN
and finding Roles you are interested in can be done using Where-Object
$Roles.items | Select-Object | Where-Object {$_.displayName -like "Kloud*"}

SailPoint Roles.PNG

Creating Roles

The example script below shows the generation of information for a Role Object. Specifically;

  • name
  • displayname
  • description
  • disabled (whether the Role is enabled or disabled)
  • owner (the IdentityNow NAME attribute value of the owner of the Role)

Executing this creates the Role.

Role Created.PNG

Looking in the Portal we can then see the Role.

Role Created in Portal.PNG

Managing Role Criteria

The Role Criteria will obviously depend on what your criteria is. Is it based on Standard Group Rules, a List of Identities or Custom? I’ll cover a List of Identities and Group based criteria.

Adding a List of Identities to a Role

This isn’t too dis-similar to Governance Roles. We search for the list of users we want to add to the Role and add them to a collection. The key difference is that we use Name instead of ID. We then specify the type of Criteria (Identity_List) and the ID of the Group to update.

Executing the Script

The example script below switches the Headers for Basic Auth to use the v2 endpoint to search and locate the users to add to the Role. It then switches the Headers over to JWT Bearer Auth, generates the body for the request to update the Role Group and updates the Role Group.

Executing the script looks as per below and runs through successfully.

Adding IdentityList Members to Role Group.PNG

Checking the Role Group in the IdentityNow Portal

Identity List Updated - Portal.PNG

Adding a Members Based on Criteria to a Role

Adding users to a Role based on Criteria is similar to above except rather than searching for Identities we are adding the criteria for a role.

The criteria can be based on an Identity Attribute an Account Attribute or an Entitlement. I’ll show using an Identity Attribute and an Account Attribute.

Account Attribute based Criteria

Account criteria is based on attributes available in the Schema of a Source. In order to reference the Source we need the ID of the Source. This is available via the non-published Source API cc/api/source

The example script below shows getting all Sources and finding the one you want to base your Role Criteria off (based on name). You can of course mix criteria across many sources, but I’m showing you doing it from one. Running the following script will return your Sources. You require the ID of the Source you want to leverage attributes from to base your criteria off. The attribute that contains this used further along in these examples is $RoleSource 

Now that we have the Source ID for the Source that contains the attribute that we want to base our Role Criteria on, we need to build the criteria. Let’s start with a simple one and Single Criteria.

Here is an example JSON document with a Single Criteria that contains the variables for the Role that we just created (Role ID), the Source ID for the Source with the attribute we are using for the criteria and the value we want to match (RoleCriteriaValue). The Attribute from my Source that I’m using is Supplier. Update for the attribute you’re using on your Source.

After converting the JSON object to a PowerShell Object using ConvertFrom-JSON it looks like this:
Single Role Criteria.PNG

Having it as a PowerShell Object also makes it easy to modify. If you wanted to change the criteria to match against, the Operator and/or the operation then just change the value. e.g. the following will change the value to match from “Kloud Solutions Pty Ltd” to “Darren J Robinson

$RoleCriteria.selector.complexRoleCriterion.children.value = "Darren J Robinson"

Convert back to JSON for Post via the webrequest to the API. That and updating the Role with the criteria thereby is;

Update Single Criteria.PNG

In the Portal we can see that the Criteria has been added.

Update Single Criteria - Portal.PNG

And you will notice I have a Role Refresh call after the update to update the Role Membership as it is now based on Criteria. If you are updating/adding criteria to numerous roles only call the Refresh at the end.

Role Refresh.PNG

Adding Multiple Criterion

Multiple criterion is just more information to pass to the update. The format can be a little confusing so here is a template for three criteria showing the three Operators (Equals, Contains and Not_Equals).

$RoleCriteria = "{`"id`":`"$($`",`"accessProfileIds`":[],`"selector`":{`"type`":`"COMPLEX_CRITERIA`",`"entitlementIds`":[],`"aliasList`":[],`"valueMap`":[],`"complexRoleCriterion`":{`"operation`":`"OR`",`"children`":[{`"operation`":`"AND`",`"children`":[{`"operation`":`"EQUALS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"attribute.Supplier`",`"sourceId`":`"$($RoleSource)`"},`"value`":`"$($RoleCriteriaValue)`"},{`"operation`":`"NOT_EQUALS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"`",`"sourceId`":`"$($RoleSource)`"},`"value`":`"New Zealand`"},{`"operation`":`"CONTAINS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"`",`"sourceId`":`"$($RoleSource)`"},`"value`":`"`"}]}]}}}"

When converted to a PowerShell Object and displayed it looks like this;

3 Account Criteria Options.PNG

Identity Attribute based Criteria

The last example is Identity Attribute Criteria. This is simpler than the Account Attribute Criteria as we just need to reference the Identity Attribute and don’t need to reference a Source. A single criteria for isEmployee  = True looks like this;


As a second Criteria Group though in the Portal it looks like this;

2nd Criteria Group.PNG

and for the JSON object (RAW without variablizing) looks like this;

"{`"id`":`"2c918086663fbbd0016612345678909876`",`"accessProfileIds`":[],`"selector`":{`"type`":`"COMPLEX_CRITERIA`",`"entitlementIds`":[],`"aliasList`":[],`"valueMap`":[],`"complexRoleCriterion`":{`"operation`":`"OR`",`"children`":[{`"operation`":`"AND`",`"children`":[{`"operation`":`"EQUALS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"attribute.Supplier`",`"sourceId`":`"2c91808365f742620165f9ba0e831bf8`"},`"value`":`"Kloud Solutions Pty Ltd`"},{`"operation`":`"NOT_EQUALS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"`",`"sourceId`":`"2c91808365f742620165f9ba0e831bf8`"},`"value`":`"New Zealand`"},{`"operation`":`"CONTAINS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"`",`"sourceId`":`"2c91808365f742620165f9ba0e831bf8`"},`"value`":`"`"}]},{`"operation`":`"EQUALS`",`"key`":{`"type`":`"IDENTITY`",`"property`":`"attribute.isemployee`"},`"value`":`"true`"}]}}}"


Using the cc/api/role APIs we can Create Role Groups, Update Roles Groups to contain Criteria and Refresh Role Groups to have the membership calculated. With the base functionality detailed we can now use this to create and manage our Role Groups through automation. Happy Days.

Managing SailPoint IdentityNow Governance Groups via the API with PowerShell

In this post I detail the management of SailPoint IdentityNow Governance Groups using the IdentityNow v2 API as the functions associated with Governance Groups is not currently detailed in the v2 API Documentation here (9 Oct 2018).

In order to interact with the v2 API you will need to use Basic Authentication which I detail in this post here. The common authentication/authorization piece from that post is also shown below.

Retrieving Governance Groups

Now that you’re authorized to IdentityNow using Basic Authentication we can look to retrieve Governance Groups. This can be achieved by calling the /v2/workgroups API.

Using PowerShell all Governance Groups can be returned by making the following API call.

$GovGroups = Invoke-RestMethod -Method Get -Uri "$($baseURI)/v2/workgroups?&org=$($orgName)" -Headers @{Authorization = "Basic $($encodedAuth)"}

To retrieve an individual group you need to know the ID of the Group. You can then retrieve it directly using the v2/workgroups API  e.g.
Doing that via PowerShell looks like this:
Invoke-RestMethod -uri "" -Method Get -Headers @{Authorization = "Basic $($encodedAuth)"}

It would be nice to search Governance Groups using the new Search (BETA) feature. But currently the only Groups that are returned via it are Entitlement Groups.

Searching for Governance Groups

As mentioned above the new Search Beta only returns Entitlement Groups. Retrieving Governance Groups via the Governance Group ID is fine, if you know it (which you won’t).  So here is my workaround for this. Retrieve all Governance Groups as detailed above using PowerShell and then use the power of PowerShell (Where-Object) to search and find the group you want.

$GovGroups = Invoke-RestMethod -Method Get -Uri "$($baseURI)/v2/workgroups?&org=$($orgName)" -Headers @{Authorization = "Basic $($encodedAuth)"}
$myGovGroup = $GovGroups | Select-Object | Where-Object {$_.description -like "Kloud*"}

The above looks through each of the Governance Groups to find the ones that contain the word Kloud in the Description field. 53 Groups returned and 2 meet the criteria.

Search Governance Groups.PNG

Creating IdentityNow Governance Groups

To create a Governance Group you will/should provide:

  • IdentityNow Governance Group
    • name (e.g Vendor XX)
    • description (e.g Vendo)
    • owner
      • displayName (e.g Vendor Admin)
      • emailaddress (e.g
      • id (eg 2c918084624f8b59016275c123456789)
      • name (e.g Vendor_Admin)

My approach is;

  • Use the Search API to search and find the user account that will be the Owner for the Governance Group
  • Create the Governance Group assigning the Owner

Here is an example of creating a single Governance Group implementing the approach above.

Executing the script successfully creates the group.

Create Governance Group.PNG

Looking at the Group in the Portal I can see that it has been created with the correct owner.

Governance Group Created

Now if you are like me and you have numerous Governance Groups to create you can of course have a list of Governance Groups to be created and loop through creating each one. Brilliant.

Managing Members of Governance Groups

Updating a group for membership is a simple case of sending through a collection of members to add/remove. This isn’t a replace operation but an addition. So if you want to add a single member, just send through the details to add that member and they will be added to the Governance Group. Likewise for removal.

Process overview;

  • taking a group that was just created we use the ID of that group to then update the membership
  • searching IdentityNow we find the members to add
  • generate the collection to add
  • update the group

Here is a sample wscript the performs that process.

And looking at the Governance Group in the IdentityNow Portal we can see the membership has been updated. Obtaining the Group (using the search method above) also allows for easy removal of all/any members.

Members Added to Governance Group.PNG


Using the v2/workgroups IdentityNow API we can create and manage Governance Groups. This is extremely powerful when you have many to create and manage.

Set up Accounts and secure passwords to run automation workloads in Azure Functions

In some of my previous blogs here, we have seen how we could use Azure Functions to to automate processes and SharePoint workloads.

Most of these jobs run using elevated or stored privileged accounts as the Azure Function is in a different context than the user context. There are various ways we could setup these accounts. Some of these approaches are below:

  1. Azure AD Service Accounts
    • Suitable for all operations
    • Need access to resource
    • Reusable across multiple workloads
  2. Azure AD Apps
    • Suitable for Graph Access
    • Need exact permissions set up
    • Might need Tenant Admin authentication
  3. SharePoint App Accounts
    • Suitable for SharePoint workloads.
    • Need Site and App specific privileges

The details of these accounts could be stored in the Azure Functions App Settings (for dev and production) or local.settings.json file during local development.

The most important consideration would be to prevent from exposing password details in the Azure functions in case of unauthorized access. There are two ways we could achieve this:

1. Encrypting the password and store in the Azure Function (PowerShell)
2. Using Azure Key Vault to store and access password details (C#)

Encrypting Passwords in Azure Functions

For doing this, first lets’ create an encrypted password using PowerShell using the script below.

Next, copy the file to a bin folder in Azure Function using Azure File Explorer (Application Settings -> App Service Editor) and decrypt using the code below

Using Azure Key Vault

For using Azure Key Vault, the steps are as below

1. Create an Azure AD App and get the Client ID and Client Secret

2. Create a Azure Key Vault and add the above Azure AD app to have Get Access to the key vault. The below permissions will suffix to read the secret.
Azure Key Vault Secret Permissions

3. Create Secret in key vault, then store the password and the secure Uri

4. Store the Secret Uri, Client ID and Client Secret in Azure App Settings

5. Use the below code to get the secure pass.


Hence above we saw how we could set up accounts in Azure Function for elevated access to SharePoint and Resource locations.

Leveraging v1, v2 and non-Published SailPoint IdentityNow API’s with PowerShell

This post supersedes my previous posts on leveraging the IdentityNow API’s in relation to API Authentication/Authorization;

Using this Compass document as my guide (which takes a bit of finding) I’ve automated the process of being able to use PowerShell to leverage the non versioned/published API’s. The method I show below is also valid for the documented and versioned API’s.

I show how I generate the necessary credentials required for:

  • IdentityNow Basic Authentication
  • IdentityNow Cookie Authentication
  • IdentityNow JWT oAuth Bearer Authentication

I also show working calls using Basic and JWT oAuth Authentication. The CCSessionID is extracted for use in Cookie Authentication for completeness, but everything can be accomplished using Basic and JWT oAuth Authentication. This document on SailPoint Compass provides more details on each.

Script Overview

Using PowerShell I;

  • authenticate to the IdentityNow Portal
  • elevate via authentication to the Admin section of the IdentityNow Portal
  • obtain the CCSESSIONID Cookie and the CSRF Token
  • provide the ability to add into the Web Session Header IdentityNow Basic or JWT oAuth Authentication credentials

That then allows access to the non-published API’s that are leveraged via the IdentityNow Portal and published API’s.

Obtaining the CSRF Token, JWT Access Token and Session Cookie Programatically with PowerShell

Update the Strong Authentication Methods for IdentityNow Admins

As we need to programatically authenticate to the Portal itself to get the necessary information required for API authorization, we need a mechanism that supports that. The only one that does easily is password. So you will need to enable that.

  • NOTE: chances are you’re using programmatic access to configure/ingest data or configuration as part of service enablement. Once that is done, REMOVE “re-entering password” as a Strong Authentication Method.

In the IdentityNow Portal go to Admin => Identities => Identity Profiles => IdentityNow Admins =>Enable By re-entering their password

Admin Centre Strong Auth Options.PNG

Using a Chrome Browser head to the IdentityNow Login screen for your Tenant. e.g. Press F12 for Developer Tools and then enter the Username and Password for an Admin account. Press the Record button (highlighted below, if it isn’t already red) to start recording and then press the Sign In button.

Portal Login Window

When the browser has logged you in, select the Network tab and then right-click on the ‘login’ entry in the trace. Select Copy and then Copy as PowerShell which will copy the webrequest that you just performed to login. Paste this into the script below on Line 25.

WebRequest Login

Next in the browser select Admin and then any of the Admin menu items. e.g. Dashboard => Overview. Select Enter password and check Remember my preference then Continue.

Record the Strong Auth Screen.PNG

The next screen will give you the prompt to supply your password again. Enter the password and then clear the traces from the Developer Tools pane and then make sure Record is enabled, then press Authenticate.

Record Password 2nd Screen.PNG

Select the strongAuthn trace from the Network tab, right-click and select Copy and then Copy as PowerShell. Paste this into the script below on Line 43. Don’t forget to edit it for the CSRF Token variable.  

Strong Auth Capture.PNG

The Script

Update the script below for your environment by:

  • Line 3 with your IdentityNow Organisation name
  • Line 9 with your IdentityNow API Client ID
  • Line 11 with your IdentityNow API Client Secret
  • Line 16 with your Admin login name for the IdentityNow Portal
  • Line 25 with the first PowerShell Webrequest you copied above
  • Line 43 update with the second PowerShell Webrequest you copied above. Make sure to go back and add in the variable for the CSRF Token as shown below. $($orgName) is optional.
    • example $admPortalAuthN = Invoke-WebRequest -Uri $adminStrongAuthURI -Method “POST” -Headers @{“Origin” = “https://$($orgName)”; “Accept-Encoding” = “gzip, deflate, br”; “X-CSRF-Token” = $($csrf); “Accept-Language” = “en-US,en;q=0.9”; “User-Agent” = “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36”; “Accept” = “*/*”; “Referer” = “https://$($orgname)”; “X-Requested-With” = “XMLHttpRequest”} -ContentType “application/x-www-form-urlencoded; charset=UTF-8” -Body “password=f2dbeb5a897abeeecafc9deee5612345678903456789bdedf7d42” -WebSession $IDN

Output from the Script

The screenshot below shows successfully making API calls to IdentityNow across the v1, v2 and the non versioned API’s after generating the necessary Authentication credentials.

Note that after calling the v1 and non versioned API’s I change the Headers to remove the Bearer Authorization Token and replace it with the Basic Authorization Token so that the v2 API’s can be called.

Examples Output.PNG

Optional: Obtaining the CSRF Token Manually

If you do want to obtain the CSRF Token manually then using a web browser.

  • Login to IdentityNow Portal main Dashboard with an Admin account
    • A direct URI looks like this;
  • Head to the Admin Dashboard and you will be prompted for Strong Authentication
    • At this point though you will also be able to retrieve the CSRF Token

Using the Chrome Developer Tools (e.g. pressing F12 in Chrome) select Console, then at the prompt type SLPT.CSRFToken You will then see your CSRF Token for that session.

CSRFToken via Browser.PNG


With the ability to programatically generate the necessary Authorization tokens for IdentityNow we can interact with the documented API’s along with the API’s that are being leveraged by the IdentityNow Admin Portal itself.

Provisioning complex Modern Sites with Azure Functions and Flow – Part 3 – Post Provisioning Site Configuration

In the previous two blogs part 1 and part 2, we looked at steps to create a Modern team site and apply a custom provisioning template to it. In this blog, we will have a look at the steps for the post provisioning process to implement site specific requirements. Some of them could be:

1. Apply default values to list fields
2. Create a bunch of default folders
3. Manage Security groups (SP level) and permission level.
4. Navigation level changes
5. Add/Enable web parts or custom actions (SPFx extensions)

Most of the above steps are part of SharePoint Provisioning processes for a long time, just are less complex now with Provisioning templates doing a lot of heavy lifting. I will be writing very soon about PnP Templates, do and don’ts.


One key point to add is that, with Modern Team Sites and Office 365 Groups we cannot add AD security groups into a Office 365 Unified Group. For more information, please see this link.

The apply template process (link) takes about 45-90 min (long running process) for complex templates so wouldn’t be possible to start the post process on a flow wait state. Hence, we could trigger the post provisioning process on update of an inventory item or poll an endpoint for the status. In our case, we triggered the process when updating the inventory list with a status that apply template process is complete.

Post Provisioning Process

1. The first step of the Post Provisioning proces is to make sure that noscript is enabled on the team site (link) and all dependencies are ready such as Term stores, Navigation items, Site Pages, Content types, site columns etc. For a complex site template, this step will check for failure conditions to make sure that all artefacts are in place.

Note: The below sequence of steps could vary based on the solution 
and site structure in place but this was the faster way to 
isolate issues and ensure dependencies.

After the failure checks are done, we will start with the Site structure changes and navigation changes. For implementing navigation changes, check the blogs here and here.

2. Next, we will update any site specific site columns, site content types and permission level changes. A upcoming blog will have more details to this.

3. After that, we will update the changes for list structure, we will move to list specific updates such as default value setting, modifying list properties etc.

4. Next let’s move on to Apps and Site Pages updates. These changes take time because of SharePoint ALM lifecycle any possible duplications. So error handling is the key. Please check the blog here and for steps to deploy app and web parts here.

5. Before we finalize the site, let’s provision folders and metadata. This is not a simple process if you have to set metadata values for large number of folders like in our case 800 recursive folders (child in parent). So we will use the metadata file override. All the values in the defaults file have to be hardcoded before override.

Note: The metadata file override is not generally a good approach 
because of possible corruption that renders the library unusable 
so do error handling for all cases. For the CSOM approach check here.

6. Finally, we will set the site Property bag values for Site to set some of the tags making the site searchable. Here is the blog for the same.


The above we saw the final process of Site Provisioning process with setting up site properties and attributes for preparing the site before handing it off to business for use.

Follow ...+

Kloud Blog - Follow