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

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

 

Background

In August 2017 Troy Hunted 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 Tory and Junade.

 

Provisioning Hybrid Exchange/Exchange Online Mailboxes with Microsoft Identity Manager

Introduction

Working for Kloud all our projects involve Cloud services, and all our customers have varying and unique requirements. Recently one of our customers embarked on their migration from On-Premise Exchange to Exchange Online. Nothing really groundbreaking there though, however they had a number of unique requirements including management of Litigation Hold. And that needed to be integrated with their existing Microsoft Identity Manager implementation (that currently provisions new users to their Exchange 2013 environment). They also required that management of the Exchange environment still be possible via the Exchange Management Console against a local Exchange server. This post details how I integrated the environments using MIM.

Overview

In order to integrate the Provisioning and Lifecycle management of Exchange Online Mailboxes in a Hybrid Exchange with Microsoft Identity Manager I created a custom PowerShell Management Agent simply because it was going to provide the flexibility I needed.

Provisioning is based on the following process;

  1. MIM Creates new user in Active Directory (no changes to existing MIM provisioning process)
  2. Azure Active Directory Connect synchronises the user to Azure Active Directory
  3. The Exchange Online MIM Management Agent sees the corresponding AAD account for the new user
  4. MIM Declarative Rules trigger the creation of a new Remote Mailbox for the AD/AAD user against the local Exchange 2013 On Premise Server. This allows the EMC to be used to manage mailboxes On Premise even though the mailbox resides in Office365/Exchange Online
  5. AADC/Exchange synchronises the information as part of the Hybrid Exchange topology
  6. MIM sees the EXO Mailbox configuration for the new user and enables Litigation Hold against the EXO Mailbox (if required)

The following diagram graphically depicts this process.

EXO IDM Provisioning Solution.png

Exchange Online PowerShell MA

As always I’m using my favourite PowerShell Management Agent, the Grandfeldt PS MA now available on Github here.

Schema Script

The Schema script configures the schema required for current and future EXO management requirements. The Schema is based on a single Object Class “MailUser” but pulls the information from a combination of Azure AD User and Exchange Online Mailbox object classes for an associated account. Azure AD User objects are prefixed by ‘AAD’. Non AAD prefixed attributes are EXO Mailbox attributes.

Import Script

The Import script connects to both Azure AD and Exchange Online to retrieve Azure AD User accounts and if present the associated mailbox for a user.

It retrieves all Member AAD User Accounts and puts them into a Hash Table. Connectivity to AAD is via the AzureADPreview PowerShell module. It retrieves all Mailboxes and puts them into a Hash Table. It then processes all the mailboxes first including the associated AAD User account (utilising a join via userPrincipalName).

Following processing all mailboxes the remainder of the AAD Accounts (without mailboxes) are processed.

Export Script

The Export script performs the necessary integration against OnPremise Exchange Server 2013 for Provisioning and Exchange Online for the rest of management. Both utilise Remote Powershell. It also leverages the Lithnet MIIS Automation PowerShell Module to query the Metaverse to validate current object statuses.

Wiring it all up

The scripts above will allow you to integrate a FIM/MIM implementation with AAD/EXO for management of users EXO Mailboxes. You’ll need connectivity from the MIM Sync Server to AAD/O365 in order to manage them.  Everything else I wired up using a few Sets, Workflows, Sync Rules and MPR’s.

Synchronizing Passwords from Active Directory to the IBM/Lotus Domino Identity Vault using Microsoft Identity Manager – Part 3

Introduction

As the title suggests this is Part 3, and the final part in a three-part post on configuring FIM/MIM to synchronise users passwords from AD to the Domino ID Vault via PCNS and FIM/MIM.
Part 1 here detailed the creation of a PowerShell Management Agent to join users from Domino to the MIM Sync Metaverse.
Part 2 here detailed the creation and configuration of the Domino Agents to receive password changes via the PS MA into the ID Vault.

This post will wrap it all up with the details on calling the Domino Agents on password sync events (from PCNS via MIM)

Prerequisites

You will need the IBM Notes client installed and configured on your MIM Sync Server in order to put a document in the database we created in Part 2 and start the agent to process the document(s).

Overview

Essentially this is the process;

  • Password changed for a user (either by an admin, or by the user via their domain joined workstation, password reset or any other password change mechanism)
  • Password change is captured by the AD PCNS Filter installed and configured on each (writeable) Domain Controller
  • The DC using the PCNS Config in the domain locates the MIM Sync Server to send the password change too
  • The MIM Sync Server has the associated AD Domain configured as a Password Sync Source
  • Our new PowerShell ID Vault Notes MA is configured as a Password Target
  • MIM Sync passes off the password change event for MIM joined users to the ID Vault Password Change MA which initiates the Password.ps1 script (below)
  • The password.ps1 script creates a document (that contains the details for the password change) in our ID Vault Password Sync Database we created in Part 2 of this series and then tells the MIMPwdTrigger Agent to start
  • The MIMPwdTrigger Agent picks up the document, passes it to the MIMPasswordSync Agent which sends the password change to the ID Vault

