Forcing the SharePoint object model to reload the 12 hive
Or: A bit of insight into the inner workings of the SharePoint object model
This isn't necessarily something many have found themselves battling, but I most certainly did yesterday. As I was trying to deploy a feature containing a site definition, and subsequently create a site using the site definition, using my new SPDeploy application, it all abruptly came to a nasty halt as I added the site (using SPSiteCollection.Add()):
File or arguments not valid for site template 'SomeTemplate#0'
Having triple-checked the template, feature, solution and just about everything else (yes, SPDeploy also does the equivalent of stsadm -o execadmsvcjobs), it eventually dawned on me how SharePoint goes about things when you create a new site.
Just about any call in SharePoint will go through whatever object model class you're dealing with, such as SPSiteCollection, then to a SPRequestManager class, on to an SPRequest, SPRequestInternalClass and eventually into an arcane COM class (which appears to be part of the old SharePoint 1.0 structure, when COM, ISAPI and such was the shizznit). This COM class exposes hundreds of functions, ranging from list administration to tepmlate lookup.
The issue in my case was two-fold. First of all, any thread in an application which uses the SharePoint object model, will access a set of SPRequest objects (based on whether you need an authenticated or non-authenticated instance) from the SPRequestManager class. These are per-thread request objects, which execute on a first-come, first-served basis. In figuring this out, I began experimenting with releasing the SPRequests for the main thread in the SPDeploy tool - which would cause them to be reinitialized in a subsequent call. I verified that this would not hurt other stateful objects, such as already lookup'd SPWebApplication instances; as all functions in these classes will acquire their SPRequest instance from SPRequestManager. And as I said: the SPRequestManager would create a new instance if an old one wasn't found.
After typing up the reflection madness to clear the current thread's SPRequest objects, I gave the application another go. But still got the error - doh.
This confirmed my worst fears: the SPRequestInternalClass COM object, which is obviously native code, did its own caching. Pulling out Sysinternals' ProcMon and watching loads from the filesystem beginning with "web server extensions\12" backed this up further: web templates, and the rest of the hive, is only loaded once - on demand - then cached.
In realizing this, the solution became pretty clear, and given the nature of SPRequestManager; not too shabby at all. For tools which require adding stuff to the 12 hive (such as deploying features with web templates), and then *use* those templates without re-executing the tool: you have to recycle the SPRequests in SPRequestManager, and recycle the OWSSVR.dll COM library.
This *also* applies for PowerShell scenarios, where you during one session call into one of the SharePoint objects, such as e.g.:
([type]"Microsoft.SharePoint.Administration.SPWebApplication")::Lookup($uri);
Doing so will load the OWSSVR.dll into your current session, and if you happen to e.g. trigger a function from the object model which scans the 12 hive for tepmlates; any subsequent script based addition to this folder, and then attempted usage of the additions, will fail with the error noted above.
The workaround I apply in SPDeploy, which may look intrusive, but is completely harmless in the single-tool scenario, is:
[DllImport("kernel32.dll")]
private static extern IntPtr FreeLibrary(IntPtr library);
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpFileName);
public static void RecycleOwssvr()
{
PropertyInfo threadCtx = typeof (SPFarm).GetProperty("ThreadContext", BindingFlags.Static | BindingFlags.NonPublic);
var tbl = (Hashtable) threadCtx.GetValue(null, null);
PropertyInfo requestProp = typeof (SPFarm).GetProperty("RequestNoAuth", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
requestProp.GetValue(SPFarm.Local, null);
tbl.Remove(typeof (SPFarm).Assembly.GetType("Microsoft.SharePoint.SPRequestManager"));
IntPtr p = GetModuleHandle("OWSSVR.DLL");
FreeLibrary(p);
string stsadmPath = SPUtility.GetGenericSetupPath("ISAPI");
p = LoadLibrary(stsadmPath + @"\OWSSVR.DLL");
}
And that will work beautifully.
One note, though; the timer service has to be running for this recycling to work, otherwise some other internal component will freak out come second-go.
