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