Thursday, May 24, 2012

FIM 2010 sync-rule-inbound-flow-rules-invalid

 

I created a fairly simple text based MA for a custom building object and had constructed an Inbound Synchronization Rule in FIM Service, but when I synched the rule into the Metaverse it failed with a sync-rule-inbound-flow-rules-invalid failure.

This was difficult to troubleshoot as there was no stack trace, which meant no other error messages with more detail were found.  Cutting the inbound flows to just the building number anchor attribute didn’t help.  I noticed in Metaverse Designer that it showed the building number now had an inbound flow even though the Synchronization Rule did not make it into the Metaverse, which I thought was strange.

I had deleted and recreated the MA and Synchronization Rule many times without correcting the error.  Without jumping all the way back to a completely new install, I took another step back and recreated the object type and attributes that were going to be used.

It turned out, when I had first created the Object Type in FIM Sync, I had defined the anchor attribute as String (non-indexable)[1].  By deleting the Object Type and reconstructing it with String (indexable) attributes I was able to get the rule to sync into the Metaverse.

So the takeaway from this is that sync-rule-inbound-flow-rules-invalid may have nothing to do with the inbound flows defined in FIM Service at all.  I suppose it makes sense that the anchor attribute should at least be indexable.  But the error message sure could use some work.

 

[1] I did not create the attributes that way on purpose.  We have so many object classes and attributes that we deal with I had written a script to populate the schema programatically and the script was creating all String attributes as non-indexable.

Monday, March 19, 2012

WinRM 0x8033809D and servicePrincipalNames

 

I had a brand new server that wasn’t letting me connect to it via WinRM.  Since we have an automated build process that configures the WinRM listener, and I could connect to other servers from the same batch, it was an unexpected failure.

Even locally on the server I couldn’t connect to WinRM.

C:\temp>winrm get winrm/config
WSManFault
    Message = WinRM cannot process the request. The following error occured while using Negotiate authentication: An unknown security error occurred.
Possible causes are:
  -The user name or password specified are invalid.
  -Kerberos is used when no authentication method and no user name are specified.
  -Kerberos accepts domain user names, but not local user names.
  -The Service Principal Name (SPN) for the remote computer name and port does not exist.
  -The client and remote computers are in different domains and there is no trust between the two domains.
After checking for the above issues, try the following:
  -Check the Event Viewer for events related to authentication.
  -Change the authentication method; add the destination computer to the WinRM TrustedHosts configuration setting or use HTTPS transport.
Note that computers in the TrustedHosts list might not be authenticated.
   -For more information about WinRM configuration, run the following command: winrm help config.

Error number:  -2144108387 0x8033809D
An unknown security error occurred.

Following the advice in the error message I verified that duplicate SPNs were not the issue.  There was only one computer in the forest with that server’s name, and it was the expected object.

Once again NetMon to the rescue!

A network trace showed a Kerberos request for a ticket for the HTTP/<server FQDN> servicePrincipalName.  The ticket was obtained successfully, but then a second request for the same ticket.  I thought that was odd, so I searched the forest for that specific SPN.  It turns out we had reused the computer name from a decommissioned FIM instance and that the proxy account that previously ran Windows SharePoint Services had a SPN for HTTP/<server FQDN>.  There were not duplicate SPNs (HOST is an alias for many SPNs including HTTP) since there was only one instance of the HOST SPN and one instance of the HTTP SPN, but the SPN was placed on the wrong object, so the ticket was being encrypted with the wrong password.  Removing the old HTTP SPN from the WSS proxy account solved the problem.

So the key takeaway is WinRM relies on the HTTP/<server FQDN> SPN, which is a non-default SPN and is usually simply covered by the HOST/<server FQDN> SPN.  Ensuring the HTTP SPN is set correctly is a corner-stone for making WinRM function.

Friday, February 17, 2012

My First Hotfix - KB2659158

 

I am now the proud father of my first hotfix to be accepted and make it all the way to release.

http://support.microsoft.com/kb/2659158

We were alerted to the problem as we began our rollout of RODCs.  Any new Windows 7 clients deployed at the site were bricks.  The staging process would complete successfully but it wouldn’t let you log on.  It would display the error “The security database on the server does not have a computer account for this workstation trust relationship.”

