Deploy Files to SharePoint Web Application Virtual Directories At Feature Activation

Original: 2009/5/6

Updated: 2009/12/1 – see bottom

Updated: 2009/12/31 – see bottom (Follow up post here)

Have you ever had a need to deploy some files to all of the virtual directories (every IIS site folder corresponding to a zone in the web app) for a SharePoint web application feature?  After searching the interwebs high and low I was unable to find any information relating to this topic, so this might very well be one of the first publicly available.  I want to say that getting this to work yesterday was one of the most fun development tasks I’ve done in awhile.  Hopefully this can help someone else who is struggling with the same task or at least give you some fun things to try out.

First the premise.  My client is hosting Reporting Services 2005 reports for a SharePoint custom application.  The custom application lives in the Layouts folder of the 12 Hive.  The reports will not be hosted on Reporting Services, but instead locally in the web application.  Since the ReportViewer web control that will be displaying the reports only knows the virtual directory root (c:inetpubwwwrootwssVirtualDirectories<port number of SharePoint zone>) we need a way to copy the files into that directory (and all other virtual directories for the web app meaning 3 zones in our environment) instead of residing in the 12 Hive.  As such we wanted a clean solution for deploying these reports to the virtual directory folder that had the least amount of human intervention (ex. a batch command that ran XCOPY to copy the files into hardcoded folders would require human intervention beyond deploying the WSP file).

As with all SharePoint development solutions, I (under the pain of death) recommend wrapping them up as features and files inside of a WSP file.  I don’t recommend building your WSP by hand as it is very tedious and there are some free tools that can help you immensely in this area.  Currently I use either Visual Studio extensions for Windows SharePoint Services 1.2 (VSeWSS) or WSPBuilder.  Each has it’s own set of pros and cons, but I’ll save that for another blog post.  On this project we’re using WSPBuilder as the lead developer began using it and I’ve been grandfathered into it.

So we have a WSPBuilder project with all of our custom application files laid out in the appropriate folders.  Our reports are placed into a subfolder of feature DeployCustomApplication.  During installation of the WSP solution these files are copied to the corresponding 12 Hive folder.  To get them from that location to the web app virtual directory we created a feature receiver.  Feature receivers allow custom code to be called upon feature install, activate, deactivate, and uninstall.  The process is carried out as follows:

  1. Get reference to target web application
  2. For Each loop to process each dictionary pair in the web application’s IISSettings property
  3. Delete report files if they already exist (1)
  4. Perform file copy from the 12 Hive location to the bin directory of the individual web site virtual directory folders

(1) = Delete report files first as they are subject to source control and will be marked read-only.  The copy method is unable to overwrite a read-only file even with the OverWrite boolean flag set to true.

Here is a screenshot of the report files in the WSP project (pixelation on files done with Paint.Net, excellent free program I use all the time for image modifications similar to Photo Shop).  For those with a super keen eye you might even be able to tell that I doctored the report file names as well, but that’ll be our little secret.


