Thursday, November 25, 2010

Build and deploy Sharepoint solutions using bat functions

Today for solution administration in Sharepoint we can use great opportunities of PowerShell. However there is interesting feature available in simple cmd (or bat) files which can be used in every day development – bat functions. Of course it is not so powerful as PowerShell, but is it very simple and doesn’t require any additional components in the system. We used this approach in several projects in conjunction with WSPBuilder and it proofed its reliability and value. If you don’t familiar with this concept you can check this link for example: Routines functions methods in .bat.

Assume that you have several class library projects (e.g. Project1 and Project2) in Visual Studio which are built into separate wsp packages using WSPBuilder. Project1 doesn’t contain web application specific resources and should be deployed globally. Project2 in contrast contains web application specific resources and should be deployed into particular web application. You need to automate build process. In order to do that you can use the following cmd file which uses bat functions technique:

   1: @cd ..
   2:  
   3: @call :install Project1 global
   4: @call :install Project2
   5:  
   6: @pause
   7: @exit
   8:  
   9: rem Makes wsp package, add it to solution store, deploys it and waits some time in order to allow sharepoint successfully install wsp
  10: :install
  11: @cd %1
  12:  
  13: @call :create_wsp %1
  14:  
  15: @call :add_solution %1
  16:  
  17:  
  18: @if "%2" == "global" (
  19:     @call :deploy_solution_globally %1
  20: ) else (
  21:     @call :deploy_solution_web_specific %1
  22: )
  23:  
  24: @call :delay 5
  25:  
  26: @cd ..
  27: @exit /b
  28:  
  29:  
  30: rem Creates wsp based on current directory
  31: :create_wsp
  32: wspbuilder -BuildWSP true -ResetWebServer true -TraceLevel Warning
  33: @exit /b
  34:  
  35:  
  36: rem Adds wsp file in the current directory to solution store
  37: :add_solution
  38: stsadm -o addsolution -filename %1.wsp
  39: @exit /b
  40:  
  41:  
  42: rem Deploys wsp from current directory to specific web applications (i.e. not globally)
  43: :deploy_solution_web_specific
  44: stsadm -o deploysolution -local -allowgac -allcontenturls -name %1.wsp
  45: @exit /b
  46:  
  47:  
  48: rem Deploys wsp from current directory globally
  49: :deploy_solution_globally
  50: stsadm -o deploysolution -local -allowgac -name %1.wsp
  51: @exit /b
  52:  
  53:  
  54: rem Delays execution
  55: :delay
  56: @echo Off
  57: PING -n %1 127.0.0.1>nul
  58: @echo On
  59: @exit /b

I added comments to the script so it is more or less self descriptive. Note that every function begins with colon “:” and ends with “@exit /b” command which returns execution to the caller. So we call “install” function for Project1 and Project2. What “install” function does? First of all it creates wsp package using wspbuilder (see “create_wsp” function). After that it adds solution to the solution store (“add_solution” function) and deploys solution to the server. Note that for Project1 we specified additional param “global” which is used in the code in order to determine how solutions should be deployed – globally (“deploy_solution_globally” function) or web application specific (“deploy_solution_web_specific”).

After solution is deployed “install” function waits some time in order to give Sharepoint time to proceed the request. Note how “delay” function is implemented: it uses “-n” parameter of well known “ping” command in order to wait specified amount of seconds

That’s how this feature can be used in Sharepoint development to automate simple scenarios using common cmd files. Of course for more complicated things you should prefer PowerShell, however for many real world cases described technique can be useful as well.

Sunday, November 14, 2010

Retain object identity during export and import of subsites in Sharepoint into different site collection hierarchy location

As you know Sharepoint object model contains several classes which allow to perform export and import of sites. Most important of them are SPExport and SPImport. If you don’t familiar with content deployment API then before to read this article I highly recommend you to read series of articles of Stefan Goßner: Deep Dive into the SharePoint Content Deployment and Migration API.

Mentioned classes allow you to export content of single SPWeb as well as whole site collection and import it into another location (probably on another server). You can find list of another features of content deployment here. In this post I want to describe problem with retaining objects identities during import of subsites into different location rather they had in source site.

When site is exported we can choose from 2 alternatives for import:

  • import this site with retaining object identities;
  • import site without retaining object ids.

With first method all child objects of exported site like lists, doclibs, list items, etc. will have the same GUIDs and location after import as they had in source site. This method has many advantages as you don’t need to make extra work after import. E.g. you don’t have a headache with retargeting of content by query web parts (if you don’t familiar with this problem I recommend you to read this post of Gary Lapointe: Retarget Content Query Web Part). Also you won’t have problems with lookup fields retargeting, etc. But with advantages this method has limitation: you should import exported subsite into the same URL location on target site relative with site collection URL. I.e. if your site2 was located at the following URL http://example.com/sites/sitecollection/site1/site2, then it should be imported to the same location on target site collection related to site collection root site: http://example1.com/site1/site2, but it can be imported for example to http://example1.com/site2 (where http://example1.com is root site of site collection).