We had a tough time starting to triage the problem because the client build ends with a lockdown of BitLocker being applied and a scramble of the local admin account.  So we couldn’t get on to the box to look at any logs.  We had to throw some hooks into a custom build to keep control of the client.  Eventually we were able to track it down to a Kerberos problem where the machine joins with a contiguous name, even though it was locally configured to have a disjoint name.  With the RODC covering the site the machine was unable to correct its invalid dNSHostName that it put into AD (the client would issue an LDAP write, the RODC would correctly respond with a referral, but the client would never chase it).  If covered by a writeable DC, the client was able to silently change its name at first reboot so the machine logon works (the RWDC accepted the LDAP write to dNSHostName).

The hotfix corrects the issue by having the client join to AD initially with a valid, disjointed, dNSHostName value so that an RODC never receives a copy of a new computer account with an invalid dNSHostName.

Friday, January 27, 2012

PowerShell DirSync Sample

 

Continuing with the PoC setup for the SQL Server MA, I want the flexibility to load the SQL data from either the authoritative text-file, or from a separate AD environment.  This will provide me the ability to test the MA in Dev and QA environments that are not as active as Production.  To do that, I want DirSync data from a source domain to use to populate the table.

Microsoft’s DirSync documentation has a sample in C++.  400+ lines of code!  And a very low percentage of the code volume is directly related to the search.  Yuck.  However, with some more Googling one can find that Brandon has a System.DirectoryServices.Protocols sample for PowerShell and the Israel Platforms PFE Team has a DirSync sample for C#.  Finally SANS had a nice overview on handling the Byte in PowerShell, which the DirSync cookie uses.

All the ingredients are assembled.  Combine and bake at 400° for 20 minutes and you get:

Add-Type -AssemblyName System.DirectoryServices.Protocols

If (Test-Path .\cookie.bin –PathType leaf) {
    [byte[]] $Cookie = Get-Content -Encoding byte –Path .\cookie.bin
} else {
    $Cookie = $null
}

$RootDSE = [ADSI]"LDAP://RootDSE"
$LDAPConnection = New-Object System.DirectoryServices.Protocols.LDAPConnection($RootDSE.dnsHostName)
$Request = New-Object System.DirectoryServices.Protocols.SearchRequest($RootDSE.defaultNamingContext, "(objectclass=*)", "Subtree", $null)
$DirSyncRC = New-Object System.DirectoryServices.Protocols.DirSyncRequestControl($Cookie, [System.DirectoryServices.Protocols.DirectorySynchronizationOptions]::IncrementalValues, [System.Int32]::MaxValue)
$Request.Controls.Add($DirSyncRC) | Out-Null

$MoreData = $true

while ($MoreData) {

    $Response = $LDAPConnection.SendRequest($Request)

    $Response.Entries | ForEach-Object {
        write-host $_.distinguishedName
    }

    ForEach ($Control in $Response.Controls) {
        If ($Control.GetType().Name -eq "DirSyncResponseControl") {
            $Cookie = $Control.Cookie
            $MoreData = $Control.MoreData
        }
    }
    $DirSyncRC.Cookie = $Cookie
}

Set-Content -Value $Cookie -Encoding byte –Path .\cookie.bin

There you have it.  PowerShell DirSync in 27 lines of code!

[Edit 2012.02.20: optimized while loop]

Thursday, January 26, 2012

FIM SQL Server MA (or PowerShell for SQL Table-Valued Parameters)

 

Working on a Proof of Concept for FIM has been a refreshing visit to some of my old hunting grounds.  Long before AD existed (or even NT) my first real job was for a database consulting company.  It no longer exists, but its legacy can be found at www.noetix.com; and some of the people I knew back then are still there.

The FIM PoC begins with a text-based authoritative data source with no natural key.  The FIM text-based MAs require a key, so I could not use those MAs and still support user account renames.  So I decided to import the data into a SQL Server table with an Identity column and use the SQL Server MA.  This decision has brought me a fair amount of SQL work that I have not done for years.

The first fun task was generating the delta view.  I opted to follow the trigger approach, but wanted it to act more like Active Directory DirSync where one can retrieve the deltas based on a cookie being held and presented, but the older deltas still exist.  So I created a timestamp-based view of the delta table at MA runtime specific to my particular FIM instance.  The view will present all the deltas from the last provided timestamp plus a 5 minute overlap to handle the Kerberos-allowed time skew.  This approach also allows a parallel FIM instance to receive its own deltas without impact to each other by simply creating a differently-named view specific to that instance.

Then I started working on multi-valued attributes.  Per standard normalization rules for SQL, one ends up with a second table linking the primary object table Identity column to the multivalued attribute name and a single value.  To add a second value to the attribute the table needs another row with the same Identity foreign key, same attribute name and the second single value.

