In Part 1, I talked about a SP2013 farm that wasn’t behaving as a result of a lengthy in-progress AD migration, and linked to an article that talked about a hack, and then ultimately a solution via a SharePoint setting if your farm was patched to a patch level sometime around October 2012 or later. On the 2013 farm that resolved the issue and all were happy.
In Part 2, We visit the same issue on a SharePoint 2007 farm, and use the articles “hack” to resolve the issue in the 2007 environment.
For reference, the article who’s approach was used is here:
http://blogs.technet.com/b/craigf/archive/2012/10/15/sharepoint-and-sid-history-not-playing-well-together.aspx
The 2007 farm, unlike the 2013 farm doesn’t have the option of using the SharePoint setting – it doesn’t exist in the version of SP2007 in use, and a shotgun upgrade of the SP2007 farm is not an option.
Time to re-visit the “Hack” portion of the above article…
The Problem:
Two domains, Users in Domain A, SharePoint in Domain B
The users are going to be migrated one day, from A to B.
The AD team has replicated all the users so that they now exist in BOTH domain A and B.
Along with this replication, an attribute called “SID History” was set in domain B for each user.
Because of this, here’s how things work in Domain B…
If a program (any program, not just SharePoint) asks Domain B to fetch a user ID from a SID, Domain B will look in it’s list of users/Sids.
In Ordinary times, when users existed in only one place, the domain B controller would try itself first, and not finding the ID, would then send off the request to Domain A.
In our case however, the ID’s exist in both places.
So an app requests the User ID from a given SID from the DC in Domain B. This time, Domain B finds that SID in SID history. “AH HA” the domain says to itself. “I don’t have this ID, but I can see right here, that ID was migrated to an ID I DO have. I’ll just give you the DomainB\Account” (Remember we want DomainA\Account)
While that would be very helpful if we were done with our migration, and really really helpful if the Domain A account was deleted and gone, in our case we had a little problem.
The users were still using accounts from Domain A, so we needed those to resolve.
In the article, Craig Forster figured out that by using the right tool to query AD, you could pre-populate the LSA cache with the “desired” user accounts from the right domain controller, eliminating the whole name lookup and SID history piece of the puzzle.
Craig’s article mentioned the approach he used, but not the scripts.
That’s where this blog post comes in…
I crafted a solution to do what Craig’s article described.
First things first, the prerequisites:
- PSGetSid.exe you can get this from Sysinternals
- You’ll need PowerShell on the WFE’s for your 2007 Farm (regular PowerShell, we’re not touching SharePoint so there’s no need to panic that SP2007 doesn’t support SharePoint)
- You’ll need the AD PowerShell module – this is a windows server
“feature”, found under “remote server tools” when you add a feature.
Ok now for an overview of the solution:
From Craig’s article, we know we need to query not just the domain, but we need to look for every specific user.
To make this practical, we want to query AD for the list of users, then use the PSGetSid.exe program to query for that user.
start-transcript D:\adhack\RunLog.txt
import-module ActiveDirectory
$PSGETSID = "D:\ADHACK\PSgetSid.exe"
$batchFileName = "D:\ADHACK\FixMyAD.bat"
# this is to start the file clean so it's not appending between executions.
"Rem Start of file" | out-file -filepath $BatchFilename -Encoding "ASCII"
#Note, there is a good chance the next two registry commands will fail, for example, if they already exist - these are easy enough to set manually so thats what you should do, but leave theme here for reference
#set the timeout - this should be a number greater than the time it takes to run this script, for example, if the script takes 15 min to run and you schedule it to run every 20 min, then you'd want this to be something like 40 minutes so if it fails once, you'd still have the values in cache.
new-itemproperty -path HKLM:\SYSTEM\CurrentControlSet\Control\LSA\ -name LsaLookupCacheRefreshTime -PropertyType dword -value 40 #time in Minutes
#set the LSA cache (Default of 128 is too small)
new-itemproperty -path HKLM:\SYSTEM\CurrentControlSet\Control\LSA\ -name LsaLookupCacheMaxSize -PropertyType dword -value 131072 #this is the decimal value
#remember, you're running this from a WFE that's joined to the DESTINATION Domain
#first OU Example - query an OU in DESTDOMAIN (This would imply you have read rights to that OU from the WFE)
$users = Get-ADUser -filter * -searchbase "OU=UserList,DC=DESTDOMAIN,DC=MYCOMPANY,DC=COM" | Select SamAccountName | Sort SamAccountName
foreach ($user in $users)
{
#Here, note we're getting the user ID in TARGETDOMAIN, but issuing the command to SOURCEDOMAIN
$act = "SOURCEDOMAIN\$($user.Samaccountname)"
write-host "Attempting to cache $act" -foregroundcolor yellow
& .\PSGetSid.exe $act
$line = "$PSGETSID $act"
$line | out-file -filepath $BatchFileName -Encoding "ASCII" -Append
write-host "Done with $act" -foregroundcolor blue
}
#Second OU EXAMPLE - querying the SOURCEDOMAIN when you've restricted access to the OU on the TARGETDOMAIN
#Need to specify the -server value name to query the SOURCEDOMAIN domain at server.
#Here what we're doing is going back to the original domain where the active user accounts exist - by active, I mean these are the ones they are logging in with each day.
#Doing this is similar, but note we need to specify the -Server parameter, and oddly, you don't actually specify a server name there, you specify the name of the Source Domain
#Also, as I wrote this, it occurred to me that it's quite possible that this query alone is doing the same thing as PSgetSid.exe, so maybe thats not needed in this case (Where the SOURCEDOMAIN is being queried) I'll have to test it one day...
$users = Get-ADUser -filter * -searchbase "OU=UserList,DC=SOURCEDOMAIN,DC=OLDCOMPANY,DC=COM" -Server SOURCEDOMAIN.OLDCOMPANY.COM | Select SamAccountName | Sort SamAccountName
foreach ($user in $users)
{
$act = "SOURCEDOMAIN\$($user.Samaccountname)"
write-host "Attempting to cache $act" -foregroundcolor yellow
& .\PSGetSid.exe $act
$line = "$PSGETSID $act"
$line | out-file -filepath $BatchFileName -Encoding "ASCII" -Append
write-host "Done with $act" -foregroundcolor blue
}
write-host "Done $($users.count) users attempted to cache" -foregroundcolor green
stop-transcript
Ok so that’s the script – put it in the same folder as the PSGetSid.exe file
Now you might be wondering, what’s that FixMyAD.bat file it’s creating?
Well, it’s not really needed, but here’s the thought behind it – if you look at what it’s doing, FixMyAd.bat is just the PSGetSid.exe command repeated a bunch of times for each user in your environment. For a while, I was having trouble getting PSgetSid.exe to run when shelled out from Powershell, so I added that code with the thought that PS could generate the command and then a different job could run the batch file – it turned out to not be necessary, but I left it in there – it might be handy for some edge cases.
Normally, I’d schedule the PowerShell to run per the video Here: http://youtu.be/oJ4nktysxnE however, in this domain, it wasn’t having any of that – the security policy would not run unsigned scripts – I tried to change it, but that registry key was locked down. Darn!
Luckily I had another trick at my disposal: A small batch file launcher to launch PowerShell with the security disabled:
This is a non-powershell batch file that launches Powershell with an executionpolicy of bypass, then calls the PS1 file with the script and runs it.
pslauncher.bat:
powershell -Command "& {Set-ExecutionPolicy -scope currentuser -executionpolicy bypass}" -NoExit
powershell -Command "& {d:\ADHack\PreCache.ps1}" -NoExit
Note that I also had to set the scheduled task to “Run with highest privileges”
So thats the solution I ended up using to rig the LSA cache so it would display the correct user ID’s
Oh one more note- if you want to test if this is working, you might want to reset the LSA cache – as best I could tell, there is no way to do this, but I think you can set the two registry keys to zero, do a test to see what an uncached response is, then set the registry back and test again. No reboot was needed (the keys are documented at the top of the PowerShell script.)
Also, please accept my apologies if this whole thing doesn’t make sense etc… Craig did a great job explaining in his original article, so I didn’t want to rehash all that here, but did want to capture the solution I used since his didn’t show that.