Friday, April 27, 2012

One problem with site creation in Sharepoint with enabled RBS

Recently I faced with the following issue: when created site collection view PowerShell I got the following error:

New-SPSite : Provisioning did not succeed. Details: The site template was not provisioned successfully. Delete this site collection in Central Administration, and then create a new site collection. OriginalException: The URL '_catalogs/masterpage/VariationRootPageLayout.aspx' is invalid.  It may refer to a nonexistent file or folder, or refer to a valid file or folder that is not in the current Web.

Page layout VariationRootPageLayout.aspx existed and was correct on the file system (14/Template/Features/PublishingResources). I didn’t find useful comments in the web and started to investigate issue in event log and Sharepoint log. One error in event log, which occurred before error shown above, attracted my attention:

Exception: Microsoft.Data.SqlRemoteBlobs.BlobStoreException: There was a generic database error. For more information, see the included exception. ---> System.Data.SqlClient.SqlException: FILESTREAM feature doesn't have file system access enabled.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.Data.BlobStores.FilestreamBlobStore.FilestreamStoreLibraryBase.Initialize(ConfigItemList commonConfiguration, ConfigItemList coreConfiguration, ConfigItemList extendedConfiguration, BlobStoreCredentials[] credentials)
   --- End of inner exception stack trace ---
   at Microsoft.Data.BlobStores.FilestreamBlobStore.FilestreamStoreLibraryBase.Initialize(ConfigItemList commonConfiguration, ConfigItemList coreConfiguration, ConfigItemList extendedConfiguration, BlobStoreCredentials[] credentials)
   at Microsoft.Data.BlobStores.BlobStore.InitializeInternal(Request request)
BlobStoreException Code: OperationFailedAuthoritative

Some time ago I enabled RBS (Remote BLOB Storage) for the database server which runs Sharepoint databases. And error shown above was somehow related with it. Although it didn’t have direct relation with first error during creation of site collection, I decided to fix that error first.

In order to fix it first of all ensure that you went through all steps described in the following link: Install and configure RBS. When you will enable FILESTREAM in Sql Server Configuration manager, ensure that on FILESTREAM tab all checkboxes are set as shown on the following picture:

image

In my case 2 checkboxes were not selected and even if I performed all other actions which are described in the link above, it caused error during site collection creation. After I selected all checkboxes in this table, restarted Sql Server instance and tried to run script again, site collection was created successfully. May be this information will be useful for someone who will face with similar problem.

Sharepoint MVP 2012

Year ago I wrote first non-technical post in the blog about MVP award which I got from MS for 2011. I’m happy to announce that renewed award for 2012. First of all I would like to thank community for recognizing my efforts, because this work is done first of all for and with Sharepoint and .Net community. And of course thanks to MS which supports community activities. I would like to pay special attention on contribution to open source software. I like OSS, I see how it helps people to make better software and try to take part in it by managing my own open source projects and providing patches for other projects. Even if we work with Sharepoint and most of time we develop proprietary software in enterprise world, there is always possibility to help other people and share you knowledge. Whenever possible I try to do it and appreciate when other developers do the same, because believe that it may make our life easier and better.

In new year I will continue to share findings, tricks, reviews, workarounds, hacks, etc, etc. which will think interesting and reasonable to write about. Thanks to everyone who reads my blog. Let’s continue journey in Sharepoint and .Net worlds.

Saturday, April 21, 2012

Override SPContext.Current.Web and Site with sites opened with elevated privileges

If you work with Sharepoint you most probably use SPSecurity.RunWithElevatedPrivileges() method in order to perform actions under high privileged account (Sharepoint\System or account of app pool of your Sharepoint web application in IIS). Very often however inside your delegate which is passed to RunWithElevatedPrivileges() method you call Sharepoint API which may use SPContext.Current.Site or SPContext.Current.Web. These SPSite and SPWeb instances in most cases are not opened with elevated privileges and you will get famous Access denied exception. Is there any workaround for this problem? Yes it is and in this post I will show how to override SPContext.Current.Site and SPContext.Current.Web with elevated versions.

First of all let’s check implementations of SPContext.Current.Site and SPContext.Current.Web. SPContext.Current returns instance of SPContext class as you probably would expect, so let’s start with SPContext.Site:

   1: public SPSite Site
   2: {
   3:     get
   4:     {
   5:         return this.Web.Site;
   6:     }
   7: }

As you can see internally it uses SPContext.Web property, so we can concentrate only on that. Let’s check its implementation:

   1: public SPWeb Web
   2: {
   3:     get
   4:     {
   5:         if (this.m_web == null)
   6:         {
   7:             this.m_web = SPControl.GetContextWeb(this.m_context);
   8:         }
   9:         return this.m_web;
  10:     }
  11: }

