{"id":1135,"date":"2010-12-20T09:21:09","date_gmt":"2010-12-20T09:21:09","guid":{"rendered":"https:\/\/www.wapshere.com\/missmiis\/?p=1135"},"modified":"2010-12-20T09:21:09","modified_gmt":"2010-12-20T09:21:09","slug":"password-sync-from-ad-to-bpos","status":"publish","type":"post","link":"https:\/\/www.wapshere.com\/missmiis\/password-sync-from-ad-to-bpos","title":{"rendered":"Password Sync from AD to BPOS"},"content":{"rendered":"<p>The FIM Sync Service allows passwords to be synchronised from a source AD account to the user&#8217;s\u00c2\u00a0accounts in other systems. The sync is done at the point of password change and relies on the Password Change Notification Service, which you must install on\u00c2\u00a0your domain controllers.<\/p>\n<p>Many target systems are supported OOB, but for BPOS you have to get a little creative and write your own <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms695388(VS.85).aspx\">Password Extension<\/a>. This post shows you how I did that.<\/p>\n<p>Before I continue however, I do feel obliged to make a comment about the security implications of doing this. There is an argument that passwords used internally should never be sent outside the firewall, and this should be considered in your environment. In my case the source AD was created just for DirSync so the accounts there are not actually being used for anything else, and the password remains different to the user&#8217;s normal desktop logon. Password Sync provides an alternative to the BPOS admin console, and opens the way for reset through the FIM Portal.<br \/>\n<!--more--><\/p>\n<h3>First, you need a connector space which represents your BPOS user objects<\/h3>\n<p>This should be pretty obvious &#8211; for password sync to work it needs a direct join, through the Sync Service, from the source AD to the target account. So you need a connector space with objects that represent your BPOS users and contain, at an absolute minimum, the BPOS Identity which you will use in the powershell password change process.<\/p>\n<p>For ideas about creating a BPOS MA see <a href=\"https:\/\/www.wapshere.com\/missmiis\/three-different-ways-to-create-a-bpos-management-agent\">Three Different Ways to Create a BPOS Management Agent<\/a>.<\/p>\n<h3>The MA has to run in process<\/h3>\n<p>There is apparently a bug with password extensions in FIM Sync &#8211; if you run the MA in a seperate process the sync service can&#8217;t find the extension and you see these errors in the event log:<\/p>\n<p><code>An unexpected error has occurred during a password set operation.<br \/>\nBAIL: MMS(4948): ma.cpp(373): 0x80040154 (Class not registered)<\/code><\/p>\n<p>Running the MA in process fixes this problem.<\/p>\n<h3>The Powershell Runspace doesn&#8217;t dispose quickly enough<\/h3>\n<p>I ran into a problem with a System.AppDomainUnloadedException. The password sync worked fine, but then five minutes later the entire miiserver.exe process crashed with this exception.<\/p>\n<p>The Sync service loads an extension dll when it is needed, and keeps it open while it&#8217;s being used. Five minutes after the last use of the dll it runs any termination code and unloads the dll. Clearly something was going wrong here.<\/p>\n<p>At this point I&#8217;m going to shout out a big thanks to <a href=\"http:\/\/briandesmond.com\/\">Brian Desmond<\/a> and <a href=\"http:\/\/www.identitytrench.com\/\">Craig Martin<\/a> who helped me with this problem. While I still don&#8217;t completely understand what&#8217;s going on, I gather it has something to do with the sync service unloading the password extension while the runspace is still being disposed. The solution I found is to add a System.Threading.Thread.Sleep(0) straight after the Dispose instruction,\u00c2\u00a0which is an instruction to wait for other threads to finish.<\/p>\n<h3>Install and configure PCNS<\/h3>\n<p>&nbsp;<br \/>\nI&#8217;m not going to go into this. There is perfectly good documentation (see Peter Geelen&#8217;s overview and troubleshooting tips <a href=\"http:\/\/social.technet.microsoft.com\/wiki\/contents\/articles\/troubleshooting-pcns.aspx?wa=wsignin1.0\">here<\/a>) and actually the instructions haven&#8217;t changed since MIIS 2003.<\/p>\n<p>Though I will just add, in case anyone&#8217;s interested, yes you can install PCNS on a server core domain controller (though not an RODC for the\u00c2\u00a0obvious reason that it can&#8217;t process a password change).<\/p>\n<h3>Configuring the Sync Service<\/h3>\n<table border=\"1\">\n<tbody>\n<tr>\n<td>Start\u00c2\u00a0by enabling Password Sync:Tools &#8211;&gt; Options &#8211;&gt; Enable Password Synchronization<\/td>\n<td><a href=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync1.jpg\"><\/a><a href=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-1179\" title=\"pwsync1\" src=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync1-300x255.jpg\" alt=\"\" width=\"300\" height=\"255\" srcset=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync1-300x255.jpg 300w, https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync1.jpg 508w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/td>\n<\/tr>\n<tr>\n<td>Configure the AD MA which is the source for synchronization of changed passwords:<\/p>\n<p>On the &#8220;Configure Directory Properties&#8221; tab, tick to enable the partition as a password synchronization source.<\/p>\n<p>Click &#8220;Targets&#8221; and select your BPOS MA.<\/td>\n<td><a href=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync2.jpg\"><\/a><a href=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync2.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-1178\" title=\"pwsync2\" src=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync2-300x215.jpg\" alt=\"\" width=\"300\" height=\"215\" srcset=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync2-300x215.jpg 300w, https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync2.jpg 679w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/td>\n<\/tr>\n<tr>\n<td>Configure the BPOS MA:<\/p>\n<p>On the &#8220;Configure Extensions&#8221; tab, tick to enable password management and select the password extension dll file (code below).<\/p>\n<p>You will also have to set the connection credentials. As I&#8217;m accessing the MSOnline cmdlets via remote powershell I actually need two sets of credentials &#8211; one for the remote server connection and one for BPOS. However the MA config for password extensions only gives you space for one username and password. I have gone with the completely inelegant workaround of including both usernames and both passwords, separated by a semi-colon, like so:<\/p>\n<p><code>&nbsp;&nbsp;&nbsp;mydomainUser;bposUser<br \/>\n&nbsp;&nbsp;&nbsp;mydomainPassword;bposPassword<\/code><\/p>\n<p>It&#8217;s not pretty and I don&#8217;t much like it, but it&#8217;s working for now.<\/td>\n<td><a href=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync3.jpg\"><\/a><a href=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync3.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-1177\" title=\"pwsync3\" src=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync3-300x224.jpg\" alt=\"\" width=\"300\" height=\"224\" srcset=\"https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync3-300x224.jpg 300w, https:\/\/www.wapshere.com\/missmiis\/wp-content\/uploads\/2010\/12\/pwsync3.jpg 679w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>The Code<\/h3>\n<p>And here is the password extension code.<\/p>\n<p><code> <\/code><\/p>\n<pre>Imports Microsoft.MetadirectoryServices\r\nImports System.Management.Automation\r\nImports System.Management.Automation.Host\r\nImports System.Management.Automation.Runspaces\r\nImports System.Diagnostics\r\n\r\nPublic Class BPOSPasswordExtension\r\n\r\n    Implements IMAPasswordManagement\r\n\r\n    Dim myRunSpace As Runspace\r\n    Dim bposCred As PSCredential\r\n\r\n    Public Sub BeginConnectionToServer(ByVal connectTo As String, _\r\n        ByVal user As String, _\r\n        ByVal password As String) _\r\n        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.BeginConnectionToServer\r\n\r\n        Dim PSRemoteUser As String = \"\"\r\n        Dim PSRemotePassword As String = \"\"\r\n        Dim BPOSUser As String\r\n        Dim BPOSPassword As String\r\n\r\n        If user.Contains(\";\") Then\r\n            PSRemoteUser = user.Split(\";\")(0)\r\n            BPOSUser = user.Split(\";\")(1)\r\n        Else\r\n            BPOSUser = user\r\n        End If\r\n\r\n        If password.Contains(\";\") Then\r\n            PSRemotePassword = password.Split(\";\")(0)\r\n            BPOSPassword = password.Split(\";\")(1)\r\n        Else\r\n            BPOSPassword = password\r\n        End If\r\n\r\n        If Not connectTo = \"\" Then\r\n            If PSRemoteUser = \"\" Or PSRemotePassword = \"\" Then\r\n                Throw New BadServerCredentialsException(\"If Connect To is configured in the Password Settings then \" _\r\n                &amp; \"there must be two semicolon separated Users (psremoteuser;bposuser) and two semicolon separated \" _\r\n                &amp; \"passwords (psremotepassword;bpospassword)\")\r\n                Exit Sub\r\n            End If\r\n        End If\r\n\r\n        ' Open powershell runspace\r\n        If connectTo = \"\" Then\r\n            myRunSpace = OpenLocalRunspace()\r\n        Else\r\n            myRunSpace = OpenRemoteRunspace(connectTo, PSRemoteUser, PSRemotePassword)\r\n        End If\r\n        If myRunSpace Is Nothing Then\r\n            Throw New PasswordExtensionException(\"Failed to open powershell runspace to server \" &amp; connectTo)\r\n            Exit Sub\r\n        End If\r\n        WriteToEventLog(\"Successfully opened powershell runspace to \" &amp; connectTo, EventLogEntryType.Information)\r\n\r\n        ' Create credential for attaching to BPOS\r\n        Dim bpospass As New Security.SecureString\r\n        For Each c In BPOSPassword\r\n            bpospass.AppendChar(c)\r\n        Next\r\n        bposCred = New PSCredential(BPOSUser, bpospass)\r\n\r\n        ' Add plugin required for BPOS cmdlets\r\n        Dim psh As PowerShell = PowerShell.Create()\r\n        psh.Runspace = myRunSpace\r\n        psh.AddCommand(\"Add-PSSnapin\")\r\n        psh.AddParameter(\"Name\", \"Microsoft.Exchange.Transporter\")\r\n        Try\r\n            psh.Invoke()\r\n        Catch ex As Exception\r\n            Throw New PasswordExtensionException(\"Failed to add PS snapin. \" &amp; ex.Message)\r\n        End Try\r\n\r\n    End Sub\r\n\r\n    Public Sub ChangePassword(ByVal csentry As Microsoft.MetadirectoryServices.CSEntry, _\r\n        ByVal OldPassword As String, _\r\n        ByVal NewPassword As String) _\r\n        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.ChangePassword\r\n    End Sub\r\n\r\n    Public Sub EndConnectionToServer() _\r\n        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.EndConnectionToServer\r\n        If Not myRunSpace Is Nothing Then\r\n            myRunSpace.Dispose()\r\n            System.Threading.Thread.Sleep(0)\r\n            WriteToEventLog(\"Runspace closed.\", EventLogEntryType.Information)\r\n        End If\r\n    End Sub\r\n\r\n    Public Function GetConnectionSecurityLevel() As Microsoft.MetadirectoryServices.ConnectionSecurityLevel _\r\n        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.GetConnectionSecurityLevel\r\n    End Function\r\n\r\n    Public Sub RequireChangePasswordOnNextLogin(ByVal csentry As Microsoft.MetadirectoryServices.CSEntry, _\r\n        ByVal fRequireChangePasswordOnNextLogin As Boolean) _\r\n        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.RequireChangePasswordOnNextLogin\r\n        ' This method is not used\r\n        Throw New EntryPointNotImplementedException\r\n    End Sub\r\n\r\n    Public Sub SetPassword(ByVal csentry As Microsoft.MetadirectoryServices.CSEntry, _\r\n        ByVal NewPassword As String) _\r\n        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.SetPassword\r\n\r\n        Dim psh As PowerShell = PowerShell.Create()\r\n        Dim psresult As New System.Collections.ObjectModel.Collection(Of PSObject)\r\n        psh.Runspace = myRunSpace\r\n\r\n        psh.AddCommand(\"Set-MSOnlineUserPassword\")\r\n        psh.AddParameter(\"Identity\", csentry.DN.ToString)\r\n        psh.AddParameter(\"Password\", NewPassword)\r\n        psh.AddParameter(\"ChangePasswordOnNextLogon\", False)\r\n        psh.AddParameter(\"Credential\", bposCred)\r\n        Try\r\n            psresult = psh.Invoke()\r\n        Catch ex As Exception\r\n            Throw New PasswordExtensionException(ex.Message)\r\n        End Try\r\n\r\n        If psh.Streams.Warning.Count &gt; 0 Then\r\n            Throw New PasswordExtensionException(psh.Streams.Warning.Item(0).Message)\r\n        ElseIf psh.Streams.Error.Count &gt; 0 Then\r\n            Throw New PasswordExtensionException(psh.Streams.Error.Item(0).ErrorDetails.Message)\r\n        End If\r\n\r\n        psh.Dispose()\r\n\r\n    End Sub\r\n\r\n#Region \"Powershell Functions\"\r\n    Private Function PSCredObject(ByVal username As String, ByVal password As String) As PSCredential\r\n        Dim PWSecureString As New Security.SecureString\r\n        Dim c As Char\r\n        For Each c In password\r\n            PWSecureString.AppendChar(c)\r\n        Next\r\n        Dim PSCred As New PSCredential(username, PWSecureString)\r\n        Return PSCred\r\n    End Function\r\n\r\n    Private Function OpenRemoteRunspace(ByVal RemoteServer As String, ByVal RemoteUser As String, ByVal RemotePassword As String) As Runspace\r\n        Const SHELL_URI As String = \"http:\/\/schemas.microsoft.com\/powershell\/Microsoft.PowerShell\"\r\n\r\n        ' Open remote powershell session\r\n        Dim serverUri As New Uri(\"http:\/\/\" &amp; RemoteServer.ToUpper &amp; \":5985\/wsman\")\r\n        Dim connectionInfo As New WSManConnectionInfo(serverUri, SHELL_URI, PSCredObject(RemoteUser, RemotePassword))\r\n        Dim myRunSpace As Runspace\r\n        Try\r\n            myRunSpace = RunspaceFactory.CreateRunspace(connectionInfo)\r\n            myRunSpace.Open()\r\n        Catch ex As Exception\r\n            Throw New PasswordExtensionException(ex.Message)\r\n            Return Nothing\r\n            Exit Function\r\n        End Try\r\n        Return myRunSpace\r\n    End Function\r\n\r\n    Private Function OpenLocalRunspace() As Runspace\r\n        Dim config As RunspaceConfiguration = RunspaceConfiguration.Create()\r\n        Dim myRunSpace As Runspace\r\n        Try\r\n            myRunSpace = RunspaceFactory.CreateRunspace(config)\r\n            myRunSpace.Open()\r\n        Catch ex As Exception\r\n            Throw New PasswordExtensionException(ex.Message)\r\n            Return Nothing\r\n            Exit Function\r\n        End Try\r\n        Return myRunSpace\r\n    End Function\r\n\r\n#End Region\r\n\r\n    Public Function WriteToEventLog(ByVal Entry As String, ByVal eventType As EventLogEntryType)\r\n        Dim appName As String = \"BPOS Password Sync\"\r\n        Dim logName = \"Application\"\r\n\r\n        Dim objEventLog As New EventLog()\r\n\r\n        Try\r\n            'Register the App as an Event Source\r\n            'Note: this only works if the service account has rights to the reg key http:\/\/support.microsoft.com\/kb\/842795\r\n            If Not EventLog.SourceExists(appName) Then\r\n                EventLog.CreateEventSource(appName, logName)\r\n            End If\r\n\r\n            objEventLog.Source = appName\r\n            objEventLog.WriteEntry(Entry, eventType)\r\n            Return True\r\n        Catch Ex As Exception\r\n            Return False\r\n        End Try\r\n    End Function\r\n\r\nEnd Class<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>The FIM Sync Service allows passwords to be synchronised from a source AD account to the user&#8217;s\u00c2\u00a0accounts in other systems. The sync is done at the point of password change and relies on the Password Change Notification Service, which you must install on\u00c2\u00a0your domain controllers. Many target systems are supported OOB, but for BPOS you&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[47,42,11,30],"tags":[],"class_list":["post-1135","post","type-post","status-publish","format-standard","hentry","category-bpos","category-fim-2010","category-password-sync","category-vbnet"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pkp1o-ij","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts\/1135","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/comments?post=1135"}],"version-history":[{"count":17,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts\/1135\/revisions"}],"predecessor-version":[{"id":1194,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts\/1135\/revisions\/1194"}],"wp:attachment":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/media?parent=1135"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/categories?post=1135"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/tags?post=1135"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}