MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Tuesday, April 1, 2008

Backup Site Collections

I wanted to do a presentation at the local SharePoint user group meeting recently and decided that I should show how to build a custom extension.  I decided that rather than use an existing one I would build a new command from scratch.  Problem was coming up with something that people could use.  I decided that the issue of the built-in backup command not handling IIS settings would be a good area to target.  I first created the command detailed here, gl-backupsites, but eventually I created a simpler command (gl-backup) which I'll discuss in a follow up post.

The gl-backupsites command basically takes the simpler built-in backup command and extends it so that you can also backup the IIS settings as well as multiple site collections without having to script each one individually.  The benefit of this is that you could use this command to backup all site collections on a regular basis without having track what site collections are created.  Note that this can also be used to backup the SSP and CA content databases which the disaster recovery option of the built-in backup command will not capture.

The complete code can be seen below:

   1:  /// <summary>
   2:  /// Runs the specified command.
   3:  /// </summary>
   4:  /// <param name="command">The command.</param>
   5:  /// <param name="keyValues">The key values.</param>
   6:  /// <param name="output">The output.</param>
   7:  /// <returns></returns>
   8:  public override int Run(string command, StringDictionary keyValues, out string output)
   9:  {
  10:      output = string.Empty;
  11:   
  12:      InitParameters(keyValues);
  13:   
  14:      string scope = Params["scope"].Value.ToLowerInvariant();
  15:      m_Overwrite = Params["overwrite"].UserTypedIn;
  16:      m_IncludeIis = Params["includeiis"].UserTypedIn;
  17:      m_Path = Path.Combine(Params["path"].Value, DateTime.Now.ToString("yyyyMMdd_"));
  18:   
  19:      int index = 0;
  20:      while (Directory.Exists(m_Path + index) && !m_Overwrite)
  21:          index++;
  22:   
  23:      m_Path += index;
  24:   
  25:      if (!Directory.Exists(m_Path))
  26:          Directory.CreateDirectory(m_Path);
  27:   
  28:      if (m_IncludeIis)
  29:      {
  30:          // Flush any in memory changes to the file system so we can capture them on an export.
  31:          //Utilities.RunCommand("cscript", Environment.SystemDirectory + "\\iiscnfg.vbs /save", false);
  32:   
  33:          using (DirectoryEntry de = new DirectoryEntry("IIS://localhost"))
  34:          {
  35:              Console.WriteLine("Flushing IIS metadata to disk....");
  36:              de.Invoke("SaveData", new object[0]);
  37:              Console.WriteLine("IIS metadata successfully flushed to disk.");
  38:          }
  39:      }
  40:   
  41:      SPEnumerator enumerator;
  42:      if (scope == "farm")
  43:      {
  44:          if (m_IncludeIis)
  45:          {
  46:              // Export the IIS settings.
  47:              string iisBakPath = Path.Combine(m_Path, "iis_full.bak");
  48:              if (m_Overwrite && File.Exists(iisBakPath))
  49:                  File.Delete(iisBakPath);
  50:              if (!m_Overwrite && File.Exists(iisBakPath))
  51:                  throw new SPException(
  52:                      string.Format(
  53:                          "The IIS backup file '{0}' already exists - specify '-overwrite' to replace the file.",
  54:                          iisBakPath));
  55:   
  56:              //Utilities.RunCommand("cscript", string.Format("{0}\\iiscnfg.vbs /export /f \"{1}\" /inherited /children /sp /lm", Environment.SystemDirectory, iisBakPath), false);
  57:              using (DirectoryEntry de = new DirectoryEntry("IIS://localhost"))
  58:              {
  59:                  Console.WriteLine("Exporting full IIS settings....");
  60:                  string decryptionPwd = string.Empty;
  61:                  de.Invoke("Export", new object[] {decryptionPwd, iisBakPath, "/lm", FLAG_EXPORT_INHERITED_SETTINGS});
  62:              }
  63:          }
  64:          enumerator = new SPEnumerator(SPFarm.Local);
  65:      }
  66:      else if (scope == "webapplication")
  67:      {
  68:          enumerator = new SPEnumerator(SPWebApplication.Lookup(new Uri(Params["url"].Value.TrimEnd('/'))));
  69:      }
  70:      else
  71:      {
  72:          // scope == "site"
  73:          using (SPSite site = new SPSite(Params["url"].Value.TrimEnd('/')))
  74:          {
  75:              BackupSite(site);
  76:          }
  77:          return 1;
  78:      }
  79:      // Listen for web application events so that we can export the settings for the specified application.
  80:      enumerator.SPWebApplicationEnumerated += new SPEnumerator.SPWebApplicationEnumeratedEventHandler(enumerator_SPWebApplicationEnumerated);
  81:   
  82:      enumerator.SPSiteEnumerated += new SPEnumerator.SPSiteEnumeratedEventHandler(enumerator_SPSiteEnumerated);
  83:      enumerator.Enumerate();
  84:   
  85:      return 1;
  86:  }
  87:   
  88:  /// <summary>
  89:  /// Validates the specified key values.
  90:  /// </summary>
  91:  /// <param name="keyValues">The key values.</param>
  92:  public override void Validate(StringDictionary keyValues)
  93:  {
  94:      Params["url"].IsRequired = (Params["scope"].Value.ToLowerInvariant() != "farm");
  95:   
  96:      base.Validate(keyValues);
  97:  }
  98:   
  99:  #endregion
 100:   
 101:  /// <summary>
 102:  /// Handles the SPWebApplicationEnumerated event of the enumerator control.
 103:  /// </summary>
 104:  /// <param name="sender">The source of the event.</param>
 105:  /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPWebApplicationEventArgs"/> instance containing the event data.</param>
 106:  private void enumerator_SPWebApplicationEnumerated(object sender, SPEnumerator.SPWebApplicationEventArgs e)
 107:  {
 108:      if (!m_IncludeIis)
 109:          return;
 110:   
 111:      foreach (SPIisSettings iis in e.WebApplication.IisSettings.Values)
 112:      {
 113:          string iisBakPath = Path.Combine(m_Path, string.Format("iis_w3svc_{0}.bak", iis.PreferredInstanceId));
 114:          if (m_Overwrite && File.Exists(iisBakPath))
 115:              File.Delete(iisBakPath);
 116:          if (!m_Overwrite && File.Exists(iisBakPath))
 117:              throw new SPException(string.Format("The IIS backup file '{0}' already exists - specify '-overwrite' to replace the file.", iisBakPath));
 118:   
 119:          //Utilities.RunCommand(
 120:          //    "cscript", 
 121:          //    string.Format("{0}\\iiscnfg.vbs /export /f \"{1}\" /inherited /children /sp /lm/w3svc/{2}", 
 122:          //        Environment.SystemDirectory, 
 123:          //        iisBakPath,
 124:          //        iis.PreferredInstanceId), 
 125:          //    false);
 126:   
 127:          using (DirectoryEntry de = new DirectoryEntry("IIS://localhost"))
 128:          {
 129:              Console.WriteLine("Exporting IIS settings for web application '{0}'....", iis.ServerComment);
 130:              string decryptionPwd = string.Empty;
 131:              string path = string.Format("/lm/w3svc/{0}", iis.PreferredInstanceId);
 132:              de.Invoke("Export", new object[] { decryptionPwd, iisBakPath, path, FLAG_EXPORT_INHERITED_SETTINGS });
 133:          }
 134:      }
 135:      
 136:  }
 137:   
 138:  /// <summary>
 139:  /// Handles the SPSiteEnumerated event of the enumerator control.
 140:  /// </summary>
 141:  /// <param name="sender">The source of the event.</param>
 142:  /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPSiteEventArgs"/> instance containing the event data.</param>
 143:  private void enumerator_SPSiteEnumerated(object sender, SPEnumerator.SPSiteEventArgs e)
 144:  {
 145:      BackupSite(e.Site);
 146:  }
 147:   
 148:  /// <summary>
 149:  /// Backups the site.
 150:  /// </summary>
 151:  /// <param name="site">The site.</param>
 152:  private void BackupSite(SPSite site)
 153:  {
 154:      string path = Path.Combine(m_Path, EncodePath(site.Url.ToString())) + ".bak";
 155:      Console.WriteLine("Backing up site '{0}' to '{1}'", site.Url, path);
 156:      bool writeLock = site.WriteLocked;
 157:      site.WriteLocked = true;
 158:      try
 159:      {
 160:          site.WebApplication.Sites.Backup(site.Url, path, m_Overwrite);
 161:      }
 162:      finally
 163:      {
 164:          site.WriteLocked = writeLock;
 165:      }
 166:  }
 167:   
 168:  /// <summary>
 169:  /// Encodes the path.
 170:  /// </summary>
 171:  /// <param name="path">The path.</param>
 172:  /// <returns></returns>
 173:  private static string EncodePath(string path)
 174:  {
 175:      Regex reg = new Regex("(?i:http://|https://)");
 176:      path = reg.Replace(path, "");
 177:      reg = new Regex("(?i: |\\.|:|/|%20|\\\\)");
 178:      return reg.Replace(path, "_");
 179:  }