FIM’s granularity for SQL deltas is limited to indicating the object as a whole has changed.  It has no granularity for indicating attribute deltas like DirSync can, so any insert or delete into the multi-valued table triggers a delta of the whole object.  This matches up well with the text-based source as all attributes (single and multi) are encoded into one row in the file.  There is no easy way to know what has changed in the source data when it is received, so I simply need to make sure the SQL data exactly matches the newly received row from the authoritative data source.  Put into practice this means all of the single-valued attributes must be rewritten, all the existing multi-valued attributes must be deleted and the new multi-valued attributes must be inserted.

I was brought up with the stored-procedure methodology for interacting with any database, so my goal was to develop a stored procedure that I could call from PowerShell to take care of the attribute updates.  I split this into two stored procedures: one for the single-valued attributes, and one for the multi-valued attributes that I could iterate over for each multi-valued attribute in the source feed.  The multi-valued stored procedure causes difficulty in the handling of the one delete and “n” number of inserts.  You can’t put that into one stored procedure and support an unknown number of multi-value attribute inserts.  So either it has to be broken into two stored procedures (execute the delete stored procedure followed by n executions of the insert stored procedure), or find a way to get the single stored procedure to recognize an array for the inserts.

The multiple stored procedures didn’t sound elegant, and I haven’t worked on SQL Server for a while, so I spent a little time searching and fairly quickly found Table-Valued Parameters and a great blog entry from Erland Sommarskog, SQL Server MVP.  One of the nice benefits of FIM not being cross-platform is its requirement of SQL 2008 and the ability for me to now use other new SQL 2008 features.

Begin by creating a User Defined Table Type:

CREATE TYPE [dbo].[multivalue_list] AS TABLE(
    [attributevalue] [nvarchar](1024) NOT NULL
)

Create a stored procedure that uses that type[1]:

CREATE PROCEDURE
    [dbo].[import_people_multivaluesdata]
          @p_cn varchar(64)
        , @p_attributename varchar(64)
        , @p_multivalue_list multivalue_list READONLY
   
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    DELETE FROM
        people_multivalues
    WHERE
            AttributeName = @p_attributename
        and ObjectID IN (
            SELECT
                ObjectID
            FROM
                objects
            WHERE
                cn = @p_cn
            )
   
    INSERT INTO
        people_multivalues
    SELECT
          o.ObjectID
        , @p_attributename
        , mvl.attributevalue
    FROM
          objects o
        , @p_multivalue_list mvl
    WHERE
        o.cn = @p_cn
       
    SELECT
        COUNT(m.objectid) as row_count
    FROM
        objects o
            JOIN
        people_multivalues m
            ON
                o.ObjectID = m.ObjectID
    WHERE
            o.cn = @p_cn
        and m.AttributeName = @p_attributename
END

There are two important things to note in this procedure.  The first is the use of the READONLY directive on the input table parameter.  The second is the FROM clause for the INSERT statement having no JOIN clause.  The incoming table only has the values for multi-valued attribute, so there is nothing on which to join.  This results in the Cartesian product between the two, resulting in one row being inserted for each row in the input table parameter – exactly the goal!

Calling this procedure from T-SQL is fairly straight forward:

declare @mylist multivalue_list
insert @mylist(attributevalue) values ('val1'), ('val2')
exec import_people_multivaluesdata @p_cn = 'cn1', @p_attributename='multiattr', @p_multivalue_list = @mylist