Below is a slightly modified version (removing application name, client code, variable names, and other extraneous items) of the feature receiver code.  As an added bonus, you’ll also see in the AddHttpHandlerVerb() method how another teammate developed a process to modify the web.config file of each virtual directory to allow the ReportViewer web control.  I wish I could claim that as well, but sadly it was done by the time I joined the team.  Here is a link to the below code: download code here.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.IO;
   5:  using System.Web.Hosting;
   6:  using Microsoft.SharePoint;
   7:  using Microsoft.SharePoint.Administration;
   8:  using Microsoft.SharePoint.Utilities;
   9:  using System.Web;
  10:  using System.Web.UI;
  11:  using System.Web.UI.WebControls;
  13:  namespace CustomApplication
  14:  {
  15:      class CustomApplication : SPFeatureReceiver
  16:      {
  18:          private SPWebApplication _webApp;
  19:          private String _Owner;
  21:          public override void FeatureActivated(SPFeatureReceiverProperties properties)
  22:          {
  23:              try
  24:              {
  25:          // get references to target web application and feature definition
  26:                  _webApp = (SPWebApplication)properties.Feature.Parent;
  27:                  _Owner = properties.Feature.DefinitionId.ToString();
  29:                  DeployReportFiles();
  31:          AddHttpHandlerVerb();
  32:              }
  33:              catch (Exception ex)
  34:              {
  35:                  string message = "Error occurred while activating CustomApplication feature";
  36:                  Logger.logException(ex, message, System.Diagnostics.EventLogEntryType.Error, "<GUID>");
  38:                  throw new SPException("Error during Activation.  See event log for further details");
  39:              }
  40:          }
  42:          public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
  43:          {
  44:              try
  45:              {
  46:          // get references to target web application
  47:                  _webApp = (SPWebApplication)properties.Feature.Parent;
  49:                  RemoveReportFiles();
  50:              }
  51:              catch (Exception ex)
  52:              {
  53:                  string message = "Error occurred while deactivating feature CustomApplication";
  54:                  Logger.logException(ex, message, System.Diagnostics.EventLogEntryType.Error, "<GUID>");
  56:                  throw new SPException("Error during Feature Deactivation. See event log for further details");
  57:              }
  58:          }
  60:          public override void FeatureInstalled(SPFeatureReceiverProperties properties)
  61:          {
  62:          }
  64:          public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
  65:          {
  66:          }
  68:          private void DeployReportFiles()
  69:          {
  70:              // first remove report files if they exist
  71:              RemoveReportFiles();
  73:          // loop through each IisSettings dictionary pair (zones [dafault, intranet, internet, etc.] configured for web application)
  74:              foreach (KeyValuePair<SPUrlZone, SPIisSettings> pair in _webApp.IisSettings)
  75:              {
  76:          // copy report from 12 Hive location to the IisSettings zone virtual directory
  77:                  File.Copy(SPUtility.GetGenericSetupPath(@"TEMPLATEFEATURESCustomApplicationReportsReport1.rdlc"), pair.Value.Path.FullName.ToString() + @"binReport1.rdlc", true);
  78:                  File.Copy(SPUtility.GetGenericSetupPath(@"TEMPLATEFEATURESCustomApplicationReportsReport2.rdlc"), pair.Value.Path.FullName.ToString() + @"binReport2.rdlc", true);
  79:              }
  80:          }
  82:          private void RemoveReportFiles()
  83:          {
  84:          // loop through each IisSettings dictionary pair (zones [dafault, intranet, internet, etc.] configured for web application)
  85:              foreach (KeyValuePair<SPUrlZone, SPIisSettings> pair in _webApp.IisSettings)
  86:              {
  87:                  // if Report1 report exists, delete
  88:                  if (File.Exists(pair.Value.Path.FullName.ToString() + @"binReport1.rdlc"))
  89:                      File.Delete(pair.Value.Path.FullName.ToString() + @"binReport1.rdlc");
  91:                  // if Report2 report exists, delete
  92:                  if (File.Exists(pair.Value.Path.FullName.ToString() + @"binReport2.rdlc"))
  93:                      File.Delete(pair.Value.Path.FullName.ToString() + @"binReport2.rdlc");
  94:              }
  95:          }
  97:          private void AddHttpHandlerVerb()
  98:          {
  99:          // get reference to web config modification object and set properties
 100:              SPWebConfigModification HttpHandlerMod = new SPWebConfigModification();
 101:              HttpHandlerMod.Owner = _Owner;
 102:              HttpHandlerMod.Path = "configuration/system.web/httpHandlers";
 103:              HttpHandlerMod.Name = "add verb HttpHandler ReportViewer";
 104:              HttpHandlerMod.Value = "<add verb="*" path="Reserved.ReportViewerWebControl.axd" type = "Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />";
 106:          // if web configuration modification exists, remove
 107:              if (_webApp.WebConfigModifications.Contains(HttpHandlerMod))
 108:              {
 109:                  _webApp.WebConfigModifications.Remove(HttpHandlerMod);
 110:              }
 112:          // apply web configuration modificatoin
 113:              _webApp.WebConfigModifications.Add(HttpHandlerMod);
 114:          }
 115:      }
 116:  }

What I like about this solution is that it was developed in a short amount of time (hour or two to work out kinks), it is easily repeatable, and can be applied to other needs besides just Reporting Services reports.  Please leave any feedback if you end up using this process, have suggestions to make it better, or have any other comments.  Thanks and enjoy.

<Update1 – 2009/12/1>