The syntax of the command can be seen below:

C:\>stsadm -help gl-backupsites

stsadm -o gl-backupsites

Backup all sites within the specified scope.  If the scope is farm or webapplication then IIS will also be backed up.

Parameters:
        -path <path to backup directory (all backups will be placed in a folder beneath this directory)>
        [-scope <Farm | WebApplication | Site>]
        [-url <url of web application or site to backup (not required if the scope if farm)>]
        [-includeiis]
        [-overwrite]

Here's an example of how to backup all site collections within a given web application while also including the IIS settings:

stsadm -o gl-backupsites -url http://portal -scope WebApplication -includeiis -path c:\backups

7 comments:

Ab said...

Hi Gary,

Great stuff you're sharing here!

Unfortunately I get an error while making a backup with IIS (-includeiis). It stops after the messages:
Flushing IIS metadata to disk....
IIS metadata successfully flushed to disk.
Exporting IIS settings for web application 'SharePoint-Portal_XYZ'....
execption has been thrown by the target of an invocation

Then the backup stops. Without the -includeiis the backup of the db's works fine aswell.

Can you help me?

Thanks in advance

Gary Lapointe said...

Ab - I'm not really sure what the issue is - I'll need to add some more error handling to see what the real error is (the real error is inside an inner exception so I'll need to catch and rethrow) - unfortuntely my dev environment is in a bad state right now so I can't make any changes for a couple of days at least.

YaronB said...

Hi,

I am trying to use your backup sites script unfortuantley I do not know hoe to install it so I can run the command line. Can you please tell me what to do ?

Yaron

Gary Lapointe said...

You need to deploy the WSP file - use stsadm's addsolution and deploysolution commands.

Anonymous said...

Hi gary,

it would be nice if you could add exclusion handling (inpu file of sites list) for this command so that you can still backup sites smaller than 15 GB even if they are siting in a web app that contains bigger sites. the scope is good but the site scope use will be like maintaining the script manually and adding only the sites you want.

Anonymous said...

HI,

I can use use your backup sites script. But I can't find restore script, can you tell me how to restore "Backup Site Collections".

for example: restore C:\XXX\test.bak

Gary Lapointe said...

You use the stsadm restore command.