Second method can be used when you need to copy objects into another site collection hierarchy (exactly 2nd method is used in site manager copy operations and in stsadm import operation). If you will open stsadm utility in reflector and see SPImportOperation class you will see:

   1: public override void Run(StringDictionary keyValues)
   2: {
   3:     ...
   4:     SPImportSettings settings;
   5:     ...
   6:     settings.RetainObjectIdentity = false;
   7:     ...
   8: }
So it just sets RetainObjectIdentity property to false without any conditions. But in opposite to 1st method you will have all problems with retargeting which are avoided in 1st method.

It is not very good as we need to select a compromise when we use one of these approaches. But is there a way to use advantages of both methods, i.e. retain objects GUIDs and have possibility to import site into any site collection hierarchy? Well, there is such method, but I need to warn that approach described below is actually a workaround and it is not supported by MS. You should keep it in mind when you will make decision whether to use it or not.

Before to show exact workaround I would describe the reason of this issue. The root of the problem is in SPImport class which can’t properly import SPWebs which are childs of another SPWebs (i.e. not childs of site collection root site) with preserving objects identities. It occurs because during exporting of web site it becomes orphan as we don’t import its parent. One of they way to handle such situation is to re-parent exported web during import process using SPImport.Started event – see Deep Dive into the SharePoint Content Deployment and Migration API - Part 3. Unfortunately this method doesn’t work with RetainObjectIdentity = true. If you saw provided link you know that in order to identify all orphaned objects args.RootObjects collection is used (where args is of type SPDeploymentEventArgs and this is parameter of OnImportStarted delegate). Lets see how this collection is initialized in SPImport class via reflector:

   1: public override void Run()
   2: {
   3:     try
   4:     {
   5:         ...
   6:         SPImportObjectCollection rootObjects = this.DeserializeRootObjectMap();
   7:         this.OnStarted(new SPDeploymentEventArgs(SPDeploymentStatus.Started, base.ObjectsProcessed, base.ObjectsTotal, base.DataFileManager.DataFilePath, rootObjects));
   8:         ...
   9:     }
  10:     catch (Exception exception3)
  11:     {
  12:         ...
  13:     }
  14:     ...
  15: }

So RootObjects collection is result of DeserializeRootObjectMap() method. Lets see this method as well:

   1: private SPImportObjectCollection DeserializeRootObjectMap()
   2: {
   3:     SPImportObjectCollection objects = new SPImportObjectCollection();
   4:     FileInfo rootObjectMapFile = base.DataFileManager.RootObjectMapFile;
   5:     if (!rootObjectMapFile.Exists)
   6:     {
   7:         return objects;
   8:     }
   9:     if (this.Settings.RetainObjectIdentity)
  10:     {
  11:         return objects;
  12:     }
  13:     ...
  14: }

Quite impressive: if we use RetainObjectIdentity = true then empty collection is always returned! That's why we can’t re-parent orphaned objects using Started event of SPImport class.

I found the following way to fix it. When we exported site using SPExport class we have a bunch of xml files with objects definitions (assume that we didn’t use compressed export into cab files). And there is a lot of relative URL paths of exported site like “/site1/site2”. You need to replace all such strings with name of the target site. I.e. if you need to import site2 not under site1 on target site collection, but under root site of site collection, you need to replace all strings “/site1/site2” by “/site2” in all files which you get after export. Doing this we made a trick – we replaced real parent of site2 (/site1) with root site of site collection (/). After that you can use SPImport with RetainObjectIdentity = true and it will work like if you would export site2 from subsite of root site of site collection.

As I already mentioned this solution should be considered as workaround. There is no guarantee that it will work for all cases. However it helped me to solve real life issue on one of the projects – with it I was able to copy sites using SPExport and SPImport classes into any site collection hierarchy and avoid problems with retargeting of Sharepoint artifacts. I hope that it will be useful for you in real life Sharepoint development as well.

Tuesday, November 2, 2010

Upgrade single SPWeb from Sharepoint 2007 to 2010

In my previous post I showed several useful links to start with if you plan to perform upgrade from Sharepoint 2007 to 2010. There are 2 possible upgrade approaches mentioned in MS guide:

  • in-place upgrade;
  • database attach upgrade.

You can use them if you need to perform upgrade of whole farm or at least whole web application or site collection. But what if we need to upgrade single SPWeb or even single SPList and leave other sites on previous version? As you probably know the minimal scalability element which can be assigned to different content database in Sharepoint is site collection. I.e. during creation of site collection you can specify that this site collection should use another content database (this is possible only if you create site collection via stsadm or object model, but not via Central Administration). But you can’t do this for particular SPWeb.

As was said above, we don’t need to upgrade all farm or affect existing environment somehow. In such case you can use the following steps in order to achieve the goal:

  1. Configure new Sharepoint 2007 environment close to existing environment;
  2. Export necessary SPWeb from existing Sharepoint 2007 site collection;
  3. Import exported site (SPWeb) to new environment created on step 1 (you can use Sharepoint SUSHI or stsadm import/export commands for export and import);
  4. So on this step you have separate Sharepoint 2007 environment and necessary SPWeb imported in it. Now you can perform in-place upgrade or database attach without affecting existing environment. If you already have configured Sharepoint 2010 environment then probably database attach approach will be faster. If not - you can consider in-place upgrade.

Hope it will help someone in everyday Sharepoint development and administration.