SharePoint Automation Gary Lapointe – Founding Partner, Aptillon, Inc.

1Apr/089

Include IIS with Disaster Recovery Backup

I mentioned in my previous post that I was doing a presentation at the local SharePoint user group meeting and wanted to create a simple command to demonstrate what is involved with creating custom stsadm extensions.  The first command I created (gl-backupsites) turned out to be too complex for demonstration purposes so I took another approach and decided to show how to simply wrap an existing command in order to extend it.  What I chose to do was to wrap the existing out-of-the-box backup command and simply add an "-includeiis" parameter.  I called my new command gl-backup.

The code is pretty simple.  I basically just reproducing the parameters from the built-in command which I just grabbed with the help of Reflector.  I then just loop through the parameters in order to build the call to the built-in command.  Once the built-in command is finished running I grab the generated XML index file and locate the folder in which the backup was stored.  I then use this folder to store the IIS settings:

   1:  public class Backup2 : SPOperation
   2:  {
   3:      private const int FLAG_EXPORT_INHERITED_SETTINGS = 1;
   4:   
   5:      /// <summary>
   6:      /// Initializes a new instance of the <see cref="BackupSites"/> class.
   7:      /// </summary>
   8:      public Backup2()
   9:      {
  10:          SPParamCollection parameters = new SPParamCollection();
  11:   
  12:          parameters.Add(new SPParam("directory", "dir", false, null, null));
  13:          parameters.Add(new SPParam("backupmethod", "method", false, "None", null));
  14:          parameters.Add(new SPParam("item", "item", false, null, null));
  15:          parameters.Add(new SPParam("quiet", "quiet", false, null, null));
  16:          parameters.Add(new SPParam("percentage", "update", false, null, new SPIntRangeValidator(1, 100)));
  17:          parameters.Add(new SPParam("backupthreads", "threads", false, null, new SPIntRangeValidator(1, 10)));
  18:          parameters.Add(new SPParam("showtree", "tree", false, "False", null));
  19:          parameters.Add(new SPParam("url", "url", false, null, null));
  20:          parameters.Add(new SPParam("filename", "f", false, null, new SPValidator()));
  21:          parameters.Add(new SPParam("overwrite", "overwrite"));
  22:          parameters.Add(new SPParam("includeiis", "iis"));
  23:   
  24:          StringBuilder sb = new StringBuilder();
  25:          sb.Append("For site collection backup:");
  26:          sb.Append("\r\n    stsadm.exe -o backup ");
  27:          sb.Append("\r\n        -url <url>");
  28:          sb.Append("\r\n        -filename <filename>");
  29:          sb.Append("\r\n        [-overwrite]");
  30:          sb.Append("\r\n\r\nFor catastrophic backup:");
  31:          sb.Append("\r\n    stsadm.exe -o backup");
  32:          sb.Append("\r\n        -directory <UNC path>");
  33:          sb.Append("\r\n        -backupmethod <full | differential>");
  34:          sb.Append("\r\n        [-item <created path from tree>]");
  35:          sb.Append("\r\n        [-percentage <integer between 1 and 100>]");
  36:          sb.Append("\r\n        [-backupthreads <integer between 1 and 10>]");
  37:          sb.Append("\r\n        [-showtree]");
  38:          sb.Append("\r\n        [-quiet]");
  39:          sb.Append("\r\n        [-includeiis]");
  40:   
  41:          Init(parameters, sb.ToString());
  42:      }
  43:   
  44:      #region ISPStsadmCommand Members
  45:   
  46:      /// <summary>
  47:      /// Gets the help message.
  48:      /// </summary>
  49:      /// <param name="command">The command.</param>
  50:      /// <returns></returns>
  51:      public override string GetHelpMessage(string command)
  52:      {
  53:          return HelpMessage;
  54:      }
  55:   
  56:      /// <summary>
  57:      /// Runs the specified command.
  58:      /// </summary>
  59:      /// <param name="command">The command.</param>
  60:      /// <param name="keyValues">The key values.</param>
  61:      /// <param name="output">The output.</param>
  62:      /// <returns></returns>
  63:      public override int Run(string command, StringDictionary keyValues, out string output)
  64:      {
  65:          output = string.Empty;
  66:   
  67:          InitParameters(keyValues);
  68:   
  69:          string args = "-o backup";
  70:          foreach (string key in keyValues.Keys)
  71:          {
  72:              if (key.ToLowerInvariant() == "includeiis" || key.ToLowerInvariant() == "o")
  73:                  continue;
  74:   
  75:              args += string.Format(" -{0} \"{1}\"", key, keyValues[key]);
  76:          }
  77:   
  78:          if (Utilities.RunStsAdmOperation(args, false) != 0)
  79:              return 0;
  80:   
  81:   
  82:          if (Params["includeiis"].UserTypedIn && Params["directory"].UserTypedIn)
  83:          {
  84:              bool verbose = !Params["quiet"].UserTypedIn;
  85:              XmlDocument xmlToc = new XmlDocument();
  86:              xmlToc.Load(Path.Combine(Params["directory"].Value, "spbrtoc.xml"));
  87:              string iisBakPath = Path.Combine(xmlToc.DocumentElement.FirstChild.SelectSingleNode("SPBackupDirectory").InnerText, "iis.bak");
  88:              using (DirectoryEntry de = new DirectoryEntry("IIS://localhost"))
  89:              {
  90:                  if (verbose)
  91:                      Console.WriteLine("Flushing IIS metadata to disk....");
  92:                  de.Invoke("SaveData", new object[0]);
  93:                  if (verbose)
  94:                      Console.WriteLine("IIS metadata successfully flushed to disk.");
  95:              }
  96:              using (DirectoryEntry de = new DirectoryEntry("IIS://localhost"))
  97:              {
  98:                  if (verbose)
  99:                      Console.WriteLine("Exporting full IIS settings to {0}....", iisBakPath);
 100:                  string decryptionPwd = string.Empty;
 101:                  de.Invoke("Export", new object[] { decryptionPwd, iisBakPath, "/lm", FLAG_EXPORT_INHERITED_SETTINGS });
 102:              }
 103:          }
 104:   
 105:          return 1;
 106:      }
 107:   
 108:      #endregion
 109:   
 110:  }

The syntax of the command can be seen below:

C:\>stsadm -help gl-backup

stsadm -o gl-backup
For site collection backup:
    stsadm.exe -o gl-backup
        -url <url>
        -filename <filename>
        [-overwrite]

For catastrophic backup:
    stsadm.exe -o gl-backup
        -directory <UNC path>
        -backupmethod <full | differential>
        [-item <created path from tree>]
        [-percentage <integer between 1 and 100>]
        [-backupthreads <integer between 1 and 10>]
        [-showtree]
        [-quiet]
        [-includeiis]

Here's an example of how to backup a farm and include the IIS settings:

stsadm -o gl-backup -directory c:\backups -backupmethod full -includeiis

1Apr/087

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