SailPoint IdentityNow Governance Groups Management Agent for Microsoft Identity Manager

Last week I posted a SailPoint IdentityNow Roles Management Agent for Microsoft Identity Manager. Today I’m posting a sister for it, an IdentityNow Governance Groups Management Agent.

I’ve posted about Governance Groups before. See Managing SailPoint IdentityNow Governance Groups via the API with PowerShell. That post details creating and managing Governance Groups via the API.

This Management Agent is essentially the enumeration of Governance Groups in IdentityNow via API wrapped up in a PowerShell Management Agent. You can extend the management agent for managing Governance Groups to fit your needs.

Prerequisites

  • On your MIM Sync Server you will need the PowerShell Community Extensions (PSCX)  for the Get-Hash cmdlet
  • Authentication to IdentityNow for Governance Groups can leverage the v2 Authentication method. I cover  enabling that in this post here. You can also use the v3 Authentication method I detail here. If you do that you will need to appropriately secure the extra credentials as I show in the Roles Management Agent.
  • The Management Agent leverages the Granfeldt PowerShell Management Agent. Start here to get up to speed with that. As detailed above this is an Import only MA so I’m not providing an Export Script and the Password is redundant. The script files need to be present but will be empty

Schema Script

The Schema Script below covers the core attributes associated with IdentityNow Governance Groups.

Import Script

The Import Script unlike the Roles Management Agent can use v2 Authentication. As such we don’t need to perform additional effort to provide the necessary credentials.

Your v2 IdentityNow credentials need to be provided on the Management Agent Connectivity Configuration page. The Username and Password Authentication options take the v2 API Client ID and API Client Secret respectively.

IdentityNow Governance Groups MA Connectivity Username Password

NOTE: The Import Script is also configured to page the import of Governance Groups. The Page Size is configured in your Run Profile.

Make the following update for your implementation;

  • Line 24 for your IdentityNow Orgname

Customisation

Depending on what you want to do with it, will depend on how you want Identity Manger to consume the data. You will likely want to;

  • Create a new ObjectType in the Metaverse along with the attributes associated with IdentityNow Governance Groups
  • Flow the information in and perform any logic
  • Create an Export Script that will;
    • create Governance Groups in IdentityNow
    • update Governance Groups in IdentityNow

Summary

Using this base management we can get connectivity and visibility of IdentityNow Governance Groups in Microsoft Identity Manager.

SailPoint IdentityNow Roles Management Agent for Microsoft Identity Manager

This is the first post in a series where I will provide a number of base-level Management Agents for Microsoft Identity Manager to integrate with SailPoint IdentityNow. Whilst the two products have areas of competing/equivalent functionality there are other aspects where integration of the two compliment each other. Whilst that is not the purpose of this post, through the series of upcoming posts it will be relatively easy to extrapolate how the two products can happy co-exist and orchestrate each other for certain functions.

This Management Agent is for Microsoft Identity Manager to have visibility of IdentityNow Roles (see customisation at the end for me functionality).

For more information on IdentityNow Roles see this post where I detailed Creating Roles as well as updating/managing them via API. The MA also consumes whether the Role is requestable that I covered in this post.

Notes

  • The Management Agent is a Full Sync only Management Agent. This is because the IdentityNow API doesn’t expose differential style requests. That is also why this is a single function Management Agent (just for Roles).
  • The Management Agent is configured for Paging the results back into Identity Manger. For more details on that see this post.

Prerequisites

  • On your MIM Sync Server you will need the PowerShell Community Extensions (PSCX)  for the Get-Hash cmdlet
  • The Management Agent uses IdentityNow v3 Authentication. You will need to request the API Keys from your SailPoint Customer Success Manager. Details on v3 Authentication can be found in this post
  • The Management Agent leverages the Granfeldt PowerShell Management Agent. Start here to get up to speed with that. As detailed above this is an Import only MA so I’m not providing an Export Script and the Password is redundant. The script files need to be present but will be empty

Schema Script

The Schema Script below covers the core attributes associated with IdentityNow Roles.

Import Script

As IdentityNow v3 API Authentication requires a number of artifacts, we need to make sure we secure them all appropriately.

