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