Domino PowerShell Management Agent Password.ps1 Script

Put this Password.ps1 script in the same location you put the Schema, Import and Export scripts earlier.

Testing Password Sync End to End (Active Directory to the ID Vault)

The following screen shots show me tracing through the logs for a password change as it makes it way from the AD Domain Controller to MIM Sync to the MA to the MA Password script to the Notes DB as a document triggered to be process by the Notes Agent and the user updated in the ID Vault.

First the password change event is initiated to the MIM Sync Service by the Domain Controller that captured the password change.

Password Change1

PCNS provides all the details for the password change.

Password Change2

The MIM Sync Server determines where to send the change which includes our PS Notes MA.

Password Change3

Our PS Notes MA logged the process.

Notes MA LOG

=============================================================

Display Name: Jane XXX/xxx/xxxxx-Aus

Action: Set

Old pwd:

New pwd: Password123456

Unlock: False

Force change: False

Validate: False

Database: System.__ComObject

As did the Notes Agent as it process the change.

Notes Agent Log

MIMPasswordSync|mimpasswordsync: 08/03/2017 02:56:22 PM: Reseting password …

MIMPasswordSync|mimpasswordsync: 08/03/2017 02:56:22 PM: Server: xxxNotes1/xxxxx-Aus User:Jane xxx/xxx/xxxxx-Aus

MIMPasswordSync|mimpasswordsync: 08/03/2017 02:56:23 PM: Return value: true

MIMPasswordSync|mimpasswordsync: 08/03/2017 02:56:23 PM: Removed User ID Vault change document from ‘xxxNotes1/xxxxx-Aus’

And finally we see the change reflected in the ID Vault. Looking at the time-stamps along the way we see that it all happened in approximately 2 seconds.

Password Change4

Summary

This three-part blog post has shown how to get passwords from Active Directory to the MIM Sync connected source across to IBM Domino and into the ID Vault using the Granfeldt PowerShell Management Agent and some configuration with a Database in Domino with two Domino Agents.

What have you synchronised passwords too using FIM/MIM ?

UPDATED: Identifying Active Directory Users with Pwned Passwords using Microsoft/Forefront Identity Manager

Earlier this week I posted this blog post that showed a working example of using a custom Pwned Password FIM/MIM Management Agent to flag a boolean attribute in the MIM Service to indicate whether a users password is in the pwned password dataset or not. If you haven’t read that post this won’t make a lot of sense, so read that then come back.

The solution when receiving a new password for a user (via Microsoft Password Change Notification Service) was checking against the Have I Been Pwned API. The disclaimer at the start of the blog post detailed why this is a bad idea for production credentials. The intent was to show a working example of what could be achieved.

This update post shows a working solution that you can implement internal to a network. Essentially taking the Pwned Password Datasets available here and loading them into a local network SQL Server and then querying that from the FIM/MIM Pwned Password Management Agent rather than calling the external public API.

Creating an SQL Server Database for the Pwned Passwords

On my SQL Server using SQL Server Management Studio I right-clicked on Databases and chose New Database. I gave it the name PwnedPasswords and told it where I wanted my DB and Logs to go to.

Then in a Query window in SQL Server Management Studio I used the following script to created a table (dbo.pwnedPasswords).

use PwnedPasswords;
 CREATE TABLE dbo.pwnedPasswords
( password_id int NOT IDENTITY(1,1) NULL,
 passwords varchar(max) NOT NULL,
 CONSTRAINT passwords_pk PRIMARY KEY (password_id)
);

Again using a query window in SQL Server Management Studio I used the following script to create an index for the passwords.

USE [PwnedPasswords]USE [PwnedPasswords]
GO
SET ANSI_PADDING ON