For the Admin Username and Password we will do that by exporting them to an XML file using Export-CLIXML and then in the Import Script, import them using Import-CLIXML. Those cmdlets respect the context by which the credentials were exported and will only be able to access them when imported under that same context. As our Management Agent will be run by the MIM Sync Server Service Account we need to create the credentials file using that login. To do that;

  • temporarily reconfigure the MIM Sync Service Account so that it can logon locally
    • On the MIM Sync Server open Local Security Policy = > Local Policies => User Rights Assignment => Deny log on locally and remove the MIM Sync Server Service Account
    • repeat for Deny access to this computer from the network
  • Logon to the MIM Sync Server using the MIM Sync Server Service Account
  • Run the following to create the credential file and put the credential file in the Extensions\yourRolesMA directory
$adminUSR = [string]"Partner_Admin".ToLower()
$adminPWDClear = 'myStr0ngP@$$w0rd'
$adminPWD = ConvertTo-SecureString $adminPWDClear -AsPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential $adminUSR,$adminPWD
$Credentials | export-clixml c:\temp\RoleAdminCred.xml
  • IMPORTANT: Add the MIM Sync Server Service Account back  into the Deny access to this computer from the network and Deny Logon Locally policies

The IdentityNow v3 API Credentials are stored on the Management Agent Connectivity Configuration page. The Username and Password Authentication options take the v3 API Client ID and API Client Secret respectively.

MA Configuration Username Password ClientID Client Secret.PNG

Make the following updates for your implementation:

  • Line 24 for your IdentityNow Organisation name
  • Line 27 for the location and name of the credentials file created above

Customisation

Depending on what you want to do with it, will depend on how you want Identity Manger to consume the data. You will likely want to;

  • Create a new ObjectType in the Metaverse along with the attributes associated with the Roles
  • Flow the information in and perform any logic
  • Create Roles in IdentityNow
  • Update Roles in IdentityNow

Summary

Using this base management we can get connectivity and visibility of IdentityNow Roles in Microsoft Identity Manager.

Granfeldt PowerShell Management Agent Schema HRESULT: 0x80231343 Error

Yesterday I was modifying the Schema configuration on a Granfeldt PowerShell Management Agent on a Microsoft Identity Manager 2016 SP1 Server.

I was changing the Anchor attribute for a different attribute and on attempting to refresh the schema or view the configuration I got the following error;

Unable to retrieve schema. Error: Exception from HRESULT 0x80231343

Unable to retreive Schema.PNG

I knew I’d seen this before, but nothing was jumping to mind. And this was a particular large Schema script.

After some debugging I realized it was because the attribute I had changed the Anchor attribute too, was also listed in the Schema script. It wasn’t obvious as the attribute entry was multiple pages deep in the script.

Essentially if you are seeing the error Unable to retrieve schema. Error: Exception from HRESULT 0x80231343 there are two likely causes;

  • you haven’t declared an Object Class e.g User
    • $obj | Add-Member -Type NoteProperty -Name “objectClass|String” -Value “User”
  • the attribute you have as your anchor is also listed as an attribute in the schema script e.g
    • $obj | Add-Member -Type NoteProperty -Name “Anchor-ObjectId|String” -Value “333a7e07-e321-42ea-b0a5-820598f2adee”
    • $obj | Add-Member -Type NoteProperty -Name “ObjectId|String” -Value “333a7e07-e321-42ea-b0a5-820598f2adee”
      • you don’t need this entry, just the Anchor entry

Hopefully this helps me quickly find the reason next time I make a simple mistake like this in the Schema script.

 

Using Invoke-WebRequest calls within a Granfeldt PowerShell MA for Microsoft Identity Manager

MIM Sync Service Account IE Security Settings

If you use PowerShell extensively you should be familiar with the Invoke-RestMethod cmdlet and the ability for PowerShell to call API’s and receive information. The great thing about Invoke-RestMethod is the inbuilt conversion of the results to PowerShell Objects. However there are times when you need the raw response (probably because you are trying to bend things in directions they aren’t supposed to be; story of many of my integrations).

From within Granfeldt PowerShell Management Agent script(s) that use Invoke-WebRequest calls, these will in turn leverage the Internet Explorer COM API on the local machine. That means the account that is performing those tasks will need to have the necessary permissions / Internet Explorer configuration to do so.

