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.