GO
CREATE UNIQUE NONCLUSTERED INDEX [PasswordIndex] ON [dbo].[pwnedPasswords]( [password_id] ASC)INCLUDE ( [passwords]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

The last thing I did on the DB was to take the MIM Sync Server Active Directory Service Account (that was already in the SQL Server Logins) and give that account Reader Access to my new PwnedPasswords Database. I gave this account access as I’m using Integrated Authentication for login to SQL and as the MA is initiated by the MIM Sync Service Account, that is the account that needs the access.

Getting the Pwned Password Datasets into the new Database

I’m far from a DBA. I’m an identity guy. So using tools I was most familiar with (PowerShell) I created a simple script to open the password dump files as a stream (as Get-Content wasn’t going to handle the file sizes), read in the lines, convert the format and insert the rows into SQL. I performed the inserts in batches of 1000 and I performed it locally on the SQL Server.

In order to get the content from the dump file, add another column and get it in a format quickly to insert into the SQL DB I used the Out-DataTable function available from here.

The script could probably be improved as I only spend about 20-30 minutes on it. It is opening and closing a connection to the SQL DB each time it inserts 1000 rows. That could be moved outside the Insert2DB Function and maybe the batch size increased. Either way it is a starting point and I used it to write millions of rows into the DB successfully.

Updated FIM/MIM Pwned Passwords Management Agent Password.ps1 script

This then is the only other change to the solution. The Password.ps1 script rather than querying the PwnedPasswords API queries the SQL DB and sets the pwned boolean flag accordingly.

Summary

This enhancement shows a working concept that will be more appealing to Security Officers within corporate organisations if you have an appetite to know what your potential exposure is based on your Active Directory Users Passwords.

 

Synchronizing Passwords from Active Directory to the IBM/Lotus Domino Identity Vault using Microsoft Identity Manager – Part 1

Introduction

Recently I wrote about getting started with the latest IBM/Lotus Notes/Domino Management Agent for Microsoft Identity Manager. In a recent engagement we are using that MA to provision and manage identities into Domino. We are also using the MA to synchronise passwords via PCNS and MIM to the Notes users’ Internet (HTTP) password.

What you may or may not be aware of is that IBM introduced a new feature with Domino 8.5 called the ID Vault. The ID Vault is a Domino based application that holds protected copies of Notes user IDs. Now here’s the twist. The Microsoft Domino MA only supports password synchronisation to the HTTP password, not to the ID Vault.

My customer is using the ID Vault and naturally we need to synchronise password changes to both the HTTP Password and the ID Vault (for users Notes IDs). This post is the first in a series that details how I recently accomplished synchronising passwords to the Domino ID Vault.

  1. This post provides the introduction and the creation of a PowerShell Management Agent into Domino to join identities into the MIM Metaverse
  2. Post two details Creating Domino Agents that will handle taking requests from the MIM PS MA to change users ID Vault password
  3. Post three will detail calling the Domino Agents on password sync events (from PCNS via MIM)

Overview

The following diagram shows a high-level overview of password synchronisation using FIM/MIM from AD to Domino. Password changes/resets can be initiated using a number of methods. The FIM/MIM Self Service Password Reset functionality, users changing their password via their domain joined workstations as defined by AD Group Password Polic(y)ies, using the AD FS Password Change function, or even on behalf of users by a Service Desk/Administrator. In each scenario implementing Microsoft’s Password Change Notification Service will get the password change to FIM/MIM. I’m not going to cover PCNS as it is out of the box and straight forward to install and configure. This MS PFE PCNS implementation document covers it quite well.

Password Sync Overview

Likewise I’m not going to go into any detail about password sync to the HTTP Password. That’s out of the box functionality, that is pretty much the same as any other MA configured as a Password Sync Target. That said in my environment I did have to configure the MS Domino MA like this to get password events out to Domino.

ID Vault FIM/MIM PowerShell Management Agent

First up, we are going to need a Management Agent to join Notes users to our users from Active Directory in the Metaverse. I’ve gone to my favourite PowerShell Management Agent (Granfeldt) for this.

The Granfeldt PS MA will be configured to;

  • Import and join Domino Users to the Metaverse. The MA will be slimline in the number of attributes it brings in. Enough to perform the join and have enough information about the users context in Domino to be able to perform the password sync event
  • be a target for Password Synchronisation
  • send the password change event to the Domino Agent we will build to perform the password change. A Domino Agent is required as the ID Vault will only accept password changes from a process run on the Domino Server(s). More on this in parts 2 and 3

The integration of the MIM Sync Engine with Domino with the PowerShell Management Agent is done using LDAP. The Name and Address Book is easily accessed via LDAP.

To get started I looked up the Server Document for the Domino Server I wanted to connect to that had the Name and Address Book. Selecting the Directory tab I could see that LDAP(S) 389/636 was enabled.

LDAP Domino

I then went to the Name and Address Book and looked up my Admin Notes ID to get its context so I could translate it to LDAP format. Darren Kloud Robinson/OrgU/Org-AUS translates to CN=Darren Kloud Robinson,OU=Org,O=Org-AUS“.

NAB Info

Knowing my Notes ID Password I used LDP from Windows Server to bind to the Domino Directory. You could use any LDAP Browser/Tool.

Bind

Once I validated I could connect and browse the tree, I knew I had my connection details sorted, I translated that to PowerShell.

That looked something like this;

I then wrapped this into a FIM/MIM PowerShell Management Agent. The Granfeldt PS MA Scripts are below.

Domino PowerShell Management Agent Schema Script

As described above the Schema Script is very light on the number of attributes specified. Basically the minimum required to get a join and give us the context of the user to process password sync events.

Domino PowerShell Management Agent Import Script

As detailed above the Import brings through enough metadata to perform the join and give us the attributes needed for the user context to be able to sync passwords through.

Domino PowerShell Management Agent Export Script

File just needs to exist. Not used in this scenario.

Domino PowerShell Management Agent Password Script

See Part 3 in this series for the Password.ps1 script. But if you are following sequentially, copy the empty Export.ps1 script for now and name it Password.ps1 and have it located in the same directory as the other PS MA scripts.

Wiring it all together

As for creating the PS MA, I’ve detailed this in-depth many times. Check out this post (or the many other similar I’ve posted) and the Getting Started section if you are new to the Granfeldt PowerShell Management Agent. Copy the above scripts to the directory you create, and when creating the MA provide the paths to the scripts (in 8.3 format).

A key item though is to configure the PS MA as a Password Sync Target as per the screenshot below. You will also need to configure where passwords are coming from to send to this new MA. If it is Active Directory, open the Properties of your AD MA select Configure Directory Partitions then under Password Synchronization enable the checkbox Enable this partition as a password synchronization source. Select Targets and select your newly created Notes ID Vault Password MA. Select Ok then Ok again.

PWD Target

After creating a Run Profile and doing a Stage and Import, based on your Join rule (probably email address) you should have a heap of connectors. In my environment displayName contains the context of the user. Eg. Full Name/OrgU/Org We’ll need this to send the password change event to the ID Vault.

CS

Summary

Through the PowerShell MA as detailed above we have been able to enumerate users from Domino and join them to existing users in the MIM Sync Metaverse. We can now set about creating Domino Agents to take password sync events from this MA and change users passwords in the Domino ID Vault.

Part 2 in this series here details creating the Domino Agents and configuring Domino to accept the changes.

Identifying Active Directory Users with Pwned Passwords using Microsoft/Forefront Identity Manager

Update: An element of this solution details checking passwords online (using the Have I Been Pwned API). Troy explains succinctly in his blog-post announcing the pwned passwords list why this is a bad idea. If you are looking to implement the concept I detail in this post then WE STRONGLY recommend using a local copy of the pwned password list.
THIS POST HERE details using a local SQL Database to hold the Pwned Passwords Datasets and the change to the Management Agent to query the SQL DB instead of the HIBP API.    

Introduction

Last week (3 Aug 2017) Troy Hunt released a sizeable list of Pwned Passwords. 320 Million in fact. I encourage you strongly to have a read about the details here.

Troy also extended his HaveIBeenPwned API to include the ability to query as to whether a password has been pwned and is likely to be used in a brute force attack.

Microsoft provide a premium license feature in Azure Active Directory (Azure Active Directory Identity Protection) whereby leaked credential sets are checked and Admins alerted via reports. But what if you aren’t licensed for the Azure AD Premium Features, or you want something a little more customised and you have Microsoft/Forefront Identity Manager? That is what this post covers.

Overview

The following diagram looks a little more complicated than what it really is. The essence though is that password changes can come from a multitude of different scenarios. Using Microsoft’s Password Change Notification Service (PCNS) we can capture password changes and send them to Microsoft Identity Manager so that we can synchronise the password to other systems, or for this use case we can lookup to see if the users new password is on the pwned password list.

This post will cover creating the Pwned Password FIM/MIM Management Agent and flagging a boolean attribute in the MIM Service to indicate whether a users password is on the pwned password or not.

Pwned Password Overview
Password change and sync architecture

Prerequisites

There are a few components to this solution depicted above. You will need;

  • FIM/MIM Synchronisation Server
    • with an Active Directory Management Agent configured (most likely you will have a Projection Rule on this MA to get your users into the Metaverse)
    • not shown in the diagram above you will also need the MIM MA configured to sync users from the Metaverse to the MIM Service
  • FIM/MIM Service and Portal Server (can be on the same server as above)
  • Microsoft Password Change Notification Service (PCNS). This MS PFE PCNS implementation document covers it quite well and you will need;
    • the PCNS AD Schema Extension installed
    • the PCNS AD Password Filters installed on all your (writeable) Domain Controllers
    • PCNS configured to send password changes to your FIM/MIM Sync Server
  • Granfeldt PowerShell Management Agent (that we will use to check users passwords against the Have I Been Pwned pwned password API)
  • Lithnet Resource Management PowerShell Module
    • download it from here and install it on your FIM/MIM Server as the Pwned Password MA will use this module to populate the Pwned Password Status for users in the MIM Service
  • Windows Management Framework (PowerShell) 5.x

Getting Started with the Granfeldt PowerShell Management Agent

If you don’t already have it go get it from here. Søren’s documentation is pretty good but does assume you have a working knowledge of FIM/MIM and this blog post is no different.

Four items of note for this solution;

  • You must have an Export.ps1 file. Even though we’re not doing exports on this MA, the PS MA configuration requires a file for this field. The .ps1 doesn’t need to have any logic/script inside it. It just needs to be present
  • The credentials you give the MA to run this MA are the credentials for the account that has permissions to the On Premise Active Directory where we will be importing users from to join to our Metaverse so we can pass password changes to this Management Agent
  • The same account as above will also need to have permissions in the MIM Service as we will be using the account to update the new attribute we are going to create
  • The path to the scripts in the PS MA Config must not contain spaces and be in old-skool 8.3 format. I’ve chosen to store my scripts in an appropriately named subdirectory under the MIM Extensions directory. Tip: from a command shell use dir /x to get the 8.3 directory format name. Mine looks like C:\PROGRA~1\MICROS~4\2010\SYNCHR~1\EXTENS~2\PwnedPWD

With the Granfeldt PowerShell Management Agent downloaded from Codeplex and installed on your FIM/MIM Server we can create our Pwned Password Management Agent.

Creating the Pwned PowerShell Management Agent

On your FIM/MIM Sync Server create a new sub-directory under your Extensions Directory. eg. PwnedPWD in C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions then create a sub-directory under PwnedPWD named DebugC:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\PwnedPWD\Debug

Copy the following scripts (schema.ps1, import.ps1, export.ps1, password.ps1) and put them into the C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\PwnedPWD directory

Schema.ps1

The following schema.ps1 script sets up the object class (user) and a handful of attributes from Active Diretory that will be useful for logic that we may implement in the future based on users password status.

Import.ps1

The import.ps1 script connects to Active Directory to import our AD users into the Pwned Password Management Agent so we can join to the Metaverse object already present for users on the Active Directory Management Agent. The user needs to be joined to the Metaverse on our new MA so they are addressable as a target for PCNS.

Export.ps1

As detailed earlier, we aren’t using an Export script in this solution.

Password.ps1

The Password script receives password changes as they occur from Active Directory and looks up the Have I Been Pwned API to see if the new password is present on the list or not and sets a boolean attribute for the pwned password status in the MIM Service.

On your FIM/MIM Sync Server from the Synchronisation Manager select Create Management Agent from the right hand side pane.  Select PowerShell from the list of Management Agents. Select Next.

create pwned passwords management agent
create pwned passwords management agent

Give your MA a Name and a Description. Select Next. 

create pwned passwords management agent
create pwned passwords management agent

Provide the 8.1 style path to your Schema.ps1 script copied from the steps earlier. Provide an AD sAMAccountName and Password that also has permissions to the MIM Service as detailed in the Prerequisites. Select Next.

connectivity pwned passwords management agent
connectivity pwned passwords management agent

Provide the paths to the Import.ps1, Export.ps1 and Password.ps1 scripts copied in earlier. Select Next.

global parameters pwned passwords management agent
global parameters pwned passwords management agent

Select Next.

partitions and hierarchies pwned passwords management agent
partitions and hierarchies pwned passwords management agent

Select the user checkbox. Select Next.

object types pwned passwords management agent
object types pwned passwords management agent

Select all the attributes in the list. Select Next.

attributes pwned passwords management agent
attributes pwned passwords management agent

Select Next.

anchors pwned passwords management agent
anchors pwned passwords management agent

Select Next.

connector filter pwned passwords management agent
connector filter pwned passwords management agent

Create a Join Rule for your environment. eg.  sAMAccountName => person:Accountname  Select Next.

join and projection rule pwned passwords management agent
join and projection rule pwned passwords management agent

Create an import flow rule for user:pwdLastSet => person:pwdLastSet. Select Next.

attribute flow pwned passwords management agent
attribute flow pwned passwords management agent

Select Next.

deprovisioning pwned passwords management agent
deprovisioning pwned passwords management agent

Ensure that Enabled password management is selected, then select Finish.

Enabled password management pwned passwords management agent
Enabled password management pwned passwords management agent

With the Pwned Password MA created and configured we need to create at least a Stage (Full Import) and Full Sync Run Profiles and execute them to bring in the users from AD and join them to the Metaverse.

This should be something you’re already familiar with.

Run Profiles Pwned Passwords Management Agent
Run Profile pwned passwords MA

When running the Synchronisation we get the joins we expect. In my environment PwdLastSet was configured to sync to the MIM Service and hence the Outbound Sync to on the MIM Service MA.

Sync and join pwned passwords MA
MIM Synchronisation pwned passwords MA

MIM Service Configuration

In the MIM Service we will create a custom boolean attribute that will hold the pwned status of the users password.

Schema

Connect to your MIM Portal Server with Administrator privileges and select Schema Management from the right hand side menu.

Select All Attributes then select New

Provide an attribute name (System name) and a Display Name with a Data Type of Boolean. Provide a Description and select Finish

Select Submit

Search for User in Resource Types then select the User checkbox from the search results and select Binding then select New.

In the Resource Type box type User then click the validate field button (the one with the green tick). In the Attribute Type box type Pwned Password then click the validate field button (the one with the green tick). Select Finish

Select  Submit

Configure the Active Directory MA to send passwords to the Pwned Passwords MA

On your existing Active Directory Management Agent select Properties. Select Configure Directory Partitions then under Password Synchronization enable the checkbox Enable this partition as a password synchronization source. Select Targets and select your newly created Pwned Password MA. Select Ok then Ok again.

Pwned Password MA
Pwned Password MA

Testing the End to End Pwned Password Check

Now you should have configured;

  • PCNS including installation of the Active Directory filters
  • The existing Active Directory Management Agent as a Password Source
  • The existing Active Directory Management Agent to send password change events to the Pwned Password MA

Select a user in Active Directory Users and Computers, right click the user and select Reset Password.

Active Directory Users and Computers, right click the user and select Reset Password
Active Directory Users and Computers, right click the user and select Reset Password

I first provided a password I know is on the pwned list, Password1

 password I know is on the pwned list
password I know is on the pwned list
Password Changed
Password Changed

With PCNS Logging enabled on the MIM Sync Server I can see the password event come through.

PCNS Logging enabled on the MIM Sync Server
PCNS Logging enabled on the MIM Sync Server

Checking in the Pwned Password MA debug log we can see in the debug logging for the user we changed the password for and that when it was checked against Have I Been Pwned the password is flagged as pwned.

Note: If you implement the solution in a production environment obviously remove the password from being logged. 

Pwned Password MA debug log
Pwned Password MA debug log

In the MIM Portal search for and locate the user the password we just changed the password for.

MIM Portal search for and locate the user
MIM Portal search for and locate the user

Select the user. Scroll to the bottom and select Advanced View. Select the Extended Attributes tab. Scroll down and we can see the Pwned Password shows as checked.

Pwned Password shows as checked
Pwned Password shows as checked

Now repeating the process with a password that isn’t in the Pwned Password list. After changing the password in Active Directory Users and Computers the password went through its sync path. The log shows the password isn’t in the list.

log shows the password isn't in the pwned password list
log shows the password isn’t in the pwned password list

And the MIM Portal shows the Boolean value for Pwned Password is now not selected.

Pwned Password is now not selected
Pwned Password is now not selected

Summary

Using PCNS and FIM/MIM we can check whether our Active Directory users are using passwords that aren’t in the Pwned Password list.

What we can then do if their password is in the Pwned Password list is a number of things based on what the security policy is and even what type of user it is. You’ll notice that I’ve included additional attributes in the MA that we can flow through the Metaverse and into the MIM Service that may help with some of those decisions (such as adminCount which indicates if the user is an Administrator).

Potentially for Admin users we could create a workflow in the MIM Service that forces their account to change password on next logon. For other users we could create a workflow that sends them a notification letting them know that they should change their password.

Either way, we now have visibility of the state of users passwords. Big thanks to Troy for adding Pwned Passwords to his Have I Been Pwned API.

 

Reiterating: An element of this solution details checking passwords online (using the Have I Been Pwned API). Troy explains succinctly in his blog-post announcing the pwned passwords list why this is a bad idea. If you are looking to implement the concept I detail in this post then WE STRONGLY recommend using a local copy of the pwned password list.  

Synchronizing Exchange Online/Office 365 User Profile Photos with FIM/MIM

Introduction

This is Part Two in the two-part blog post on managing users profile photos with Microsoft FIM/MIM. Part one here detailed managing users Azure AD/Active Directory profile photo. This post delves deeper into photos, specifically around Office 365 and the reason why you may want to manage these via FIM/MIM.

Background

User profile photos should be simple to manage. But in a rapidly moving hybrid cloud world it can be a lot more complex than it needs to be. The best summary I’ve found of this evolving moving target is from Paul Ryan here.

Using Paul’s sound advice we too are advising our customers to let users manage their profile photo (within corporate guidelines) via Exchange Online. However as described in this article photos managed in OnPremise Active Directory are synchronized to Azure AD and on to other Office365 services only once. And of course we want them to be consistent across AD DS, Azure AD, Exchange Online and all other Office365 Services.

This post details synchronizing user profile photos from Exchange Online to MIM for further synchronization to other systems. The approach uses a combination of Azure GraphAPI and Exchange Remote PowerShell to manage Exchange Online User Profile Photos.

Current State

  • Users historically had a photo in Active Directory. DirSync/ADSync/AzureADConnect then synchronized that to Azure AD (and once only into Office 365).
  • Users update their photo in Office365 (via Exchange Online and Outlook Web Access)
    • the photo is synchronized across Office365 Services

Desired State

  • An extension of the Current State is the requirement to be able to take the image uploaded by users in Exchange Online, and synchronize it back to the OnPremise AD, and any other relevant services that leverage a profile photo
  • Have AzureADConnect keep AzureAD consistent with the new photo obtained from Office365 that is synchronized to the OnPrem Active Directory
  • Sync the current photo to the MIM Portal

Synchronizing Office365 Profile Photos

Whilst Part-one dealt with the AzureAD side of profile photos as an extension to an existing AzureAD PowerShell Management Agent for FIM/MIM, I’ve separated out the Office365 side to streamline it and make it as efficient as possible. More on that later. As such I’ve created a new PowerShell Management Agent specifically for Office365 User Profile Photos.

I’m storing the Exchange Online photo in the MIM Metaverse as a binary object just as I did for the AzureAD photo (but in a different attribute ). I’m also storing a checksum of the photos (as I did for the AzureAD Photo, but also in a different attribute) to make it easier for comparing what is in Azure AD and Exchange Online, to then be used to determine if changes have been made (eg. user updated their profile photo).

Photo Checksum

For generating the hash of the profile photos I’m using Get-Hash from the Powershell Community Extensions.  Whilst PowerShell has Get-FileHash I don’t want to write the profile photos out to disk and read them back in just to get the checksum. That slows the process up by 25%. You can get the checksum using a number of different methods and algorithms. Just be consistent and use the same method across both profile photos and you’ll be comparing apples with apples and the comparison logic will work.

Some notes on Photos and Exchange Online (and MFA)

This is where things went off on a number of tangents. Initially I tried accessing the photos using Exchange Online Remote PowerShell.

CAVEAT 1: If your Office365 Tenant is enabled for Multi-Factor Authentication (which it should be) you will need to get the Exchange Online Remote PowerShell Module as detailed here. Chances are you won’t have full Office365 Admin access though, so as long as the account you will be using is in the Recipient Management Role you should be able to go to the Exchange Control Panel using a URL like https://outlook.office365.com/ecp/?realm=<tenantname>&wa=wsignin1.0 where tenantname is something like customer.com.au From the Hybrid menu on in the right handside pane you will then be able to download the Microsoft.Online.CSE.PSModule.Client.application I had to use Internet Explorer to download the file and get it installed successfully. Once installed I used a few lines from this script here to load the Function and start my RPS session from within PowerShell ISE during solution development.

CAVEAT 2: The EXO RPS MFA PS Function doesn’t allow you to pass it your account password. You can pass it the identity you want to use, but not the password. That makes scheduled process automation with it impossible.

CAVEAT 3: The RPS session exposes the Get-UserPhoto cmdlet which is great. But the RPS session leverages the GraphAPI. The RPS PS Module doesn’t refresh it’s tokens, so if the import takes longer than 60 minutes then using this method you’re a bit stuffed.

CAVEAT 4: Using the Get-UserPhoto cmdlet detailed above, the syncing of photos is slow. As in I was only getting ~4 profile photos per minute slow. This also goes back to the token refresh issue as for pretty much any environment of the size I deal with, this is too slow and will timeout.

CAVEAT 5: You can whitelist the IP Address (or subnet) of your host so MFA is not required using Contextual IP Addressing Whitelisting. At that point there isn’t really a need to use the MFA Enabled PREVIEW EXO RPS function anyway. That said I still needed to whitelist my MIM Sync Server(s) from MFA to allow integration into the Graph API. I configured just the single host. The whitelist takes CIDR format so that looks like /32 (eg. 11.2.33.4/32)

Performance Considerations

As I mentioned above,

  • using the Get-UserPhoto cmdlet was slow. ~4 per minute slow
  • using the GraphAPI into Exchange Online and looking at each user and determining if they had a photo then downloading it, was also slow. Slow because at this customer only ~50% of their users have a photo on their mailbox. As such I was only able to retrieve ~145 photos in 25 minutes. *Note: all timings listed above were during development and actually outputting the images to disk to verify functionality. 

Implemented Solution

After all my trial and error on this, here is my final approach and working solution;

  1. Use the Exchange Online Remote PowerShell (non-MFA version) to query and return a collection of all mailboxes with an image *Note, add an exception for your MIM Sync host to the white-listed hosts for MFA (if your Office365 Tenant is enabled for MFA) so the process can be automated
  2. Use the Graph API to obtain those photos
    • with this I was able to retrieve ~1100 profile photos in ~17* minutes (after ~2 minutes to query and get the list of mailboxes with a profile photo)

Pre-requisites

There’s a lot of info above, so let me summarize the pre-requisties;

  • The Granfeldt PowerShell MA
  • Whitelist your FIM/MIM Sync Server from MFA (if your Office 365 environment is enabled for MFA)
  • Add the account you will run the MA as, that will in turn connect to EXO via RPS to the Recipient Management Role
  • Create a WebApp for the PS MA to use to access users Profile Photos via the Graph API (fastest method)
  • Powershell Community Extensions to generate the image checksum

Creating the WebApp to access Office365 User Profile Photos

Go to your Azure Portal and select the Azure Active Directory Blade from the Resource Menu bar on the left. Then select App Registrations and from the Manage Section of the Azure Active Directory menu, and finally from the top of the main pane select “New Application Registration“.

Give it a name and select Web app/API as the type of app. Make the sign-in URL https://localhost and then select Create.

Record the ApplicationID that you see in the Registered App Essentials window. You’ll need this soon.

Now select All Settings => Required Permissions. Select Read all users basic profiles in addition to Sign in and read user profile. Select Save.

Under Required Permissions select Add and then select 1 Select an API, and select Office 365 Exchange Online then click Select.

Choose 2 Select Permissions and then select Read user profiles and Read all users’ basic profiles. Click Select.

Select Grant Permissions

From Settings select Keys, give your key a Description, choose a key lifetime and select Save. RECORD the key value. You’ll need this along with the WebApp ApplicationID/ClientID for the Import.ps1 script.

Using the information from your newly registered WebApp, we need to perform the first authentication (and authorization of the WebApp) to the Graph API. Taking your ApplicationID, Key (Client Secret) and the account you will use on on the Management Agent (and that you have assigned the Recipient Management Role in Exchange Online) and run the script detailed in this post here. It will authenticate you to your new WebApp via the GraphAPI after asking you to provide the account you will use on the MA and Authorizing the permissions you selected when registering the app. It will also create a refresh.token file which we will give to the MA to automate our connection. The Authorization dialog looks like this.

Creating the Management Agent

Now we can create our Management Agent using the Granfeldt PowerShell Management Agent. If you haven’t created one before checkout a post like this one, that further down the post shows the creation of a Granfeldt PSMA. Don’t forget to provide blank export.ps1 and password.ps1 files on the directory where you place the PSMA scripts.

PowerShell Management Agent Schema.ps1

PowerShell Management Agent Import.ps1

As detailed above the PSMA will leverage the WebApp to read users Exchange Profile Photos via the Graph API. The Import script also leverages Remote Powershell into Exchange Online (for reasons also detailed above). The account you run the Management Agent as will need to be added to the Recipient Management Role Group in order to use Remote PowerShell into Exchange Online and get the information required.

Take the Import.ps1 script below and update;

  • Update lines 11, 24 and 42 for the path to where you have put your PSMA. Mine is under the Extensions directory in a directory named EXOPhotos.
  • copy the refresh.token generated when authenticating and authorizing the WebApp earlier into the directory you specified in line 42 above.
  • Create a Debug directory under the directory you specified in lines 11,24 and 42 above so you can see what the MA is doing as you implement and debug it the first few times.
  • I’ve written the Import to use Paged Imports, so make sure you tick the Paged Imports checkbox on the configuration of the MA
  •  Update Lines 79 and 80 with your ApplicationID and Client Secret that you recorded when creating your WebApp

 

Running the Exchange User Profile Photos MA

Now that you have created the MA, you should have select the EXOUser ObjectClass and the attributes defined in the schema. You should also create the EXOPhoto (as Binary) and EXOPhotoChecksum (as String) attributes in the Metaverse on the person ObjectType (assuming you are using the built-in person ObjectType).

Configure your flow rules to flow the EXOPhoto and EXOPhotoChecksum on the MA to their respective attributes in the MV.

Create a Stage Only run profile and run it. If you have done everything correctly you will see photos come into the Connector Space.

Looking at the Connector Space, I can see EXOPhoto and EXOPhotoChecksum have been imported.

After performing a Synchronization to get the data from the Connector Space into the Metaverse it is time to test the image that lands in the Metaverse. That is quick and easy via PowerShell and the Lithnet MIIS Automation PowerShell Module.

$me = Get-MVObject -ObjectType person -Attribute accountName -Value "drobinson"
$me.Attributes.EXOPhoto.Values.ValueBinary
[System.Io.File]::WriteAllBytes("c:\temp\myOutlookphoto.jpg" ,$me.Attributes.EXOPhoto.Values.ValueBinary )

The file is output to the directory with the filename specified.

Opening the file reveals correctly my Profile Photo.

Summary

In Part one we got the AzureAD/Active Directory photo. In this post we got the Office365 photo.

Now that we have the images from Office365 we need to synchronize any update to photos to Active Directory (and in-turn via AADConnect to Azure AD). Keep in mind the image size limits for Active Directory and that we retrieved the largest photo available from Office365 when synchronizing the photo on. There are a number of PowerShell modules for photo manipulation that will allow you to resize accordingly.