Calling this from PowerShell requires a bit more setup.  Mr. Sommarskog’s sample for ADO.Net (really C# SQLClient) can be translated into PowerShell as follows (switching from int to VarChar, and using our above stored procedure):

$products = "A", "B", "C"

$product_list = New-Object 'System.Collections.Generic.List[Microsoft.SqlServer.Server.SqlDataRecord]'

$tvp_definition = New-Object Microsoft.SqlServer.Server.SqlMetaData ("attributevalue", "VarChar", 1024)

ForEach ($product in $products) {
    $rec = new-object Microsoft.SqlServer.Server.SqlDataRecord($tvp_definition)
    $rec.SetSQLString(0, $product)
    $product_list.Add($rec)
}

$cmd.CommandType = CommandType.StoredProcedure
$cmd.CommandText = "dbo.import_people_multivaluesdata"

$cmd.Parameters.AddWithValue("@p_cn", "cn1")  | Out-Null
$cmd.Parameters.AddWithValue("@p_attributename", "multiattr")  | Out-Null
$cmd.Parameters.Add("@p_multivalue_list", [System.Data.SqlDbType]::structured) | Out-Null
$cmd.Parameters["@p_multivalue_list"].TypeName = "multivalue_list"
$cmd.Parameters["@p_multivalue_list"].Value = $product_list

$ReturnRowCount = $cmd.ExecuteScalar()

And there you have it.  With one call to a stored procedure, all current multi-value attribute entries for a specific attribute will be deleted and completely replaced with the new list of values and is scalable to any number of entries in the list.

[1] You can tell the difference between my own SQL code for the procedure and the GUI-generated code for the UDT.  This is how I was taught to write SQL code.  I see very few other samples that follow the same layout.  However, this is the only layout that gives you complete control of the ordering of table names or column names where you don’t have to worry about forgetting to have correct punctuation.  All the column names in the select statement are nicely lined up, and you can easily verify all subsequent columns begin with a comma.

Friday, December 16, 2011

Administrator Locked Out of FIM Portal

 

I’ve been coming up to speed on MIIS/ILM/FIM lately reading the documentation and walking through the evaluation guides in a small lab forest.  I was walking through the FIM 2010 procedure Introduction to Publishing To Active Directory from Two Authoritative Data Sources using FIM 2010 R2.  I had completed the main sync of HR into FIM and back to the Metaverse.  When I switched back to the portal to check on the results I was greeted with an error screen.



I’m not sure what caused the problem.  I didn’t have a backup to roll back to, and I didn’t want to give up and just reinstall.  From experience I know that you seldom learn more about a program than when it’s broken.  So I dove in to the problem.

I know FIM is mostly a large SQL application.  I understand the sync database pretty well.  It has two main tables.  The mms_metaverse table stores each object in a row and each attribute in a column.  This allows for indexing the attributes for fast joins and searching the metaverse.  The mms_connectorspace is more opaque.  The data from the connector space is stored as XML blobs in the hologram.

My first glance at the FIMService database showed me it didn’t look anything like the sync database.

To get started I profiled my attempt to open the portal.  You see a call retrieve the default page from SharePoint, then you see the call to figure out who I am.



This is a stored procedure call to GetUserFromSecurityIdentifier where the parameter appears to be my SID in binary form.

Since SQL Server Profiler doesn’t show the return value of the query, I ran that query myself.



Sure enough, no rows returned.  Looking at the stored procedure, it references the tables UserSecurityIdentifiers and Objects.  UserSecurityIdentifiers was completely empty.  UserSecurityIdentifiers is a simple table with only two columns: UserObjectKey and SecurityIdentifier.

There’s also a stored procedure called GetUserFromName.  When I ran that, it returned my name, so I was pretty sure I was just missing the row that tied my AD SID to the admin user in FIM.

To find my admin user in FIM I ran a query against the Objects table to find the FIM builtin administrator account '7FB2B853-24F0-4498-9534-4E10589723C4'


For my instance it returned a value of 2340.

I issued one more query to insert my SID into the UserSecurityIdentifiers table.



The value successfully added.  I opened the portal and was treated to the normal administrative view!

I still don’t know what I changed to cause the SID to be deleted, but now at least I know how to get back in without a backup.  Perhaps some more testing in the future will reveal a repeatable pattern that causes the lockout.

Monday, July 18, 2011

Access Denied when backing up WINS on Windows Server 2008

 

As part of switching our services over to Windows Server 2008, we began migrating WINS and our management scripts for WINS.  Our existing Windows Server 2003 based backup script did not work.  It was returning an access denied error.

Firing up Process Monitor produced this report.

WINS_ProcMon

I couldn’t image how a windows service didn’t have permission to write to the filesystem.

Looking at the properties of the Process Monitor event shows this detail.



The user wasn’t NT Authority\System like I had expected.  Instead it was NT Authority\Local Service.  A search for NT Authority\Local Service and WINS produced KB Article 943514.  The article only references moving the database from its default location, but it also applies to backing up the database to another folder.  The access denied error is resolved by issuing a command like

icacls d:\backupWINS /grant "NT SERVICE\WINS:(OI)(CI)F"

Obviously this grants the WINS service full control of the d:\backupWINS folder so that it now has permissions to create its backup files.  With that in place, no more errors were encountered.

Monday, March 21, 2011

PowerShell example for LdapSessionOptions.VerifyServerCertificate

 

I’ve started switching most of my management scripts over to PowerShell.  I previously had written a small C# command-line tool that would display the certificate expiration date of a Domain Controller’s LDAPS certificate.  This utility was based on Joe Kaplan’s sample.  As this utility was called as part of a much larger and more complex VBScript it only made sense to incorporate this functionality directly into PowerShell as well.  However, figuring out how to get PowerShell to deal with the VerifyServerCertificateCallback object was a more complex an undertaking than I had anticipated, and there were several times I almost gave up and kept the certificate date check as an external utility.  However, I did eventually figure it out and thought I’d share since there are no specific examples anywhere and few examples about PowerShell and callbacks in general.

Within the .NET Framework, System.DirectoryServices.Protocols provides comparatively raw access to the LDAP APIs.  In Joe’s example, the VerifyServerCertificate property is assigned to a new VerifyServerCertificateCallback object, which itself is a function that returns either True or False based on whatever logic one wants to employ.

Most of the examples I found centered on web-server SSL certificates and ServerCertificateValidationCallback and were based on either C# which has no issues with callbacks, like Joe’s, or PowerShell v1 which had to do lots of unnatural things to use the callback.

It turns out there are two important items to know about callbacks and PowerShell v2.  First, callbacks are implemented as a scriptblock.  Second, access to the callback parameters are provided via the args array.

Add-Type -AssemblyName System.DirectoryServices.Protocols
$LDAPId = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier(($DC + ":636"), $true, $false)
$LDAPConnection = New-Object System.DirectoryServices.Protocols.LdapConnection($LDAPId)
$LDAPConnection.Credential = New-Object System.Net.NetworkCredential("", "")
$LDAPConnection.AuthType = [system.directoryservices.protocols.authtype]::anonymous
$LDAPConnection.SessionOptions.SecureSocketLayer = $true
$LDAPConnection.SessionOptions.VerifyServerCertificate = {
    $MyCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $args[1]
    $DCCertDate = $MyCert.NotAfter
    $true
}
Try {
    $LDAPConnection.Bind()
} Catch {
    $DCCertDate = "No Certificate Found"
}

During execution, when the Bind method is called, the scriptblock attached to the VerifyServerCertificate property is called.  The LdapConnection parameter for the VerifyServerCertificateCallback delegate is contained in args[0] and the X509Certificate in args[1].  In this particular example I use the callback certificate to create a new certificate object, store the expiration date and return true, which allows the connection to succeed.

Tuesday, May 25, 2010

Execute PowerShell Scripts with a Restricted Execution Policy

 

By default, PowerShell will default to an execution policy of Restricted.  Hopefully this is not a surprise to anyone.  However, it is possible to execute scripts under this policy.  Simply start the shell using the ExecutionPolicy parameter.

For example, in our server loads, we need to prompt for an administrative password during the post-install process.  Previously, in W2K3, we were able to use the ScriptPW object to mask the input.  The object no longer exists in W2K8, so a different solution was needed.  Powershell provides the Read-Host cmdlet, which includes the AsSecureString parameter.  Using that cmdlet, I created a simple script that would prompt for the password.

I then needed to run the script automatically, but didn’t want to change the default execution policy.  To solve this, one can specify a one-time execution policy when starting the shell.

powershell -ExecutionPolicy RemoteSigned –File c:\postinstall\password.ps1

Monday, April 12, 2010

Happy 10th PromoDay <widgets>.com

 

My forest[1] celebrated the ten-year anniversary of the initial promotion today.  We had upgraded from an NT4 multi-master configuration, so this was a new promotion to become the empty root.  The account domains were slowly upgraded over the next year.  Technically, the domain services as a whole are more than 10 years old, but the official NT4 dates are lost to the sands of time.

c:\>adfind -config -f name="Enterprise Configuration" whencreated -alldc

AdFind V01.40.00cpp Joe Richards (joe@joeware.net) February 2009

Using server: xxxx.xxxx.com:389
Directory: Windows Server 2003
Base DN: CN=Configuration,DC=xxxx,DC=com

dn:CN=Enterprise Configuration,CN=Partitions,CN=Configuration,DC=xxxx,DC=com
>whenCreated: 2000/04/12-19:25:22 Eastern Daylight Time

1 Objects returned

We held a party for it today, and even got a “PromoDay” cake.

cake_edited

 

[1] Not technically “my forest”.  It does belong to the widget company for which I work, and I didn’t even work for them 10 years ago, and had nothing to do with its creation.  joe was responsible for most of it [2].  These days there are 5 EAs with operational responsibility, and me with engineering responsibility who can likely continue to call it “ours”.

[2] There are a lot of good stories about the early days of our AD.  This event brought out a few of them from ensuring joe didn’t promote the forest as joe.com to the buddy builds that were delivered almost daily as we pushed the scalability limits of Windows 2000 beyond what Microsoft had yet seen, but they’re joe’s to tell, not mine.  Go read about them if you want.