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

Summary

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.

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

https://$($orgName).api.identitynow.com/cc/api/app/list

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 {$_.name -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 : https://files.accessiq.sailpoint.com/modules/builds/static-assets/perpetual/identitynow/icons/2.0/n
ytimes/
health : @{status=HEALTHY; lastChanged=1539766560496; since=0; healthy=True}
enableSso : True
ssoMethod : PASSWORD
hasLinks : True
hasAutomations : True
primaryLink : https://myaccount.nytimes.com/auth/login
primaryMobileLink : https://myaccount.nytimes.com/mobile/login/smart/index.html?EXIT_URI=://mobile.nytimes.com/loginRet
urnthUK?url=/?redir_esc=
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

Summary

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;

Get-WorkdayWorkerProvData

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

Get-WorkdayWorkerMgmtData

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

Get-WorkdayWorkerPhoto

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'

Summary

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.

https://OrgName.api.identitynow.com/cc/api/role

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.

https://OrgName.api.identitynow.com/cc/api/source

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:

https://OrgName.api.identitynow.com/cc/api/role/list

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

Roles = Invoke-RestMethod -Uri "https://OrgName.api.identitynow.com/cc/api/role/list" -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.

"{`"id`":`"$($newRole.id)`",`"accessProfileIds`":[],`"selector`":{`"type`":`"COMPLEX_CRITERIA`",`"entitlementIds`":[],`"aliasList`":[],`"valueMap`":[],`"complexRoleCriterion`":{`"operation`":`"OR`",`"children`":[{`"operation`":`"EQUALS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"attribute.Supplier`",`"sourceId`":`"$($RoleSource)`"},`"value`":`"$($RoleCriteriaValue)`"}]}}}"
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`":`"$($newRole.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`":`"attribute.country`",`"sourceId`":`"$($RoleSource)`"},`"value`":`"New Zealand`"},{`"operation`":`"CONTAINS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"attribute.email`",`"sourceId`":`"$($RoleSource)`"},`"value`":`".com.au`"}]}]}}}"

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;

{`"operation`":`"EQUALS`",`"key`":{`"type`":`"IDENTITY`",`"property`":`"attribute.isemployee`"},`"value`":`"true`"}

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`":`"attribute.country`",`"sourceId`":`"2c91808365f742620165f9ba0e831bf8`"},`"value`":`"New Zealand`"},{`"operation`":`"CONTAINS`",`"key`":{`"type`":`"ACCOUNT`",`"property`":`"attribute.email`",`"sourceId`":`"2c91808365f742620165f9ba0e831bf8`"},`"value`":`".com.au`"}]},{`"operation`":`"EQUALS`",`"key`":{`"type`":`"IDENTITY`",`"property`":`"attribute.isemployee`"},`"value`":`"true`"}]}}}"

Summary

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.

https://OrgName.api.identitynow.com/v2/workgroups?&org=ORGName

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.

https://OrgName.api.identitynow.com/v2/workgroups/6289788a-c73c-426b-9170-12340aaa6789
Doing that via PowerShell looks like this:
Invoke-RestMethod -uri "https://OrgName.api.identitynow.com/v2/workgroups/6289788a-c73c-426b-9170-12340aaa6789" -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 vendor_admin@vendor.com.au)
      • 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

Summary

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.

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. https://TENANT.identitynow.com. 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).identitynow.com”; “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).identitynow.com/ui/admin”; “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;
https://TENANT.identitynow.com/login/login?goto=https%3A%2F%2FTENANT.identitynow.com%2Fui%2Fd%2Fdashboard
  • Head to the Admin Dashboard and you will be prompted for Strong Authentication
https://TENANT.identitynow.com/ui/admin#admin:dashboard:overview
    • 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

Summary

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.

Querying for updates/changes in Workday HR using PowerShell

Nathan Hartley has an awesome PowerShell Module for Workday that you can find here. I detailed how I’m using that module in this post here Building a Microsoft Identity Manager PowerShell Management Agent for Workday HR.

