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.
4 comments:
That's a great little line of code, saves IISRESET'ing all the time which takes AGES! ;-)
You are my hero...
We have been over this on a SharePoint 2010 solution. Everything was scripted with PowerShell, but when it came to creating our sitecollections based on our own template, we got the error.
We have even tried to run IISRESET but this didn't help anything.
When I used your code I got a null exception with the "ThreadContext", so I had some serious digging in the SharePoint 2010 internal API.
The unsafe code you did is the exact same, but the clearing of the SPRequestManager is different.
Type sprequestmanager = typeof(SPFarm).Assembly.GetType("Microsoft.SharePoint.SPRequestManager", true, true);
Type spthreadcontext = sprequestmanager.Assembly.GetType("Microsoft.SharePoint.Utilities.SPThreadContext");
MethodInfo setcontext = spthreadcontext.GetMethod("Set", BindingFlags.Static | BindingFlags.NonPublic);
Type[] genericArguments = new Type[] { sprequestmanager };
MethodInfo setcontextgeneric = setcontext.MakeGenericMethod(genericArguments);
// set hte current sprequest manager to null!
setcontextgeneric.Invoke(null, new object[] { null });
Now I have created a PowerShell SnapIn with a cmdlet called Reset-SPContext and running this is out script makes everything work, yeah!
Thanks again.
@Rasmus: You're very welcome!
I'm slightly disappointed by the fact that this is still required in SharePoint 2010, though. One would think that they had come up with a way for the object model to detect changes made by e.g. the timer process, and either reload its state data, or notify the user.
I'm all down with eventual consistency, but at least providing sane error messages would help.
I totally agree. You would think this was something Microsoft would have fixed
Post a Comment