It calls SPControl.GetContextWeb() which in turn calls SPWebEnsureSPControl() method:

   1: private static SPWeb SPWebEnsureSPControl(HttpContext context)
   2: {
   3:     SPWeb web = (SPWeb) context.Items["HttpHandlerSPWeb"];
   4:     if (web == null)
   5:     {
   6:         SPSite site;
   7:         if (context.User == null)
   8:         {
   9:             throw new InvalidOperationException();
  10:         }
  11:         if (SPSecurity.ImpersonatingSelf || (SPSecurity.UserToken != null))
  12:         {
  13:             throw new InvalidOperationException();
  14:         }
  15:         context.Items["HttpHandlerSPSite"] =
  16:             site = new SPSite(SPFarm.Local, SPAlternateUrl.ContextUri, true);
  17:         web = site.OpenWeb();
  18:         context.Items["HttpHandlerSPWeb"] = web;
  19:         site.InitUserToken(web.Request);
  20:         SPRequestModule.InitContextWeb(context, web);
  21:         if (SPContext.GetShouldInitThreadCultureWhenContextWebIsInited(context))
  22:         {
  23:             web.SetThreadCultureAfterInit();
  24:         }
  25:     }
  26:     return web;
  27: }

Now we have all information in order to override SPContext.Current.Web. We need to store elevated instance os SPWeb in HttpContext.Items[“HttpHandlerSPWeb”] and force Sharepoint to call SPWebEnsureSPControl() method. First of all we need to set private variable m_web in SPContext site to null. As shown in listing of SPContext.Web it will call SPControl.GetContextWeb() only when it is null. After that the only thing to do is to override web site in HttpContext.Items collection with elevated version. Here is the code which makes the trick:

   1: SPSecurity.RunWithElevatedPrivileges(
   2: () =>
   3: {
   4:     using (var elevatedSite = new SPSite(SPContext.Current.Site.ID,
   5: SPContext.Current.Site.Zone))
   6:     {
   7:         using (var elevatedWeb = elevatedSite.OpenWeb(SPContext.Current.Web.ID))
   8:         {
   9:             var origSite = HttpContext.Current.Items["HttpHandlerSPSite"] as SPSite;
  10:             var origWeb = HttpContext.Current.Items["HttpHandlerSPWeb"] as SPWeb;
  11:             var privatem_web = ReflectionHelper.GetPrivateField(SPContext.Current,
  12:                 "m_web") as SPWeb;
  13:  
  14:             try
  15:             {
  16:                 HttpContext.Current.Items["HttpHandlerSPSite"] = elevatedSite;
  17:                 HttpContext.Current.Items["HttpHandlerSPWeb"] = elevatedWeb;
  18:                 ReflectionHelper.SetPrivateField(SPContext.Current, "m_web", null);
  19:  
  20:                 // perform calls to Sharepoint APIs here which uses
  21:                 // SPContext.Current.Web and SPContext.Current.Site
  22:             }
  23:             finally
  24:             {
  25:                 HttpContext.Current.Items["HttpHandlerSPSite"] = origSite;
  26:                 HttpContext.Current.Items["HttpHandlerSPWeb"] = origWeb;
  27:                 ReflectionHelper.SetPrivateField(SPContext.Current,
  28:                     "m_web", privatem_web);
  29:             }
  30:         }
  31:     }
  32: });

On lines 9,10 we store current values from HttpContext.Items collection. On line 11 we also store current value in m_web private variable in order to restore it later (this is not necessary actually – see below). Then on lines 16,17 we override values in HttpContext.Items with elevated SPSite and SPWeb. On line 18 we set SPContext.m_web to null using ReflectionHelper class (see below). After that we can call Sharepoint API which uses SPContext.Current.Web and Site and if originally we got Access denied error, now it will work successfully. In finally block we restore original values in order to minimize affected surface of our workaround. We set private m_web to original value, but we can also set it to null here – Sharepoint will retrieve original value from HttpContext.Items[“HttpHandlerSPWeb”].

Here is the code of ReflectionHelper which is used in example above (if you read my blog you may find it in other posts, but I decided to add it here as well in order to have complete example in one place):

   1: public static class ReflectionHelper
   2: {
   3:     public static void SetPrivateField(object obj, string name,
   4:         object val)
   5:     {
   6:         SetPrivateField(obj, obj.GetType(), name, val);
   7:     }
   8:  
   9:     public static void SetPrivateField(object obj, Type type, string name,
  10:         object val)
  11:     {
  12:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
  13:             BindingFlags.Public | BindingFlags.NonPublic;
  14:         FieldInfo mi = type.FindMembers(MemberTypes.Field, bf,
  15:             Type.FilterName, name)[0] as FieldInfo;
  16:         mi.SetValue(obj, val);
  17:     }
  18:  
  19:     public static object GetPrivateField(object obj, string name)
  20:     {
  21:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
  22:             BindingFlags.Public | BindingFlags.NonPublic;
  23:         FieldInfo mi = obj.GetType().FindMembers(MemberTypes.Field, bf,
  24:             Type.FilterName, name)[0] as FieldInfo;
  25:         return mi.GetValue(obj);
  26:     }
  27: }