A large portion of that post detailed the nuances of working with the Worday API especially for implementations at scale. Those are constraints I have. Specifically I was looking for a couple more functions;

  • Changes since the last time I queried the API
  • Changes including those who are now Inactive workers*
  • in the summary PowerShell Object return Hire Date, Start Date, Active Status and Supplier

Not wanting to re-invent the wheel I forked Nathan’s Project and added those enhancements. The Github link is here https://github.com/darrenjrobinson/powershell_module_workdayapi

Querying changes from Workday using PowerShell

The following assumes you’ve imported the WorkdayAPI PowerShell Module and provided connection information for your Workday Tenant. e.g

The following will then give you a summary of the changes in the last 28 days.
Update line 35 to change this date range.

The output looks like this:

Delta Changes.PNG

To query for Contingent Workers updated within a date range (e.g. the last 28 days) use use the new Get-WorkdayWorkerAdv cmdlet with the -FromDate and -ToDate as shown below.

$datenow = Get-Date
$now = $datenow.AddMinutes(-1).ToString('o')
$from = $datenow.AddDays(-28).ToString('o')
$contractors=Get-WorkdayWorkerAdv-WorkerType Contingent_Worker_ID -FromDate $from-ToDate $now
$contractors.Count

Querying Inactive Users from Workday using PowerShell

-ExcludeInactive is the new switch. The default is still true. To return Inactive workers set it to false. e.g -ExcludeInactive ‘false’. This is exactly the same as the -Force* switch, it is just a bit more obvious for me.

$contractor = Get-WorkdayWorkerAdv -WorkerType Contingent_Worker_ID 123456 -ExcludeInactive 'false'

However another change is to return additional information for the worker such as Hire_Date, Start_Date, Active (boolean) and Supplier (for Contractors/Contingent Workers).

Extended PSObject Attributes.PNG

Summary

With a few modifications I’ve been able to get the additional information I require from Workday to drive a number of business functions. Having Nathan’s module and therefore working examples made it very easy to get up to speed with the Workday WebService. Thank’s Nathan.

 

AAD-Connect PTA with SSO & Kerberos Decryption Key Roll Over

When setting up PTA with SSO the Kerberos decryption keys must be rolled over every 30 days. Unfortunately Microsoft have not yet devised a streamline process to automate, but hoping to deliver within the next 6 months.  Till this is made available the following solution has been developed to automatically perform this function.

The problem is best illustrated in the following test environment which has three internal domains configured for seamless single sign-on.

AD-Connect Screen

As illustrated below the decryption keys should be rolled over every 30 days to ensure the platform remains secure and operational. If this does not occur the keys will eventually expire !

AD-Connect Screen2

Proposed Solution
Leveraging the existing AD-Connect Server and gMSA account the Windows Task Scheduler is used to run a custom PowerShell script every 30 days. Although the script must use both the Office365 Global Admin account and on premise PTA_SSO accounts the passwords are encrypted.

As a prerequisite step the service account which will be used to run the PowerShell account must be granted local Admin rights on the AD-Connect Server.  Illustrated below the account being leveraged is the gMSA account which is already being used by the AD-Connect service (acmegroup\adc_gmsa).

Pre_req1
To allow the service account to be able to run as a scheduled task open the Local Security Policy on the AD-Connect Server and choose Log on as a batch job. As depicted in the below screenshot add the gMSA account trailing with the $ symbol, choose Apply and OK.

Pre_req2
On the AD-Connect Server create two new folders (if they don’t already exist) on C:\ one called scripts and the other called temp.

Pre_req3
In the scripts directory copy the following two included PowerShell scripts.
PTASSO_RENEWAL.ps1
PTA_SSO_Key_Rollover_Task_Schedule.ps1 

Next login to your Domain Controller for the domain\s which are configured to leverage SSO and create a domain user account.  In Active Directory Users and Computers turn on the Advanced Features view if not already enabled by selecting View from the top menu bar.

Pre_req5

Create a user account, in this example it has been provisioned into a Service Account OU, ensure this account has a routable UPN suffix. As demonstrated in the following screen capture under the Account options section check both boxes for User cannot change password and Password never expires.

Pre_req4

If you have multiple domains create an account in each domain and use the same password, make note of the password as this will be used in a future step.

