One of the things I needed to do in order to get my Site Directory set up and working properly was to be able to modify the view that controls which columns appear when creating a new site collection. The way SharePoint determines which columns to use is via a special view that is created when you set up a site directory. The name of the view is “Site Creation Categories” and you can see it here: “http://[portal]/SiteDirectory/SitesList/Capture.aspx”. If you want to control which columns appear when creating a new site then simply edit this view and add or remove those columns. Of course, I’m writing a batch file to make all these modifications so I needed to be able to do this via STSADM.
As you might imagine there is no built-in command to edit views so I had to create my own which I called gl-updatelistview
. I started out pretty simple – just made it so that you could control the visibility of columns but then I decided that I’d try to add more so as to make it potentially more useful. In the end you can modify just about everything via this command that you could otherwise modify via the browser (there are some things I left out such as the Totals column settings). I thought about maybe taking it further and creating the ability to create a view as well but at present I don’t have that need so I only did the update (it wouldn’t be a stretch to create a createlistview
command if needed).
The piece of functionality that messed me up the most was the setting of the View Style. It’s easy enough to set the style but if you want to change the setting back to the “Default” setting there’s simply no way to do this via the API. What I ended up having to do was use reflection to get the internal XmlDocument
object where all the settings are stored and then manually remove the <ViewStyle />
node. Ugly I know but its the only way I could find to do it. The 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>
8public override int Run(string command, StringDictionary keyValues, out string output)
9{
10 output = string.Empty;
11
12 InitParameters(keyValues);
13
14 string url = Params["url"].Value;
15
16 using (SPSite site = new SPSite(url))
17 using (SPWeb web = site.OpenWeb())
18 {
19 SPList list = Utilities.GetListFromViewUrl(web, url);
20 if (list == null)
21 throw new Exception("List not found.");
22
23 SPView view = null;
24 foreach (SPView v in list.Views)
25 {
26 if (url.ToLower() == SPEncode.UrlDecodeAsUrl(web.Site.MakeFullUrl(v.ServerRelativeUrl)).ToLower())
27 {
28 view = v;
29 break;
30 }
31 }
32 if (view == null)
33 throw new Exception("View not found.");
34
35 if (Params["visiblefieldorder"].UserTypedIn)
36 {
37 view.ViewFields.DeleteAll();
38 foreach (string s in Params["visiblefieldorder"].Value.Split(','))
39 {
40 view.ViewFields.Add(s);
41 }
42 }
43
44 if (Params["viewname"].UserTypedIn)
45 view.Title = Params["viewname"].Value;
46
47 if (Params["mobileview"].UserTypedIn)
48 view.MobileView = bool.Parse(Params["mobileview"].Value);
49
50 if (Params["defaultmobileview"].UserTypedIn)
51 view.MobileDefaultView = bool.Parse(Params["defaultmobileview"].Value);
52
53 if (Params["rowlimit"].UserTypedIn)
54 view.RowLimit = uint.Parse(Params["rowlimit"].Value);
55
56 if (Params["paged"].UserTypedIn)
57 view.Paged = bool.Parse(Params["paged"].Value);
58
59 if (Params["scope"].UserTypedIn)
60 view.Scope = (SPViewScope)Enum.Parse(typeof(SPViewScope), Params["scope"].Value, true);
61
62 if (Params["style"].UserTypedIn)
63 {
64 string viewStyle = Params["style"].Value;
65 SetStyle(web, view, viewStyle);
66 }
67
68 if (Params["sort"].UserTypedIn)
69 {
70 string sortString = Params["sort"].Value;
71 SetSort(view, sortString);
72 }
73
74 if (Params["filter"].UserTypedIn)
75 {
76 string caml = Params["filter"].Value;
77 SetFilter(view, caml);
78 }
79
80 if (Params["group"].UserTypedIn)
81 {
82 string groupString = Params["group"].Value;
83 SetGroups(view, groupString);
84 }
85
86 bool? collapseGroups = null;
87 int? groupLimit = null;
88 if (Params["collapsegroups"].UserTypedIn)
89 collapseGroups = bool.Parse(Params["collapsegroups"].Value);
90 if (Params["grouplimit"].UserTypedIn)
91 groupLimit = int.Parse(Params["grouplimit"].Value);
92
93 SetGroupAttributes(view, collapseGroups, groupLimit);
94
95 if (Params["makedefault"].UserTypedIn)
96 view.DefaultView = bool.Parse(Params["makedefault"].Value);
97
98 if (Params["hidden"].UserTypedIn)
99 view.Hidden = bool.Parse(Params["hidden"].Value);
100
101
102 view.Update();
103 }
104
105 return 1;
106}
107
108/// <summary>
109/// Sets the filter.
110/// </summary>
111/// <param name="view">The view.</param>
112/// <param name="caml">The caml.</param>
113private static void SetFilter(SPView view, string caml)
114{
115 XmlDocument sortDoc = new XmlDocument();
116 sortDoc.LoadXml(string.Format("<Query>{0}</Query>", view.Query));
117
118 XmlElement where = (XmlElement)sortDoc.SelectSingleNode("//Where");
119 if (string.IsNullOrEmpty(caml))
120 {
121 if (where != null)
122 {
123 sortDoc.DocumentElement.RemoveChild(where);
124 view.Query = sortDoc.DocumentElement.InnerXml;
125 }
126 }
127 else
128 {
129 if (where != null)
130 sortDoc.DocumentElement.RemoveChild(where);
131
132 view.Query = sortDoc.DocumentElement.InnerXml + caml;
133 }
134}
135
136/// <summary>
137/// Sets the sort.
138/// </summary>
139/// <param name="view">The view.</param>
140/// <param name="sortString">The sort string.</param>
141private static void SetSort(SPView view, string sortString)
142{
143 XmlDocument sortDoc = new XmlDocument();
144 sortDoc.LoadXml(string.Format("<Query>{0}</Query>", view.Query));
145
146 XmlElement orderBy = (XmlElement)sortDoc.SelectSingleNode("//OrderBy");
147 if (string.IsNullOrEmpty(sortString))
148 {
149 if (orderBy != null)
150 sortDoc.DocumentElement.RemoveChild(orderBy);
151 }
152 else
153 {
154 if (orderBy != null)
155 sortDoc.DocumentElement.RemoveChild(orderBy);
156
157 orderBy = sortDoc.CreateElement("OrderBy");
158 sortDoc.DocumentElement.AppendChild(orderBy);
159
160 foreach (string s in sortString.Split(','))
161 {
162 string[] s2 = s.Split('=');
163 string fieldName = s2[0];
164 string order = s2[1];
165
166 SPField field = null;
167 try
168 {
169 field = view.ParentList.Fields.GetFieldByInternalName(fieldName);
170 }
171 catch (ArgumentException)
172 {
173 }
174
175 if (field == null)
176 throw new SPException(string.Format("The field '{0}' specified in the sort parameter was not found. Make sure the case is correct.", fieldName));
177
178 XmlElement fieldRef = sortDoc.CreateElement("FieldRef");
179 fieldRef.SetAttribute("Name", field.InternalName);
180 if (order.ToLowerInvariant() != "asc")
181 fieldRef.SetAttribute("Ascending", "FALSE");
182
183 orderBy.AppendChild(fieldRef);
184 }
185 }
186 view.Query = sortDoc.DocumentElement.InnerXml;
187}
188
189/// <summary>
190/// Sets the group attributes.
191/// </summary>
192/// <param name="view">The view.</param>
193/// <param name="collapseGroups">The collapse groups.</param>
194/// <param name="groupLimit">The group limit.</param>
195private static void SetGroupAttributes(SPView view, bool? collapseGroups, int? groupLimit)
196{
197 if (!collapseGroups.HasValue && !groupLimit.HasValue)
198 return;
199
200 XmlDocument groupDoc = new XmlDocument();
201 groupDoc.LoadXml(string.Format("<Query>{0}</Query>", view.Query));
202
203 XmlElement groupBy = (XmlElement)groupDoc.SelectSingleNode("//GroupBy");
204
205 if (groupBy == null)
206 throw new SPException("There are no group by columns set. Cannot set collapsegroups or grouplimit.");
207
208 if (collapseGroups.HasValue)
209 groupBy.SetAttribute("Collapse", collapseGroups.Value.ToString().ToUpper());
210 if (groupLimit.HasValue)
211 groupBy.SetAttribute("GroupLimit", groupLimit.Value.ToString());
212
213 view.Query = groupDoc.DocumentElement.InnerXml;
214}
215
216/// <summary>
217/// Sets the sort.
218/// </summary>
219/// <param name="view">The view.</param>
220/// <param name="groupString">The sort string.</param>
221private static void SetGroups(SPView view, string groupString)
222{
223 XmlDocument groupDoc = new XmlDocument();
224 groupDoc.LoadXml(string.Format("<Query>{0}</Query>", view.Query));
225
226 XmlElement groupBy = (XmlElement)groupDoc.SelectSingleNode("//GroupBy");
227 if (string.IsNullOrEmpty(groupString))
228 {
229 if (groupBy != null)
230 groupDoc.DocumentElement.RemoveChild(groupBy);
231 }
232 else
233 {
234 bool collapse = true;
235 int groupLimit = 100;
236
237 if (groupBy != null)
238 {
239 collapse = bool.Parse(groupBy.GetAttribute("Collapse"));
240 groupLimit = int.Parse(groupBy.GetAttribute("GroupLimit"));
241
242 groupDoc.DocumentElement.RemoveChild(groupBy);
243 }
244
245 groupBy = groupDoc.CreateElement("GroupBy");
246 groupDoc.DocumentElement.AppendChild(groupBy);
247
248 groupBy.SetAttribute("Collapse", collapse.ToString().ToUpper());
249 groupBy.SetAttribute("GroupLimit", groupLimit.ToString());
250
251 foreach (string s in groupString.Split(','))
252 {
253 string[] s2 = s.Split('=');
254 string fieldName = s2[0];
255 string order = s2[1];
256
257 SPField field = null;
258 try
259 {
260 field = view.ParentList.Fields.GetFieldByInternalName(fieldName);
261 }
262 catch (ArgumentException)
263 {
264 }
265
266 if (field == null)
267 throw new SPException(string.Format("The field '{0}' specified in the group parameter was not found. Make sure the case is correct.", fieldName));
268
269 XmlElement fieldRef = groupDoc.CreateElement("FieldRef");
270 fieldRef.SetAttribute("Name", field.InternalName);
271 if (order.ToLowerInvariant() != "asc")
272 fieldRef.SetAttribute("Ascending", "FALSE");
273
274 groupBy.AppendChild(fieldRef);
275 }
276 }
277 view.Query = groupDoc.DocumentElement.InnerXml;
278}
279
280/// <summary>
281/// Sets the view style.
282/// </summary>
283/// <param name="web">The web.</param>
284/// <param name="view">The view.</param>
285/// <param name="viewStyle">The view style.</param>
286private static void SetStyle(SPWeb web, SPView view, string viewStyle)
287{
288 bool styleFound = false;
289 foreach (SPViewStyle style in web.ViewStyles)
290 {
291 // Locate the style from the list of styles so that we can get the style ID.
292 if (!string.IsNullOrEmpty(style.Title) && style.Title.ToLowerInvariant() == viewStyle.ToLowerInvariant())
293 {
294 styleFound = true;
295 view.ApplyStyle(style);
296 }
297 }
298
299 // If we didn't find the style and the user specified "default" then we need to clear the existing style setting.
300 // Problem is that there's no way to do this using public API calls so we have to manipulate the XML directly.
301 if (!styleFound && viewStyle.ToLowerInvariant() == "default")
302 {
303 // XmlDocument m_xdView = view.m_xdView;
304 FieldInfo m_xdViewProp = view.GetType().GetField("m_xdView",
305 BindingFlags.NonPublic |
306 BindingFlags.Instance |
307 BindingFlags.InvokeMethod |
308 BindingFlags.GetField);
309 XmlDocument m_xdView = (XmlDocument)m_xdViewProp.GetValue(view);
310 XmlElement viewStyleNode = (XmlElement)m_xdView.SelectSingleNode("//ViewStyle");
311 if (viewStyleNode != null)
312 {
313 viewStyleNode.ParentNode.RemoveChild(viewStyleNode);
314
315 view.ViewHeader = null;
316 view.ViewBody = null;
317 view.ViewFooter = null;
318 view.ViewEmpty = null;
319 view.GroupByHeader = null;
320 view.GroupByFooter = null;
321 }
322 }
323 else if (!styleFound)
324 throw new SPException(string.Format("The View Style '{0}' was not found.", viewStyle));
325}
The syntax of the command can be seen below:
C:\>stsadm -help gl-updatelistview
stsadm -o gl-updatelistview
Updates a list view.
Parameters:
-url <list view URL>
[-visiblefieldorder <comma separated list of internal field names>]
[-viewname <view name>]
[-mobileview <true | false>]
[-defaultmobileview <true | false>]
[-rowlimit <number of items to display>]
[-paged <true | false>]
[-scope <default | recursive | recursiveall | filesonly>]
[-style <view style>]
[-sort <use the following format where the field name is the internal field name (leave blank to clear): "field1=asc|desc,field2=asc|desc">]
[-filter <CAML XML (leave blank to clear)>
[-group <use the following format where the field name is the internal field name (leave blank to clear): "field1=asc|desc,field2=asc|desc">]
[-collapsegroups <true | false>]
[-grouplimit <number of groups to display per page>]
[-makedefault <true | false>]
[-hidden <true | false>]
Here’s an example of how to update a view using all of the above parameters:
stsadm -o gl-updatelistview -url "http://intranet/SiteDirectory/SitesList/Capture.aspx" -visiblefieldorder "DivisionMulti,RegionMulti" -viewname "Site Creation Categories" -mobileview false -defaultmobileview false -rowlimit 100 -paged true -scope recursive -style "Basic Table" -sort "DivisionMulti=asc,RegionMulti=asc" -filter "<Where><Or><Eq><FieldRef Name='DivisionMulti' /><Value Type='Text'>Operations</Value></Eq><Eq><FieldRef Name='DivisionMulti' /><Value Type='Text'>Human Resources</Value></Eq></Or></Where>" -group "DivisionMulti=asc" -collapsegroups false -grouplimit 100 -makedefault true -hidden false
Note that when specifying the CAML for the filter make sure that you wrap it in quotes and use tick marks for quotes in the XML otherwise you’ll get command line errors.