Generate an XML file from Active Directory
Note: This script is an example of how to generate XML from different sources for use with Interact's profile sources. Customisation of this script is not supported by Interact. Use it as a starting point and tweak it as necessary.
Before you start
- You need a General Profile Source configured in Interact. See General Profile Sources.
- You need PowerShell (version 3) and the ActiveDirectory module, plus an Active Directory user with read access.
Overview
With PowerShell, you can read your Active Directory, generate an XML file, and send the file to Interact to synchronise via General Profile Sources.
The script sets some variables, defines a set of helper functions that write each part of the XML, then runs a short execution sequence that builds the file and posts it to Interact. The walkthrough below explains the parts you are most likely to change.
The script
Import-Module ActiveDirectory
# Set up connection details
$server = "URI OF ACTIVE DIRECTORY DOMAIN CONTROLLER"
$userName = "USERNAME OF USER WITH READ ACCESS TO ACTIVE DIRECTORY"
$password = "PASSWORD OF USER ABOVE"
$userDn = "DN FOR THE OU OF THE USERS TO BE IMPORTED FROM ACTIVE DIRECTORY"
$groupDn = "DN FOR THE OU OF GROUPS TO BE CREATED IN INTERACT"
$uri = "URL TO PROFILE SOURCE IN INTERACT"
$defaultUserPassword = "DEFAULT PASSWORD FOR NEW USERS"
$authtoken = "AUTH TOKEN CONFIGURED IN INTERACT"
$domain = "NAME OF PROFILE SOURCE IN INTERACT"
$ldapId = "ID OF PROFILE SOURCE IN INTERACT"
$xmlPath = "PATH TO XML FILE TO CREATE"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Write-SyncOption([System.Xml.XmlTextWriter] $writer, [string] $optionName, [string] $optionValue){
# Write a syncoption element using the passed values
$writer.WriteStartElement('option')
$writer.WriteAttributeString('name', $optionName)
$writer.WriteString($optionValue)
$writer.WriteEndElement()
}
function Write-DocumentBase([System.Xml.XmlTextWriter] $writer){
# Write the document root and syncoptions elements
$writer.WriteStartElement('syncdata')
$writer.WriteAttributeString('version', '1')
$writer.WriteStartElement('syncoptions')
$writer.WriteAttributeString('domain', $domain)
$writer.WriteAttributeString('ldapid', $ldapId)
Write-SyncOption $writer 'syncCompanies' 'true'
Write-SyncOption $writer 'syncLocations' 'true'
Write-SyncOption $writer 'syncDepartments' 'true'
Write-SyncOption $writer 'syncManagers' 'true'
Write-SyncOption $writer 'actionDisabledUsers' 'x'
Write-SyncOption $writer 'actionMissingDeletedUsers' 'x'
Write-SyncOption $writer 'loginType' '1'
Write-SyncOption $writer 'defaultCulture' '1'
Write-SyncOption $writer 'newUserPasswordBehaviour' 'strict'
$writer.WriteEndElement()
}
function Write-PersonElement([System.Xml.XmlTextWriter] $writer, [System.Object] $user){
# Write a person child element for a user
$writer.WriteStartElement('person')
$writer.WriteElementString('firstname', $user.GivenName)
$writer.WriteElementString('surname', $user.Surname)
$writer.WriteElementString('title', $user.personalTitle)
$writer.WriteElementString('initials', $user.Initials)
$writer.WriteElementString('jobtitle', $user.Title)
$writer.WriteElementString('phone', $user.telephoneNumber)
$writer.WriteElementString('mobile', $user.mobile)
$writer.WriteElementString('fax', $user.facsimileTelephoneNumber)
$writer.WriteElementString('extension', $user.ipPhone)
$writer.WriteElementString('address', $user.homePostalAddress)
$writer.WriteEndElement()
}
function Write-AdditionalField([System.Xml.XmlTextWriter] $writer, [string] $fieldName, [string] $fieldValue){
# Write an additional field element using the passed values
$writer.WriteStartElement('field')
$writer.WriteAttributeString('name', $fieldName)
$writer.WriteString($fieldValue)
$writer.WriteEndElement()
}
function Write-ManagerElement([System.Xml.XmlTextWriter] $writer, [System.Object] $user, [string] $searchBase, [string] $serverName){
# Write the manager element for a user
# If the AD Manager property has a value, query AD for the manager's GUID and DN
$writer.WriteStartElement('manager')
if (![string]::IsNullOrWhiteSpace($user.Manager)) {
$manager = Get-ADUser -Filter {DistinguishedName -eq $user.Manager} -SearchBase $searchBase -SearchScope Subtree -Server $serverName -Credential $credential -Properties DistinguishedName, objectGUID
$writer.WriteAttributeString('uid', $manager.ObjectGUID.ToString())
$writer.WriteAttributeString('dn', $manager.DistinguishedName)
} else {
$writer.WriteAttributeString('uid', '')
$writer.WriteAttributeString('dn', '')
}
$writer.WriteEndElement()
}
function Write-PrimaryOrganisationElement([System.Xml.XmlTextWriter] $writer, [string] $organisationType, [string] $organisationValue){
# Write an organisation element using the passed values
$writer.WriteStartElement('organisation')
$writer.WriteAttributeString('type', $organisationType)
$writer.WriteAttributeString('primary', 'true')
$writer.WriteString($organisationValue)
$writer.WriteEndElement()
}
function Write-OrganisationsElement([System.Xml.XmlTextWriter] $writer, [System.Object] $user){
# Write the organisations element for a user, including only fields that have a value
$writer.WriteStartElement('organisations')
if (![string]::IsNullOrWhiteSpace($user.department)) {
Write-PrimaryOrganisationElement $writer 'department' $user.department
}
if (![string]::IsNullOrWhiteSpace($user.company)) {
Write-PrimaryOrganisationElement $writer 'company' $user.company
}
if (![string]::IsNullOrWhiteSpace($user.physicalDeliveryOfficeName)) {
Write-PrimaryOrganisationElement $writer 'location' $user.physicalDeliveryOfficeName
}
$writer.WriteEndElement()
}
function Write-UsersElement([System.Xml.XmlTextWriter] $writer, [string] $searchBase, [string] $serverName){
# Write the users element: query AD for users and create an element for each
$writer.WriteStartElement('users')
$users = @(Get-ADUser -Filter * -SearchBase $searchBase -SearchScope Subtree -Server $serverName -Credential $credential -Properties *)
$writer.WriteAttributeString('TotalUsers', $users.Count)
foreach ($user in $users)
{
$writer.WriteStartElement('user')
$writer.WriteAttributeString('uid', $user.ObjectGUID.ToString())
$writer.WriteAttributeString('dn', $user.DistinguishedName)
$writer.WriteAttributeString('username', $user.SamAccountName)
$writer.WriteAttributeString('email', $user.mail)
Write-PersonElement $writer $user
$writer.WriteElementString('statusenabled', $user.Enabled.ToString().ToLower())
$writer.WriteElementString('password', $defaultUserPassword)
$writer.WriteElementString('culture', '1')
$writer.WriteStartElement('language')
$writer.WriteAttributeString('id', '1')
$writer.WriteEndElement()
# Additional fields: uncomment and adapt these to map your own additional fields
$writer.WriteStartElement('additionalfields')
#Write-AdditionalField $writer 'first_aider' 'true'
#Write-AdditionalField $writer 'car_registration' 'AB 66 XYZ'
$writer.WriteEndElement()
Write-ManagerElement $writer $user $searchBase $serverName
Write-OrganisationsElement $writer $user
$writer.WriteEndElement()
}
$writer.WriteEndElement()
}
function Write-GroupsElement([System.Xml.XmlTextWriter] $writer, [string] $searchBase, [string] $serverName){
# Write the groups element: query AD for groups and their members
$writer.WriteStartElement('groups')
$groups = @(Get-ADGroup -Filter * -SearchBase $searchBase -SearchScope Subtree -Server $serverName -Credential $credential)
# Pre-fetch members so the required count attributes can be written before the group elements
$groupMembers = @{}
$totalMembers = 0
foreach ($group in $groups) {
$members = @(Get-ADGroupMember -Identity $group.ObjectGUID -Server $serverName -Credential $credential)
$groupMembers[$group.ObjectGUID] = $members
$totalMembers += $members.Count
}
$writer.WriteAttributeString('TotalUsers', $totalMembers)
$writer.WriteAttributeString('TotalGroups', $groups.Count)
foreach ($group in $groups)
{
$writer.WriteStartElement('group')
$writer.WriteAttributeString('uid', $group.ObjectGUID.ToString())
$writer.WriteAttributeString('dn', $group.DistinguishedName)
$writer.WriteAttributeString('name', $group.Name)
$members = $groupMembers[$group.ObjectGUID]
$writer.WriteAttributeString('UserCount', $members.Count)
$writer.WriteStartElement('users')
foreach ($member in $members)
{
$writer.WriteStartElement('user')
$writer.WriteAttributeString('uid', $member.ObjectGUID.ToString())
$writer.WriteAttributeString('dn', $member.DistinguishedName)
$writer.WriteAttributeString('username', $member.SamAccountName)
$writer.WriteAttributeString('email', $member.mail)
$writer.WriteEndElement()
}
$writer.WriteEndElement()
$writer.WriteEndElement()
}
$writer.WriteEndElement()
}
###########################################
#
# The main execution sequence of the script
#
# Set up the XML document
$xmlWriter = New-Object System.Xml.XmlTextWriter($xmlPath, $null)
$xmlWriter.Formatting = 'Indented'
$xmlWriter.Indentation = 1
$xmlWriter.IndentChar = "`t"
$xmlWriter.WriteStartDocument()
# Build the syncoptions
Write-DocumentBase $xmlWriter
# Set up the Active Directory credentials
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName, $secstr
Write-Host "Processing users..."
Write-UsersElement $xmlWriter $userDn $server
Write-Host "Processing groups..."
Write-GroupsElement $xmlWriter $groupDn $server
# Close the document and flush to disk
$xmlWriter.WriteEndElement()
$xmlWriter.Flush()
$xmlWriter.Close()
Write-Host "Complete"
# Deliver the file to the API endpoint
Invoke-RestMethod -Uri $uri -Method Post -InFile $xmlPath -ContentType "multipart/form-data" -Headers @{'X-ApiKey'=$authtoken}
Step 1: Set up the variables
At the top of the script, set the variables that relate to your Interact instance, Active Directory and profile source:
# Set up connection details
$server = "URI OF ACTIVE DIRECTORY DOMAIN CONTROLLER"
$userName = "USERNAME OF USER WITH READ ACCESS TO ACTIVE DIRECTORY"
$password = "PASSWORD OF USER ABOVE"
$userDn = "DN FOR THE OU OF THE USERS TO BE IMPORTED FROM ACTIVE DIRECTORY"
$groupDn = "DN FOR THE OU OF GROUPS TO BE CREATED IN INTERACT"
$uri = "URL TO PROFILE SOURCE IN INTERACT"
$defaultUserPassword = "DEFAULT PASSWORD FOR NEW USERS"
$authtoken = "AUTH TOKEN CONFIGURED IN INTERACT"
$domain = "NAME OF PROFILE SOURCE IN INTERACT"
$ldapId = "ID OF PROFILE SOURCE IN INTERACT"
$xmlPath = "PATH TO XML FILE TO CREATE"
The Write- functions that follow generate the XML for each user, each group and its members, and the synchronisation options. You should not need to change them unless you want to map different Active Directory fields.
Step 2: Understand the synchronisation options
The synchronisation options are set in the Write-DocumentBase function. When you use profile sources, Interact takes these options from the XML source itself rather than from the screens within Interact. Each option is described below.
- syncCompanies — whether to synchronise user companies. If a company does not exist in Interact, it is created automatically and the user is assigned to it.
- syncLocations — whether to synchronise user locations. If a location does not exist in Interact, it is created automatically and the user is assigned to it.
- syncDepartments — whether to synchronise user departments. If a department does not exist in Interact, it is created automatically and the user is assigned to it.
- syncManagers — whether to synchronise the manager relationship between users.
- actionDisabledUsers — what to do with users marked as disabled in the XML:
x— do nothing; do not update the Interact status of those users.d— deactivate those users in Interact.a— deactivate and archive users in Interact.
- actionMissingDeletedUsers — what to do with users previously created in Interact with this profile source, or not assigned to any groups in the XML. The options are the same as above.
- loginType — must be present and set to
1. - defaultCulture — must be present and set to
1. - newUserPasswordBehaviour — how Interact creates passwords for new users:
strict— use the password specified in the user part of the XML.random— create a random password for each user.
Step 3: Understand the main execution sequence
The main part of the script (under The main execution sequence of the script) ties everything together. It sets up the XML writer, writes the synchronisation options with Write-DocumentBase, builds the Active Directory credentials from the variables you set in Step 1, then calls Write-UsersElement and Write-GroupsElement to write the users and groups. Finally it closes and flushes the file, and posts it to your profile source endpoint with Invoke-RestMethod.
Troubleshooting
PowerShell reports errors from the script itself, and Interact returns useful error messages if the XML is invalid, so troubleshooting is straightforward. See Troubleshooting for more information.
Related
- General Profile Sources
- Active Directory connector (on-premise)
- General Profile Sources schema
- Troubleshooting