Next from the Computer OU find the AZUREADSSOACC object ( Right mouse button ) and chose Properties or using the left mouse button double left click the object.

Pre_req6

Navigate to the  Security tab and add the new PTA_SSO account created previously in the permissions box and only allocate write and Reset Password permissions, it does not require Change Password.

Pre_req7

These steps will need to be repeated for each domain if you have a multiple domain configuration.

Script Setup:
Edit the PTASSO_RENWAL.PS1 PowerShell Script using PowerShell ISE. An extract of the sections of script that needs re-configuring is included below.

Step 1.
Uncomment lines 33 and 34 of the script and replace the {password} section with the plain text passwords of the Office365 Global Admin Account and the PTA_SSO reset password account configured in the above steps. The example below demonstrates this change.

PowerShell is using the Windows Data Protection API to encrypt the password from plan text to a secure string and then converts this into a text string. By design only the original user and the original machine where the encryption was performed can use this file.  For this reason it is imperative the script is initially run manually through the Task Schedule service account at a later stage.

Step 2.
Edit line 42, 51 and 60 – this should reference the on-premise domains and user account that has write and reset password permission on the AZUREADSSOACC object.  If SSO is only configured for a single on-premise domain delete lines 48 to 64 and re-save the file otherwise edit the file as required.

Step 3.
Edit the PTA_SSO_Key_Rollover_Task_Schedule.ps1 PowerShell Script using PowerShell ISE  It is assumed the name of the file the task schedule will run has not been changed. If it has you must edit the syntax in Line 3 of the script.

Edit line 7 as the UserID value should be changed to reflect the account being used to run the task. If this is a gMSA account then remember to include the trailing $ symbol as depicted below and the password should remain configured as Password.

Step 4.
Re-save the file and then from an elevated PowerShell Window execute the PTA_SSO_Key_Rollover_Task_Schedule.PS1, this will create the following task schedule all configured and ready to be run.

Pre_req8

Step 5.
Run the task manually through the task scheduler, this will create two new encrypted files in the C:\Scripts folder which only the gMSA account can decipher as illustrated below.

Pre_req9

Login into the Office365 Tenant and check Azure Active Directory Admin > Azure AD Connect. Select the Seemless single sign-on link.

result1

Notice the status is now green and the key creation date has updated.

result2

If this has not worked check the C:\temp directory for an error log file and troubleshoot accordingly.

Step 6.
Storing passwords in clear text in scripts is a serious security risk, it is imperative to obfuscate these in your code. In this demonstration the passwords are encrypted by initially running the script manually through the scheduled task.

Once this has been performed open the PTASSO_RENWAL.ps1 script. using PowerShell ISE navigate to both lines 33 and 34 and comment out both of these lines using the # symbol at the beginning of the line. Also remove the clear text passwords and re-save the file.

Building a Microsoft Identity Manager PowerShell Management Agent for Workday HR

Before I even get started with this post, let me state that the integration I describe here is not a standalone solution. Integrating with Workday for any organisation of significant size will require multiple integration points each providing coverage for the scenarios for your implementation. I list a few in this post, but Alexander Filipin has already done an awesome job here.

You may state, that there is of course the Azure Active Directory Provisioning Service for Workday. But what if you need more granular customisation than that provides, or you have requirements to get that data to a number of other systems and you desire to have connectivity to the authoritative source? Those are requirements I had and why I built a Management Agent for Workday to consume Workday HR data directly.

As the title implies it uses the ever versatile Granfeldt PowerShell Management Agent. The other key component is a PowerShell Module that eases the integration with Workdays’ SOAP API. Specifically the Workday API PowerShell Module available here.

Enabling the Workday (Get_Workers) API

In order to access the Workday API you need to have an API  account created. I pointed the Workday Support guys to this Microsoft Azure Inbound Workday Provisioning Documentation. Specifically the ‘Configure a system integration user in Workday‘ section in that link.