Enabling Invoke-WebRequest for the FIM/MIM Synchronization Server Service Account

Logon to your Microsoft Identity Manager Synchronization Server using the Service Account associated with the Forefront Identity Manager Synchronization Service. If you’ve secured the service account properly you will need to temporarily allow that service account to Log on Locally.

FIM Sync Service Account.PNG

Open Internet Explorer and open Internet Options. Select the Security Tab, the Internet zone and select Custom Level.

Locate the Scripting section and set Active scripting to Enable. Set the Allow websites to prompt for information using scripted windows to Disable. Select Ok and Ok.

MIM Sync Service Account IE Security Settings.PNG

With the configuration completed, go back and change back the Forefront Identity Manager Service Accounts’ local permissions so it can’t logon locally.

Your Import / Export Granfeldt PowerShell Management Agent Scripts can now use Invoke-WebRequest where the requests/responses use the Internet Explorer COM API and respect the Internet Explorer security settings for the user profile that the requests are being made by.

Hopefully this helps someone else that is wondering why the scripts that work standalone fail when operating under the FIM/MIM Sync Service Account by the Granfeldt PowerShell Management Agent.

Adding Delta Sync Support to the Microsoft Identity Manager PowerShell Management Agent for Workday HR

Recently I posted a sample Microsoft Identity Manager Management Agent for Workday HR. Subsequently I also posted about some updates I made to the WorkdayAPI PowerShell Module to enable functionality to specify the time period to return changes for. This post details updating  my sample Workday Management Agent to support Delta Synchronisation.

WorkdayAPI PowerShell Module

First up you will need the updated WorkdayAPI PowerShell Module that provides the Get-WorkdayWorkerAdv cmdlet and can take a time period to return information for. Get the updated WorkdayAPI PowerShell Module from here

Update the PowerShell Module on the MIM Sync Server. The module by default will be in the  C:\Program Files\WindowsPowerShell\Modules\WorkdayApi folder.

You will need to unblock the new files.

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

Updated Schema

In the updated Management Agent I’m also bringing into MIM additional attributes from the other enhancements I made to the PowerShell Module for HireDate, StartDate, EndDate, Supplier and WorkdayActive. The updates to the Schema.ps1 are shown below.

$obj | Add-Member -Type NoteProperty -Name "HireDate|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "StartDate|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "EndDate|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "Supplier|string" -Value "string"
$obj | Add-Member -Type NoteProperty -Name "WorkdayActive|Boolean" -Value $True

The full updated Schema Script is below;

With the Schema Script updated, refresh the Management Agent Schema.

Update Schema

You can then select the new attributes in the Workday MA under Select Attributes.

Select New Attributes.PNG

Then select Ok.

Attributes Selected.PNG

Updated Import Script

The Import Script has a number of changes to handle creating and updating a WaterMark File that is used to store the date stamp of the last run. Also updated in the Import Script is the change to use the Get-WorkdayWorkerAdv cmdlet over the Get-WorkdayWorker cmdlet so that a time period can be specified, and to retrieve the additional attributes we just added to the schema.

Update:

  • Line 11 for the path and name of the Watermark File you wish to use
  • Line 31 for the URI of your Workday Tenant

Executing the Management Agent using a Delta Import Delta Sync Run Profile

After creating a Delta Import Delta Sync Run Profile we can now run a Delta Sync. The following graphic is after seeding the WaterMark file (with the last run time in a format like this 2018-10-29T22:09:08.3628953+00:00), as by default without the WaterMark file being present a Full Import is performed by the MA as it doesn’t have a watermark to base the import time period on.

The changed records in Workday HR are then identified and those records obtained, imported and synchronised via the Management Agent.

Delta Sync.PNG

Summary

Using Delta Synchronisation functionality from Workday HR allows for much quicker synchronsiation from Workday HR to Microsoft Identity Manager.

Updated: Azure AD B2B Guest Invitations Microsoft Identity Manager Management Agent

In August I posted this that detailed Automating Azure AD B2B Guest Invitations using Microsoft Identity Manager. More recently Microsoft updated the Microsoft Graph to include additional information about Azure AD B2B Guest users and I wrote this that creates HTML Reports based off these new attributes.