Of course technique shown in this article should be treated as hack and you should use it only if standard ways didn’t work for you. Also I can’t predict all side effects which may be caused by this trick, however as always it is nice to have plan B if there are no other ways to proceed.

Monday, April 16, 2012

Use MS practices enterprise library logger in Sharepoint timer jobs

If you work with server technologies and applications you know how important to have reliable logging system which will allow to monitor work and find problems if something went wrong. Sharepoint is not exception. It has flexible ULS (unified logging service) with many configuration options (I recommend to use convenient tool for reading Sharepoint logs: ULS Viewer. It allows to filter events of different types and displays critical errors with red color which will attract your attention faster).

Sometimes ULS is not enough however. You may need to use Windows event viewer as reserve logging system – e.g. write all errors and warnings both to ULS and to event log, while information messages can be written only to ULS. In event log errors are displayed more expressively so you will find it faster). In order to work with event viewer you may use logger from Microsoft Enterprise Library. It contains convenient wrappers and allows to write messages to the Windows event log like this:

   1: LogEntry entry = new LogEntry();
   2: entry.Severity = TraceEventType.Error;
   3: entry.Message = msg;
   4: Logger.Write(entry);

In order to use Enterprise library logger you need to configure it first. Configuration is added to the config file and looks like this (I divided some rows on several lines in order to keep it within width of my blog layout. In the real config you should remove line breaks):

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="loggingConfiguration"
   5: type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings,
   6: Microsoft.Practices.EnterpriseLibrary.Logging,
   7: Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
   8:   </configSections>
   9:   <loggingConfiguration name="Logging Application Block" tracingEnabled="true"
  10: defaultCategory="General" logWarningsWhenNoCategoriesMatch="true">
  11:     <listeners>
  12:       <add source="My source" formatter="Text Formatter" log="Application" machineName=""
  13: listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData,
  14: Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  15: traceOutputOptions="None" filter="All"
  16: type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener,
  17: Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  18: name="Formatted EventLog TraceListener" />
  19:     </listeners>
  20:     <formatters>
  21:       <add template="Timestamp: {timestamp}&#xD;&#xA;Message: {message}&#xD;&#xA;
  22: Category: {category}&#xD;&#xA;Priority: {priority}&#xD;&#xA;EventId: {eventid}&#xD;&#xA;
  23: Severity: {severity}&#xD;&#xA;Title:{title}&#xD;&#xA;Machine: {machine}&#xD;&#xA;
  24: Application Domain: {appDomain}&#xD;&#xA;Process Id: {processId}&#xD;&#xA;
  25: Process Name: {processName}&#xD;&#xA;Win32 Thread Id: {win32ThreadId}&#xD;&#xA;
  26: Thread Name: {threadName}&#xD;&#xA;Extended Properties: {dictionary({key} - {value}&#xD;&#xA;)}"
  27: type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter,
  28: Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  29: name="Text Formatter" />
  30:     </formatters>
  31:     <categorySources>
  32:       <add switchValue="All" name="General">
  33:         <listeners>
  34:           <add name="Formatted EventLog TraceListener" />
  35:         </listeners>
  36:       </add>
  37:     </categorySources>
  38:     <specialSources>
  39:       <allEvents switchValue="All" name="All Events" />
  40:       <notProcessed switchValue="All" name="Unprocessed Category" />
  41:       <errors switchValue="All" name="Logging Errors &amp; Warnings">
  42:         <listeners>
  43:           <add name="Formatted EventLog TraceListener" />
  44:         </listeners>
  45:       </errors>
  46:     </specialSources>
  47:   </loggingConfiguration>
  48: </configuration>

This configuration sections should be added to the web.config of the Sharepoint site. After that you will be able to write messages to the Windows event log from the context of the site (e.g. in codebehind of application layouts pages, user control or web part).

But what if you need to write messages to event log also from Sharepoint timer job? Is it possible to configure it by the same way? The answer is yes. As you probably know job is executed in the separate process (Sharepoint timer service). Executable owstimer.exe is located in 14/bin folder (12/bin in case of Sharepoint 2007). So in order to MS Enterprise library logger you have to create owstimer.exe.config file (or modify it if it is already exists) and add configuration to it like shown above. After that restart Sharepoint timer service from Administration > Services and run your job. You will see that it now writes messages to the Windows event log.