Once enabled they were able to give me a Service and Tenant name along with a Username and Password.

  • when using this information your Username is the username and the tenant. So if the username is ‘API User’ and the Tenant is ‘Identity_Corp’ then loginID for our purpose is API User@Identity_Corp
  • the URL you are provided will combine the Service and Tenant names. It will look something like this for the Human Resources Endpoint https://wd3-impl-services1.workday.com/ccx/service/TENANTNAME/Human_Resources/v30.2
    • where wd3-impl-services1 is the Service Name

Install the WorkdayAPI PowerShell Module

On your FIM/MIM Sync Server you will need to install the Workday API PowerShell Module available here. You will need to install it using an Elevated PowerShell session.

Unblock the PowerShell Module and Scripts

After installing the Workday API PowerShell Module it should be located in ‘C:\Program Files\WindowsPowerShell\Modules\WorkdayApi’. You will need to unblock the module and scripts. Run the following two commands in an elevated PowerShell session.

Get-ChildItem 'C:\Program Files\WindowsPowerShell\Modules\WorkdayApi' | Unblock-File
Get-ChildItem 'C:\Program Files\WindowsPowerShell\Modules\WorkdayApi\scripts' | Unblock-File

Verify your Execution Policy

As the PowerShell Module is unsigned you might need to do something similar to the following. The Get-ExecutionPolicy -List command will show you what the Execution Policy settings currently are.

Set-ExecutionPolicy "Unrestricted" -Scope Process -Confirm:$false
Set-ExecutionPolicy "Unrestricted" -Scope LocalMachine -Confirm:$false

Import Analytics

50k records with just the base profile (no -include work or -include personal options) takes ~7 minutes to ‘stage’ into the connector space. 50k records WITH work and personal metadata takes ~32 hours at a pretty consistent rate of ~20 mins/500 user records.

If you are retrieving just the Base record then the networking receive bandwidth consumption is ~240kbps. When retrieving the full records as a batch process the networking receive bandwidth consumption it ~3Mbps as shown below.

Full Object Network Graph

Why is this important?

The first “FULL” Sync depending on how many records you have in Workday will alter the approach you will need to take in order to obtain them all. I found that trying to retrieve full records in one call for anything over ~5000 records got inconsistent. I wouldn’t get the full dataset and the machine running it would start to run out of resources (processing power and memory). If you have only a few thousand records, requesting full records in one call will probably suffice.

Now I have ~100k records to return. What I found worked best is to get just the base record for all users then the full record for each user using pagination (via PSMA Paged Imports; I have my set to 500). The the PSMA Paged Imports feature will process the objects through the MA 500 at a time. That way you’re not stressing the host running the Sync Engine to the maximum and you don’t have to wait an hour+ to see any processing of objects on the MA.

Once you have completed a Full Sync and you are of any significant scale you will want to perform Delta Sync’s for the objects that have changed since your last sync. I’m not going to cover that in this post, but in a separate one in the future.

Here is a screenshot of showing the time taken for a Stage (Import) of 50k objects. Just under 33 hours.

Import - Stage Only.PNG

Other Options for Scale

If you are a large organisation this solution isn’t necessary a valid one (in isolation) as I indicated in the opening paragraph. Consider it ancillary augmentation to a multi-pronged implementation (as described nicely by Alexander Filipin here). Potentially something like;

  • Azure Active Directory Inbound Provisioning for object creation
  • A Management Agent such as the one I describe in this post for certain aspects
    • and a modification or two to identify new accounts from a Base Workday discovery and only import the full object for them on workdays and a full sync on the weekends or
    • delta syncs using the Workday Transaction Log Criteria Data and Transaction_Date_Range_Data
      • I’ll cover this in a future post but essentially on every sync I store a cookie-file with the watermark of the time of the sync. On the next deltasync I retrieve the cookie-file with the timestamp and make a call to get all objects changed since the previous sync up to the current time

PSMA Workday Management Agent Script Files

Wow, what a lot of caveats and clarifications. But with all that said, below are base  Schema and Import Scripts examples for the Grandfeldt PowerShell Management Agent that leverages the Workday API PowerShell module.

Schema.ps1

The schema is the base schema for my tenant. You shouldn’t have to change anything here unless you are retreiving additional attributes you want in MIM.

Import.ps1