That information is also handy when managing the lifecyle of Azure AD B2B Users. As we do that using Microsoft Identity Manager I’ve updated my Azure AD B2B Guest Invitation Management Agent for these attributes so they can be used in the lifecycle logic.

Updated Schema

I’ve updated the Schema script to include three new attributes that are shown in bold below in an extract from the Microsoft Graph.

odata.type : Microsoft.DirectoryServices.User
objectType : User
objectId : 38154c4c-a539-4920-a656-b5f8413768b5
deletionTimestamp : 
accountEnabled : True
creationType : Invitation
displayName : Rick Sanchez
givenName : Rick
mail : Rick.Sanchez@customer.com.au
mailNickname : Rick.Sanchez_customer.com.au#EXT#
otherMails : {Rick.Sanchez@customer.com.au}
proxyAddresses : {SMTP:Rick.Sanchez@customer.com.au}
refreshTokensValidFromDateTime : 2018-08-26T02:05:36Z
showInAddressList : False
surname : Sanchez
userPrincipalName : Rick.Sanchez_customer.com.au#EXT#@corporationone.onmicrosoft.com
userState : PendingAcceptance
userStateChangedOn : 2018-08-26T02:05:36Z
userType : Guest

Each are String attributes and I’ve named these;

  • B2BCreationType
  • B2BUpdatedDateTime
  • B2BExternalUserState

Here is the full updated Schema.ps1 Script.

Updated Import Script

The Import Script requires the following changes to bring in the B2B User State attributes.

 # B2B External User State for B2B Users from other AAD's 
if ($user.creationType) {$obj.Add("B2BCreationType", $user.creationType)} 
[string]$B2BUpdatedDateTime = $null 
if ($user.userStateChangedOn) {$B2BUpdatedDateTime = get-date($user.userStateChangedOn); $obj.Add("B2BUpdatedDateTime", $B2BUpdatedDateTime)} 
if ($user.userState) {$obj.Add("B2BExternalUserState", $user.userState)}

The full script with these additions is below. As per this post, make the following updates;

  • Change line 10 for your file path
  • Change line 24 for the version of an AzureAD or AzureADPreview PowerShell Module that you have installed on the MIM Sync Server so that the AuthN Helper Lib can be used. Note if using a recent version you will also need to change the AuthN calls as well as the modules change. See this post here for details.
  • Change line 27 for your tenant name
  • Change line 47/48 for a sync watermark file
  • The Import script also contains an attribute from the MA Schema named AADGuestUser that is a boolean attribute. I create the corresponding attribute in the MetaVerse and MIM Service Schemas for the Person/User objectClasses. This is used to determine when a Guest has been successfully created so their naming attributes can then be updated (using a second synchronisation rule).

Updating the Management Agent

With the updated Schema.ps1 and Import.ps1 scripts in place on the Synchronisation Server, using the Microsoft Identity Manager Synchronisation Service Manager right-click on the B2B Invitiation PSMA and select Refresh Schema.

Refresh B2B MA Schema.PNG

Select the Properties of the MA and choose Select Attributes.  Select the new Attributes.

Select New Attributes

Select Ok.

Select New Attributes - Selected

With the Schema updated and the Attributes selected a Stage/Full Sync can be performed. We now see the External User State, User Creation Type and External User Updated DateTime.

Updates with B2B External State.PNG

Summary

With a change to the Schema and Import B2B Invitation PSMA scripts we can now leverage the new B2B Attributes from the Microsoft Graph for use in our lifecycle management logic.

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.

Automating Azure AD B2B Guest Invitations using Microsoft Identity Manager

Update: Oct 30 '18 
Also see this post that adds support for Microsoft's updates 
to the Microsoft Graph to include additional information 
about Azure AD B2B Guest users.

Introduction

Earlier this year Microsoft released the Microsoft Identity Manager Azure AD B2B Management Agent. I wrote about using it to write to Azure AD in this post here. As detailed in that post my goal was to write to Azure AD using the MA. I provided an incomplete example of doing that for Guests. This post fills in the gap and unlike the note preceding the post indicates, I’ve updated my MA to use the Graph API over the Azure AD PowerShell Module. It does though work in unison with the Microsoft Azure AD B2B Management Agent.