After 6 months using the below method to deploy files to our development environments, it wasn’t until just recently that I noticed a fatal flaw in the below design: files are only deployed on the server from which the feature activate command is processed.  In plain English, if you have 3 web front ends (WFEs) and you activate the feature on WFE1 then WFE1 will get the files, but WFE2 and WFE3 will not.  The solution to this problem was suggested by my new good friend Sean McDonough: deploy using a SharePoint timer job.  I’m working on ironing out some of the changes for this and will have a new post on the matter once that’s complete.  Look for a link to that post from here soon.


<Update2 – 2009/12/31>

Here is the link to the follow up post I mentioned above: Click here.  After doing a little research and trying out timer jobs I found that a timer job was not the best solution for my situation.  Instead I opted for a method of looping through the web front ends and doing a file copy directly to the servers.  That same mechanism can be wrapped around the logic in this post so that files are copied to all web application folders on each web front end.  Read more about it in that follow up post.


-Frog Out

12 thoughts on “Deploy Files to SharePoint Web Application Virtual Directories At Feature Activation

  1. Originally posted on: Shafaqat, Can you check your feature.xml (or equivalent file) where you specify the metadata for your feature? If you check within your <Feature>…</Feature> tags you should find a property for Scope. Based on your exception I’m guessing your feature is set (or defaulted to) a web level scope. Change Scope=”Web” to Scope=”WebApplication” and deploy again. Let me know if that solves your problem.


  2. Originally posted on:,Because my feature is deployed to my farm through a WSP, that means it will automatically attempt to be distributed to all WFEs. We have deployed to environments with multiple WFEs already and seen the files existing on all WFEs. Check out (about 3rd paragraph down) for more info about WSPs deploying to all WFEs.Granted there is a slim chance that the distribution to all WFEs may fail due to an unforeseen error, but logging into Central Administration and checking the deployment status of your WSP will point out which server has an error. A simple retract and re-deploy typically corrects the error so long as it was only a deployment issue and not something wrong with your WSP.Thanks for the question, let me know if you have further questions. Brian


  3. Originally posted on:, You are correct that a web application feature will not get activated automatically. I’m not aware of a way to automatically activate features at that level. I’m in the process of writing up a series of posts on exporting a site template into a feature and automatically activating features at the site or web level, but that’s the highest I’ve personally seen. Thanks for your feedback. Brian


  4. Originally posted on: FolksI had one issue with the solution pacakge. I created my master page as a solution per web application level. But the problem is …In “operations-Solution management” it is showing as mytest.wsp deployed to http://application:100 But other applications also showing this feature when I check in the site collection features page. When I activate the feature ..I could see that respective master page in the master page dropdown..and when select it and go to home page ..saying unexpected error. It shouldn’t show to all applications since I deployed to particular application. Did I do any mistake in my class or?? I just added empty class (intention is to get safe control tag in my manifest.xml and to apply web application level……and got after I add this simple class).namespace Test{ public class sampletest : MasterPage { }}Any help would be appreciated. Thanks in advance.


  5. Originally posted on:, If you are following this post above, note that master pages are not deployed to the file system (which this post describes.) Instead master pages are deployed to the master page gallery per site collection (or site.) Follow along the steps at the below link for adding your master page through a feature. Make sure it is scoped appropriately. Let me know if you have issues deploying.


  6. Originally posted on: I used your requirement is to update web.config file differently for NTLM and FBA.we have four WFEsI first copied the web.config files into fetures folder and then doing a file copy from features folder to NTLM virtual directory folder and FBA as well.but it gave me an error message saying :”Given Path’s format is not supported”thanks


  7. Originally posted on:, Funny you mention that, at my current client we have the exact same issue of needing separate settings in the web.config file for NTLM and FBA. We’re 99.1% of the way to that using the SharePoint APIs, but a few minor things that just can’t be done. Are you able to execute your code in debug mode? If so, put a break point around the trouble spot and take a look at the path that it’s trying to use. Sometimes you can get errors if the path contains a space, special character or something else unexpected. Also take a look at my follow up post here that adds some additional pieces I didn’t originally think of: me know if that helps or you have further issues.


  8. Originally posted on:,I’m not sure I understand your question. Can you be more specific?The purpose of this code is to give a sample of a feature receiver built using WSPBuilder. The feature receiver copies files out to the IIS website directories associated with our SharePoint web application on the web front end servers.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s