The import script leverages AuthN creds from the MA config. Make sure the Username is in the format of UserID@TenantName. Also update;

  • Line 10 for the location you put your extension as well as the 8.3 format path to the MA Debug folder
  • Line 30 for the correct Service and Tenant info
  • Make sure you have Paged Imports selected on the Global Parameters screen of the MA Configuration

Export.ps1

I haven’t provided an example. The Workday API PowerShell Module has examples for updating Email, Phone and Photos. You can implement what you require.

Summary

The sample Workday MA Config in this post will give you a base integration with Workday. It is unlikely that it will give you everything you need and there isn’t a single solution that probably will, unless your organisation is quite small. There are other options as mentioned in this post and also the Workday Reports REST API. But those are topics for future posts.

Automate the Generation of a Granfeldt PowerShell Management Agent Schema Definition File

Generating Schema.ps1 for the Granfeldt FIM/MIM PowerShell Management Agent

Getting started writing your first Forefront/Microsoft Identity Manager Granfeldt PowerShell Management Agent can be a bit daunting. Before you can do pretty much anything you need to define the schema for the PSMA. Likewise if you have written many, the generation of the schema file often seems to take longer than it should and can be a little tedious when all you want to do is write the logic for the Import and Export scripts.

After a few chats with Soren around enhancements for the PSMA I suggested it would be awesome if the generation of the schema.ps1 file could be (semi)automated. So here is my first stab at doing just that.

My approach is;

  • Using PowerShell get an object that represents an object that will be managed on the PSMA
  • Enumerate the Properties of the PSObject and generate the Schema script accordingly
  • All that is left to do afterwards is;
    • define your anchor
    • define the name of the ObjectType
    • combine multiples if your MA will have multiple ObjectClasses
      • Update $obj to $obj2 etc for any additional object classes residing in the same schema file

Below I provide four examples covering the script that generates the schema definition along with the output. The four examples cover;

  • Azure AD User
  • Azure AD Group
  • Workday User
  • Flat File CSV

Example 1: Azure Active Directory User

The example below utilises the AzureAD PowerShell Module to connect to Azure AD. It then gets a User Object (update line 7 for a user to retrieve) and enumerates the properties of the User to generate the Schema file.

The output looks like this:

$obj = New-Object -Type PSCustomObject
$obj | Add-Member -Type NoteProperty -Name "Anchor-YourAnchorATTR|String" -Value ""
$obj | Add-Member -Type NoteProperty -Name "objectClass|String" -Value "YourObjectClass"
$obj | Add-Member -Type NoteProperty -Name "AccountEnabled|boolean" -Value $true
$obj | Add-Member -Type NoteProperty -Name "AgeGroup|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "AssignedLicenses|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "AssignedPlans|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "City|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "CompanyName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ConsentProvidedForMinor|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Country|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "CreationType|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "DeletionTimestamp|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Department|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "DirSyncEnabled|boolean" -Value $true
$obj | Add-Member -Type NoteProperty -Name "DisplayName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ExtensionProperty|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "FacsimileTelephoneNumber|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "GivenName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ImmutableId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "IsCompromised|boolean" -Value $true
$obj | Add-Member -Type NoteProperty -Name "JobTitle|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "LastDirSyncTime|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "LegalAgeGroupClassification|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Mail|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "MailNickName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Mobile|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ObjectId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ObjectType|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "OnPremisesSecurityIdentifier|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "OtherMails|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "PasswordPolicies|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "PhysicalDeliveryOfficeName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "PostalCode|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "PreferredLanguage|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ProvisionedPlans|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "ProvisioningErrors|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "ProxyAddresses|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "RefreshTokensValidFromDateTime|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ShowInAddressList|boolean" -Value $true
$obj | Add-Member -Type NoteProperty -Name "SignInNames|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "SipProxyAddress|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "State|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "StreetAddress|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Surname|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "TelephoneNumber|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "UsageLocation|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "UserPrincipalName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "UserType|string" -Value "string"

Update the Anchor for the attribute you’d like to use. I recommend ObjectId and give the ObjectClass a name for how you’d like it represented on your MA (User, AADUser or similar) and save it as something like schema.ps1 in you MA folder and you can get started.

