SharePoint 2013 introduces a new Active Directory Import mode for user profiles, but can the creation of the required synchronization connections be easily scripted? Heck no. Out of the box (OOTB), the PowerShell support for creating user profile AD “direct mode” import connections isn’t what it needs to be. In this post, I offer a scripting alternative that allows you to specify display names and LDAP filters.
I’ve been operating under the Bitstream Foundry banner for a few months now, and I have to admit that it’s been absolutely wonderful to return to hands-on work with SharePoint. It really didn’t take long for me to realize just how much I missed solving problems and making SharePoint do what customers/clients wanted it to do. Some of you may think I’m crazy for saying this, but it’s good to be back in the trenches.
Brief note: I lump the UPS, UPA, and related pieces under the “UPS” acronym for this post. It’s not technically accurate, but it keeps things simpler for readability.
The UPS was buggy, problematic, and easily broke if you looked at it the wrong way. I know that the bugs were ultimately ironed-out and that the UPS was “fixed” to a large extent, but I stopped playing with it before it got to that point. I never really needed it for anything I wanted to do, and so I did my best ostrich impersonation anytime anything related to the User Profile Service and its underlying plumbing came up.
It’s somewhat ironic, then, that I was only back into hands-on work for about a month before I found myself thrown into the UPS with SharePoint 2013. A client wanted me to put together a script that they could use to perform the post-setup configuration for the UPS in a farm once the UPS instance had been created (through AutoSPInstaller – which also happens to be my “go to” tool for SharePoint setup). The client wanted the script to configure built-in profile properties, create custom properties, create user sub-types, and most importantly: configure synchronization connections. End-to-end, the script needed to do everything that an admin might normally have to do by hand in order to get the UPS ready for production usage.
The Twist
UPS configuration via PowerShell isn’t new territory. There’s a pretty substantial base of online material to draw from for guidance and inspiration (starting with TechNet), but my task was novel in one very important regard: the synchronization connections I was setting up were using SharePoint 2013’s new Active Directory Import mode.
If you’re not familiar with Active Directory Import mode (“AD Direct Mode”), pause your reading here and head over to Spence Harbar’s blog for a thorough and extremely well-written overview. It is a top-notch post on Active Directory Import Mode and provides a foundation for the rest of this post.
If you’ve scripted-out the creation of UPS synchronization connections through PowerShell under SharePoint 2010, then you’re undoubtedly familiar with the Add-SPProfileSyncConnection cmdlet and its syntax (taken straight from TechNet):
As I started putting together my script and looking for usage examples covering the Add-SPProfileSyncConnection cmdlet, I naturally came to another of Spence Harbar’s posts. Spence’s post was the first sign of trouble for me. Even though Spence’s post was written in the SharePoint 2010 time frame, he noted a number of limitations with the Add-SPProfileSyncConnection cmdlet versus creating sync connections within Central Administration. For example, there was no way to specify a DisplayName parameter with Add-SPProfileSyncConnection; you were stuck with a name that was automatically assigned based on the connection’s associated domain name.
The Wall
The DisplayName limitation was annoying, but it wasn’t a show-stopper for me. With a little more investigation, though, the real roadblocks quickly made themselves known.
If you were to compare the fields and options that are available when adding a sync connection through SharePoint Central Administration (shown on the right) with those that are available when adding a connection with the Add-SPProfileSyncConnection, you’d see some significant gaps. The ones that stood between me and a configuration script that did what I needed it to do were the following:
The inability to use the “Filter out disabled users” switch
The inability to specify an LDAP filter to restrict imported users
The ability to filter disabled users was really a convenience; I could just as easily extend my LDAP filter to include the following:
(!userAccountControl:1.2.840.113556.1.4.803:=2)
… if I wanted to filter disabled users without using the checkbox that was available in Central Admin. Without the ability to specify an LDAP filter in the Add-SPProfileSyncConnection call, though, I was stuck. I needed some way to specify an LDAP filter to restrict imported users for the connections I was going to be creating.
I spent a lot of time hunting through forums and looking for information online, but the hunt proved fruitless. No matter how I approached the problem and tinkered with PowerShell, I came up empty. Falling back to the SharePoint Server Object Model, the ConnectionManager class (in the Microsoft.Office.Server.UserProfiles namespace), and its related types got me no further, either. Absolutely nothing in the APIs seemed to expose a way to add Active Directory Import sync connections in a way that exposed the properties I needed to specify.
I had a hard time believing it, but it seemed that the only way to create an Active Directory Import-based sync connection that allowed me to specify a DisplayName and LDAP filter was to use SharePoint Central Administration.
Huh?
The Source of the Problem
Since Central Administration supplied the mechanism to create sync connections with the desired parameters, I knew that SharePoint had the plumbing to support what I wanted to do somewhere in its assemblies. I just needed to figure out where that functionality was. So, I did what I typically do in these sorts of situations: I pulled out Reflector and went diving into SharePoint’s internals.
I took a look at both the EditDSServer.aspx page in the _layouts/15 folder (which is the Central Admin page for creating sync connections) and the Add-SPProfileSyncConnection cmdlet, did some backtracking, and eventually found myself back at the ConnectionManager class – no big surprise there. The somewhat surprising part, though, was what I found (shown to the left).
It appeared that the ConnectionManager class sported several methods to support the addition of sync connections: AddActiveDirectoryConnection, AddActiveDirectoryImportConnection, AddBusinessDataCatalogConnection, and AddLdapConnection. The one that I was interested in was the second one – AddActiveDirectoryImportConnection:
The big problem was that method was marked internal – meaning it wasn’t publicly exposed to outside callers.
Tracing things through, I found that the AddActiveDirectoryImportConnection method was ultimately accessible through two different public entry points: the Central Admin EditDSServer.aspx page and the Add-SPProfileSyncConnection PowerShell cmdlet. The EditDSServer.aspx page call was pretty direct, but the PowerShell cmdlet call ended up channeling through the UpdateCreateSyncConnection method on the Microsoft.Office.Server.Administration.UserProfileApplication class before making its way to the AddActiveDirectoryImportConnection method. And the UpdateCreateSyncConnection method was the source of the problem.
UpdateCreateSyncConnection is a method that coordinates the creation and modification of different sync connection types – not just the Active Directory Import connection type I was focused on. Most of the parameters that are needed to create a sync connection are available on the method signature, but not all of them.
Looking through the method to where it actually calls into the AddActiveDirectoryImportConnection internal method reveals how it handles passing-in an LDAP filter … or rather, how it doesn’t handle it:
See the string.Empty parameter in the method call? It’s in the same position where the AddActiveDirectoryImportConnection method expects an LDAP filter. The UpdateCreateSyncConnection method basically “drops the ball” and does absolutely nothing with an LDAP filter. If code had attitude, I envisioned this as the UpdateCreateSyncConnection method throwing up its virtual arms and saying “whatever.”
That crashing noise? My efforts thus far slamming into a wall.
Working Around the Limitation
Since I’d officially proven (to myself, anyway) that the Add-SPProfileSyncConnection cmdlet wasn’t going to work for my purposes, and I wasn’t going to be able to script out the desired action through Central Administration, I was looking at going against the AddActiveDirectoryImportConnection method myself directly from within my PowerShell script.
Insert a giant “ewwwwwwwww” sound here. Why? Because the method was marked internal. Since the method wasn’t publicly accessible, I’d need to use Reflection to call it.
I’ve used Reflection when I’ve had to in the past, and it certainly works, but it’s always had something of a “dark art” feel to me. And the times when I have used Reflection heavily have been within managed C# code – not PowerShell. Reflection is very persnickety regarding object types and method signatures, and C# (with its strict typing) makes it relatively painless to manage these issues. PowerShell, on the other hand, is pretty loose with typing and coercion – not ideal for working with Reflection-based parameter specification and method invocation.
Dark arts or not, I was bound and determined to provision my sync connections through PowerShell. So onward I marched.
The Result
I’m happy to say that this story does have a happy ending. The script that I’m sharing (Add-SPProfileDirectSyncConnection) to provision Active Directory Import connections appears below. It’s not the prettiest thing around, but hey – it works.
A word of caution before you pluck this out and start using it: the script goes directly against an internal method on the ConnectionManager class instead of a publicly exposed method, so I want to stress that this is a “use at your own risk” script. Anytime I have the option of using publicly accessible APIs, I do … but I had no other choice in this case.
[sourcecode language=”powershell” toolbar=”true” wraplines=”true”]
<#
.SYNOPSIS
Add-SPProfileDirectSyncConnection.ps1
.DESCRIPTION
Establishes a new Active Directory Direct user profile synchronization connection for SharePoint 2013.
.NOTES
Author: Sean McDonough
Last Revision: 27-June-2013
#>
param (
[parameter(mandatory=$true,position=1)][object]$userProfileApp,
[parameter(mandatory=$true)][string]$displayName,
[parameter(mandatory=$true)][string]$forestName,
[parameter(mandatory=$true)][string]$syncOU,
[parameter(mandatory=$false)][switch]$useSsl = $false,
[parameter(mandatory=$false)][switch]$useDisabledFilter = $false,
[parameter(mandatory=$false)][string]$ldapFilter = "",
[parameter(mandatory=$true)][string]$domain,
[parameter(mandatory=$true)][string]$username,
[parameter(mandatory=$true)][string]$password,
[parameter(mandatory=$false)][string]$claimProviderType = "Windows",
[parameter(mandatory=$false)][string]$claimProviderId = "Windows",
[parameter(mandatory=$false)][string]$claimIdMapAttribute = "samAccountName"
)
# Perform an LDAP lookup to get the object ID for the target domain.
$ldapLookupContext = "LDAP://" + $forestName
$ldapUsername = $domain + "\" + $username
$objDomain = New-Object System.DirectoryServices.DirectoryEntry($ldapLookupContext, $ldapUsername, $password)
if ($useSsl) {
$objDomain.AuthenticationType = [System.DirectoryServices.AuthenticationTypes]::SecureSocketsLayer
}
$ldapDomainDn = $objDomain.distinguishedName
$ldapDomainGuid = New-Object Guid($objDomain.objectGUID)
# Creation of the objects needed to properly specify the OU for the sync connection
$dnCtx = New-Object Microsoft.Office.Server.UserProfiles.DirectoryServiceNamingContext(
$ldapDomainDn, $forestName, $isDomain, $ldapDomainGuid, $includedOU, $excludedOU, $preferredDCs, $useOnlyPreferredDCs)
$namingContext = New-Object System.Collections.Generic.List[[Microsoft.Office.Server.UserProfiles.DirectoryServiceNamingContext]]
$namingContext.Add($dnCtx)
$ncParam = [System.Collections.Generic.List[Microsoft.Office.Server.UserProfiles.DirectoryServiceNamingContext]]$namingContext
# Since the method we’re about to invoke is internal, some hoops have to be jumped through
# to call it via PowerShell and Reflection
$paramTypes = @([Microsoft.Office.Server.UserProfiles.ConnectionType], [System.Guid], `
[System.String], [System.String], [System.Boolean], [System.Boolean], [System.String], `
[System.String], [System.String], [System.Security.SecureString], `
[System.Collections.Generic.List[Microsoft.Office.Server.UserProfiles.DirectoryServiceNamingContext]], `
[System.String], [System.String], [System.String])
$addConnMethodInfo = [Microsoft.Office.Server.UserProfiles.ConnectionManager].GetMethod("AddActiveDirectoryImportConnection", `
$INVOKE_ATTRIBUTES_NON_PUBLIC_MEMBERS, $null, $paramTypes, $null)
$methodParams = @($connType, $dcId, $displayName, $forestName, $isSslUsed, $isDisabledFilterUsed, $ldapFilter, `
$domain, $username, $securePassword, $ncParam, $claimProviderType, $claimProviderId, $claimIdMapAttribute)
$addConnMethodInfo.Invoke($upConfigMgr.ConnectionManager, $methodParams)
The script accepts a variety of parameters and has a couple of switches:
-userProfileApp. A reference to the User Profile Service Application instance for which the sync connection will be provisioned. This parameter is required.
-displayName. A string containing the “friendly name” that will appear for the sync connection within Central Administration. This parameter is required.
-forestName. A string that contains the fully qualified domain name (FQDN) for the domain that hosts the user profiles that will be imported by the sync connection. This parameter is required.
-syncOU. A string that contains the full distinguished specification of the Active Directory organizational unit (OU) where target user accounts (for import) reside. This parameter is required.
-useSsl. A switch that can be used to direct the script to use Secure Sockets Layer (SSL) transport encryption when connecting to Active Directory for synchronization operations and LDAP lookups. If this switch is not specified, connections are not made using SSL.
-useDisabledFilter. If this switch is included, the user profile selection process that takes place for the sync connection being established will filter out user accounts that are disabled. If this switch is not specified, both active and inactive user accounts will be imported during synchronization.
-ldapFilter. A string that contains an LDAP (Lightweight Directory Access Protocol) filter that will be applied to restrict the accounts which will be imported during synchronization. Creating this type of filter is made easier with the use of a tool like ADSI Edit. This parameter is optional.
-domain. A string containing the NetBIOS name of the domain where the profile synchronization account resides. This string is combined with the –username parameter to form a domain + username account specification. This parameter is required.
-username. A string containing the username of the profile synchronization account. This parameter should include the username only without a domain name qualifier. A domain name is specified separately through the –domain parameter. This parameter is required.
-password. A string containing the plaintext password of the profile synchronization account. This parameter is required.
-claimProviderType. A string identifying the type of authentication provider that is used to encode user profile account names. Common values for this parameter are Windows and Trusted. This parameter is optional; if unspecified, a value of Windows is used.
-claimProviderId. A string specifying the name or ID of the claims provider in-use. When a –claimProviderType of Windows is used, this parameter value is also typically Windows. If a –claimProviderType of Trusted is used, then this parameter value is used in conjunction with that value to identify the specific auth provider instance used. This parameter is optional and will default to Windows if unspecified.
-claimIdMapAttribute. A string that specifies the claims ID or field used by the claim provider. This parameter is specific to the claim provider, and in Windows authentication scenarios will usually be samAccountName. This parameter is optional.
The following is an example of how you might use the Add-SPProfileDirectSyncConnection script in a larger script that provisions user profile Active Directory Direct sync connections.
Some tips and information regarding the script and its usage:
Before executing this script, ensure that the account under which the script is being run has Full Control connection permission to the User Profile Service Application. This is set by highlighting the UPA instance in the “Manage Service Applications” page (ServiceApplications.aspx) in Central Admin, clicking the Permissions button on the toolbar, and granting Full Control permissions to the desired account as shown on the right. If this step is not performed, the script will error-out.
If a sync connection with the supplied –displayName already exists, the script will exit. It will not attempt to create another connection with the same name.
Ensure that the user profile synchronization account specified has the necessary replication permissions with the target user profile store. If the account lacks these permissions, the sync connection will be properly established but profiles will not be imported. Both TechNet and Spence Harbar covers this permission requirement in their various articles/posts, and it’s the same one that was needed in SharePoint 2010.
Final Word
My goal with this post was to (hopefully) save some of you the hassle I encountered while trying to automate a task that I thought would have been trivial … but wasn’t. Please let me know if this ends up helping you!
And although this script has been tested, I don’t have the facilities to test every combination of parameter and environment. If you find something that’s wrong or should be refactored, please let me know and I’ll perform an update.
While applying service pack 1 (SP1) for SharePoint 2010 to my environments, I encountered an odd problem that left my SharePoint Timer Service (OWSTIMER.EXE) repeatedly crashing. This post walks through my analysis, troubleshooting, and (quasi-)resolution.
After much anticipation, Microsoft finally released service pack 1 (SP1) for SharePoint 2010 earlier last week. Like so many other SharePoint professionals, I headed out to Microsoft’s site and pulled down everything I needed to patch-up my servers and virtual machines (VMs).
I’ve patched about four different server and VM environments with SP1 thus far, and although my experience with SP1 has been relatively positive, I did run into one particular snag that I thought I’d blog about. It was a weird one. I don’t claim to have good answers, but (at a minimum) I wanted to share my experience and observations.
What Happened?
My primary development environment is a virtual machine running Windows Server 2008 R2, SQL Server 2008 R2 Enterprise, SharePoint Server 2010, and the Office Web Apps. I don’t have any cumulative updates (CUs) or hotfixes installed, and everything is patched-up via Windows Update. Generally speaking, life is pretty happy in my little VM bubble. I sometimes have a hiccup or two due to the VM’s role as a domain controller (DC), but I can usually resolve those problems without too much trouble.
The relative peace and tranquility I normally experience was interrupted by none other than SP1. Like a bull in a china shop, SP1 announced that it had arrived on the scene with the dialog seen on the right. What struck me as weird is that all I had done up until the point when the dialog started appearing was the following:
Install SharePoint Foundation 2010 SP1
Reboot (as prompted by the SPF 2010 SP1 installer)
Install SharePoint Server 2010 SP1.
I hadn’t even run PSCONFIG or gone through the SharePoint 2010 Products Configuration Wizard, and suddenly I started getting unhandled exceptions. I still had the Office Web Apps SP1 package to install and wasn’t planning to run PSCONFIG until all the binaries had been laid down. My first reaction was “huh, that’s weird. I’ll just reboot.” That didn’t make the problem go away. Neither did running PSCONFIG and upgrading the farm. Shortly after rebooting, the dialog shown above would start appearing. If I’d clear it out by saying clicking “No,” I’d have another one waiting for me in about a minute.
My “Ostrich Maneuver” wasn’t cutting the mustard, so I had to dig in and figure out what I needed to do in order to get my environment back in order.
Reluctant Analysis
The Visual Studio Just-In-Time Debugger dialog was popping up about every minute, so I figured that it was as good a place to start as any. The only thing the dialog really told me was the following:
the problem was with OWSTIMER.EXE (i.e., the SharePoint Timer Service)
a System.ServiceModel.EndpointNotFoundException was being thrown
Since this was my development environment and Visual Studio 2010 was installed, I decided to attach the VS debugger to the SharePoint Timer Service process and see if I could learn anything more. The dialog on the left was the result.
Although the dialog didn’t contain a substantial amount of new information, it did include a few tidbits I needed to continue my hunt for the culprit behind my constant timer service crashes:
The exception was being generated by the metadata exchange (MEX) endpoint for a service that resided on the ResourceManagementService path. Since the connection was being actively refused, there was a decent chance that the service simply wasn’t running
The service in question was apparently running (or expected to be running) on TCP port 5725
A web search for “SharePoint” and “5725” quickly brought me to the Plan security hardening (SharePoint Server 2010) page on TechNet. On that page, I found the following under the User Profile service hardening requirements section:
TCP port 5725 must be open on the server that runs the Forefront Identity Management agent and is set up to crawl a directory store.
The revelation that I was dealing with Forefront Identity Manager (FIM) and the SharePoint 2010 User Profile Service (UPS) immediately kicked-off a round of cursing and forehead slapping that took a couple of minutes to rein in.
User Profile Service Investigation
I’m sure that the ResourceManagementService path and port 5725 is obvious to many of you, but I’ll come clean and tell you that I try to stay as far away from the User Profile Service as I possibly can. Unless I know that I’m going to be doing something with the UPS and social networking, I don’t even bother with it in most of my SharePoint environments. Why? Because the UPS is just a colossal pain in the butt to configure and keep running. It is the bane of SharePoint administrators everywhere. Since I’m averse to pain and rarely do anything with it, I stay away from the UPS.
I had trouble believing that I would have gone through the trouble of configuring the UPS in my development environment, so I decided to take a look in Central Administration. Much to my surprise, I had actually taken the time to jump through all of the hoops (as shown on the right) to get the service running, AD synchronization going, etc.
If the UPS was running and the service was actually started (which I confirmed through the Services on Server section of Central Administration), why was I seeing constant timer service crashes?
I decided to look a little further; specifically, I went in to look at the actual Forefront Identity Manager Service which backstops the whole User Profile Service. I popped-open Start –> Administrative Tools –> Services and navigated to the FIM service. The result (which is shown on the left) explained a lot.
Even though the service was set to auto-start, it wasn’t running. Without the service running, it would make sense that attempts to reach it or interact with it would fail.
I decided to manually start the service. Once I did so, the OWSTIMER.EXE crashes ceased. I thought I was in the clear … until I rebooted. Once I rebooted, the FIM service failed to start again and the OWSTIMER.EXE exceptions started happening again. Ugh.
Rewind
I spent about an hour or two poking and prodding, but I didn’t make much headway. Since I was more interested in getting my development environment usable than solving my UPS problems, I decided to cut bait and rewind.
Remember: this is a VM that we’re talking about. Since I try to practice what I preach (particularly from a backup and restore perspective), I had taken a VM snapshot prior to the start of SP1 work. I rolled-back to my pre-SP1 state and took a different approach to the service pack application.
Fans of the movie Aliens will appreciate my application of the “Ellen Ripley Strategy” as shown on the right:
I say we take off and nuke the entire site from orbit. It’s the only way to be sure.
As mentioned earlier, I seldom if ever use the User Profile Service. If it was causing me problems with the application of SP1, I decided that it had to go. At a minimum, I wanted to see if SP1 would go on properly without an instance of the User Profile Service Application running in my environment.
I deleted my UPS service application instance and decided to have a look at the FIM service again. What I saw (on the left) looked a bit different. Rather than being set to auto-start (“Automatic”), both the FIM Service and its associated synchronization service were set to “Disabled.”
I then went through the process of laying down the various service packs, and even before running PSCONFIG I knew that things were going to be different. Applying the SharePoint 2010 Server Service Pack 1 binaries did not start a constant stream of OWSTIMER.EXE exceptions. I was able to lay down all of the service packs and run PSCONFIG without issue. Problem solved.
That’s Your Solution?
So, is deleting your UPS service application instance a solution to this problem? In most production environments, I’d wager that the answer is “probably not".” If I think about the situation from a production perspective, the answer may be to preserve the appropriate UPS databases, tear down the UPS service application instance for SP1 application, and then rebuild the UPS service application afterwards.
My primary objective with this post, though, was to simply provide some initial root-cause analysis and troubleshooting. I may be the only person seeing this; if so, I hope that what I’ve written was at least entertaining. If you happen to be seeing the same symptoms I saw, though, then I hope that what I’ve written saves you a little time while troubleshooting.