Overview

The process is;

  • Using the Microsoft Azure B2B Management Agent connect to an Azure AD Tenant that contains users that you want to invite as Guests to your Tenant. Flow in the naming information for users and their email address and any other metadata that you need to drive the logic for who you wish to invite
  • Use my Azure AD B2B Invitation Management Agent to automate the invitation of users to your Azure AD Tenant using Azure AD B2B

My Azure AD B2B Invitation Management Agent works in two phases;

  1. Invitation of Users as Guests
  2. Update of Guests with naming information (Firstname, Lastname, DisplayName)

The Azure AD B2B Invite Management Agent uses my favorite PowerShell Management Agent (the Granfeldt PSMA). I’ve posted many times on how to configure it. See these posts here if you are new to it.

Prerequisites

Setting up an Azure AD User Account for the Management Agent

In your Azure AD create a New User that will be used by the Management Agent to invite users to your Azure AD. I named mine B2B Inviter as shown below.

Inviter Account.PNG

You then want to assign them the Guest inviter role as shown below. This will be enough permissions to invite users to the Azure AD.

Inviter Role.PNG

However depending on how you want these invitee’s to look, you probably also want their names to be kept consistent with their home Azure AD. To also enable the Management Agent to do that you need to also assign the User administrator role as shown below.

Add User Admin Role.PNG

Now log in using that account to Azure AD and change the password. The account is now ready to go.

Management Agent Scripts

The Management Agent uses the Granfeldt PowerShell Management Agent. This is a cut down version of my MIM Azure AD Management Agent. 

Schema Script

I’ve kept the schema small with just enough interesting info to facilitate the functionality required. Expand it if you need additional attributes and update the import.ps1 accordingly.

Import.ps1

The Import script imports users from the Azure AD Tenant that you will be inviting remote Azure AD users too (as Guests).

  • Change line 10 for your file path
  • Change line 24 for the version of an AzureAD or AzureADPreview PowerShell Module that you have installed on the MIM Sync Server so that the AuthN Helper Lib can be used. Note if using a recent version you will also need to change the AuthN calls as well as the modules change. See this post here for details.
  • Change line 27 for your tenant name
  • Change line 47/48 for a sync watermark file
  • The Import script also contains an attribute from the MA Schema named AADGuestUser that is a boolean attribute. I create the corresponding attribute in the MetaVerse and MIM Service Schemas for the Person/User objectClasses. This is used to determine when a Guest has been successfully created so their naming attributes can then be updated (using a second synchronisation rule).

Export.ps1

The Export script handles the creation (invitation) of users from another azure AD Tenant as Guests as well synchronizing their naming data. It doesn’t include deletion logic, but that is simple enough include a deletion  API call based on your MA Deprovisioning logic and requirements.

  • By default I’m not sending invitation notifications. If you want to send invitation notifications change “sendInvitationMessage“= $false to $true on Line 129. You should then also change the Invitation Reply URL on line 55 to your Tenant/Application.
  • Change Line 10 for the path for the debug logging
  • Change Line 24 as per the Import Script if you are using a different version of the help lib
  • Change Line 27 for your Azure AD Tenant Name

Declarative Sync Rules

I’m not going to cover import flow configurations on the MS Azure AD B2B MA here. See here for that. Below details how I’ve configured my Invitation MA for the Creation/Export functions. My join rule (configured in the Sync Engine Invitation MA Config) is email address as shown below. Not the best anchor as it isn’t immutable. But as we don’t know what the DN is going to be until after it is created this is the next best thing.

Join Rule.PNG

Creation Sync Rule

Here are the three attributes I’m syncing to the B2B Invite Management Agent to perform the invitation. I’m using the mail attribute for the DN as it matches the anchor for the schema. We don’t know what objectID will be assigned until the directory service does it. By using email/upn once created we will get the join and won’t end up with two objects on the MA for the same user.

Outbound Flow for Create 2.PNG

For Inbound I’m flowing in the AADGuestUser boolean value. You will need to create this attribute in the MetaVerse and then the MIM Service. In the MIM Service allow the Sync Service Account to manage the attribute and change the MIM Service Filter Permissions to allow Admins to use the attribute in Sets. Also on the MIM Service MA add an Export flow from the MV to the MIM Service for AADGuestUser.