Example 2: Azure Active Directory Group

The example below utilises the AzureAD PowerShell Module to connect to Azure AD. It then gets a Group Object (update line 7 for a group to retrieve) and enumerates the properties of the Group to generate the Schema file

The output looks like this:

$obj = New-Object -Type PSCustomObject
$obj | Add-Member -Type NoteProperty -Name "Anchor-YourAnchorATTR|String" -Value ""
$obj | Add-Member -Type NoteProperty -Name "objectClass|String" -Value "YourObjectClass"
$obj | Add-Member -Type NoteProperty -Name "DeletionTimestamp|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Description|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "DirSyncEnabled|boolean" -Value $true
$obj | Add-Member -Type NoteProperty -Name "DisplayName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "LastDirSyncTime|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Mail|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "MailEnabled|boolean" -Value $true
$obj | Add-Member -Type NoteProperty -Name "MailNickName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ObjectId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ObjectType|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "OnPremisesSecurityIdentifier|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "ProvisioningErrors|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "ProxyAddresses|String[]" -Value ("","")
$obj | Add-Member -Type NoteProperty -Name "SecurityEnabled|boolean" -Value $true

Update the Anchor for the attribute you’d like to use. I recommend ObjectId and give the ObjectClass a name for how you’d like it represented on your MA (Group, AADGroup or similar) and save it as something like schema.ps1 in you MA folder and you can get started.

Example 3: Workday User

The example below utilises the Workday PowerShell Module to connect to Workday. It then gets a User Object (update line 7 for a user to retrieve) and enumerates the properties of the User to generate the Schema file.

Update

  • Line 6 for your ServiceName and Tenant.
  • Line 13 for an object to retrieve

This script differs from AAD User and Group above in that the PowerShell Object returned uses NoteProperty as the type. So I updated Line 14 for that. Also the attribute when parsed by Get-Member includes a value so I had to get a substring of the result for the attribute name. That is what this change does:

$d[1].substring(0,$d[1].indexof("="))

The output looks like this:

$obj = New-Object -Type PSCustomObject
$obj | Add-Member -Type NoteProperty -Name "Anchor-YourAnchorATTR|String" -Value ""
$obj | Add-Member -Type NoteProperty -Name "objectClass|String" -Value "YourObjectClass"
$obj | Add-Member -Type NoteProperty -Name "BusinessTitle|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "FirstName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "JobProfileName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "LastName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Location|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "PreferredName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "UserId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerDescriptor|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerType|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerTypeReference|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkSpace|string" -Value "string"

Example 4: Flat File CSV

The example below utilises a sample CSV file with headers. It uses the Header row to generate the Schema file. It defaults all columns to strings.

Update;

  • Line 2 for your CSV File Name

The output looks like this (for my CSV File):

$obj = New-Object -Type PSCustomObject
$obj | Add-Member -Type NoteProperty -Name "Anchor-YourAnchorATTR|String" -Value ""
$obj | Add-Member -Type NoteProperty -Name "objectClass|String" -Value "YourObjectClass"
$obj | Add-Member -Type NoteProperty -Name "id|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "name|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "displayName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "comments|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "created|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "endDate|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "lastLogon|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "modified|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "startDate|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "status|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "type|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "groups|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "costCenter|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "country|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "department|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "division|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "email|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "employeeNumber|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "familyName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "givenName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "honorificPrefix|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "honorificSuffix|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "locale|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "location|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "manager|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "middleName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "organization|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "phoneNumber|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "preferredLanguage|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "preferredName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "secondaryEmail|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "secondaryPhoneNumber|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "timezone|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "title|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "risk|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerWid|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerDescriptor|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "OtherId|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "JobProfileName|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkSpace|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerTypeReference|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkerType|string" -Value "string"
$obj

Summary

Using a simple script and an example object we can quickly create the basis for a Granfeldt PSMA Schema Definition script.
As shown with the Workday example a minor tweak was required, but it was still a lot quicker than generating manually.

Hopefully this helps you get started quickly with your first, or next PSMA that you are building.

Follow ...+

Kloud Blog - Follow