Inbound Flow Create.PNG

Naming Update Sync Rule

The second Sync Rule is to update the guests GivenName, Surname and DisplayName. This sync rule has a dependency on the creation sync rule and has a corresponding Set, Workflow and MPR associated with value of the AADGuestUser boolean attribute populated by the Import script. If True (which it will be after successful creation and the confirming import) the second synchronization rule will be applied.

Sync Naming Synchronisation Rule.PNG

I will trigger an export flow for the three naming attributes.

Outbound Flow for Naming.PNG

Example of Inviting Guests

In this example Rick Sanchez is a member of a guest organisation and meets the criteria of my rules to be invited as a guest to our Tenant. We then, that we get an Add for Rick on the B2B Invite MA.

Create Rick Sanchez.PNG

On export Rick is created in our Azure AD as a Guest User

Rick Created Sync Engine.PNG

Rick appears in Azure AD as a Guest via the Azure Portal.

Rick Created AzureAD.PNG

Following the confirming import our second sync rule fires and flows through an update to DisplayName and adds GivenName and Surname.

Update Rick.PNG

This naming attributes are then successfully exported.

Success Export.PNG

Going to the Azure AD Portal we see that Rick has indeed been updated.

Rick Updated.PNG

Notification Emails

If you enable notification emails a generic notification email is sent like shown below. The import.ps1 MA script has them disabled by default.

Email Invite Notification2.PNG

Summary

Using a combination of the Microsoft Azure AD B2B Management Agent and my Azure AD B2B Invitation Management Agent you can automate the invitation of Guest users to your Azure AD Tenant.

Identifying Active Directory Users with Pwned Passwords using Microsoft/Forefront Identity Manager v2, k-Anonymity and Have I Been Pwned

Microsoft Identity Manager - Have I Been Pwned

Background

In August 2017 Troy Hunt released a sizeable list of Pwned Passwords. 320 Million in fact.

I subsequently wrote this post on Identifying Active Directory Users with Pwned Passwords using Microsoft/Forefront Identity Manager which called the API and sets a boolean attribute in the MIM Service that could be used with business logic to force users with accounts that have compromised passwords to change their password on next logon.

Whilst that was a proof of concept/discussion point of sorts AND  I had a disclaimer about sending passwords across the internet to a third-party service there was a lot of momentum around the HIBP API and I developed a solution and wrote this update to check the passwords locally.

Today Troy has released v2 of that list and updated the API with new features and functionality. If you’re playing catch-up I encourage you to read Troy’s post from August last year, and my two posts about checking Active Directory passwords against that list.

Leveraging V2 (with k-Anonymity) of the Have I Been Pwned API

With v2 of the HIBP passwod list and API the number of leaked credentials in the list has grown to half a billion. 501,636,842 Pwned Passwords to be exact.

With the v2 list in conjunction with Junade Ali from Cloudflare the API has been updated to be leveraged with a level of anonymity. Instead of sending a SHA-1 hash of the password to check if the password you’re checking is on the list you can now send a truncated version of the SHA-1 hash of the password and you will be returned a set of passwords from the HIBP v2 API. This is done using a concept called k-anonymity detailed brilliantly here by Junade Ali.

v2 of the API also returns a score for each password in the list. Basically how many times the password has previously been seen in leaked credentials lists. Brilliant.

Updated Pwned PowerShell Management Agent for Pwned Password Lookup

Below is an updated Password.ps1 script for the previous API version of my Pwned Password Management Agent for Microsoft Identity Manager. It functions by;

  • taking the new password received from PCNS
  • hashes the password to SHA-1 format
  • looks up the v2 HIBP API using part of the SHA-1 hash
  • updates the MIM Service with Pwned Password status

Checkout the original post with all the rest of the details here.

Summary

Of course you can also download (recommended via Torrent) the Pwned Password dataset. Keep in mind that the compressed dataset is 8.75 GB and uncompressed is 29.4 GB. Convert that into an On-Premise SQL Table(s) as I did in the linked post at the beginning of this post and you’ll be well in excess of that.

Awesome work from Troy and Junade.