As part of the upgrade I needed to be able to fix some web parts that did not migrate correctly (either during the upgrade itself or as a result of moving a web). Before I started messing around with the web parts though I wanted to be able to see what I was dealing with. So I decided to create this simple command called gl-enumpagewebparts that would enable me to list out in XML all the web parts that are on a given page (open or closed).

One thing that I found that was very interesting was that the web part manager export method treats V2 and V3 web parts very differently. But perhaps the biggest annoyance I found was that I couldn’t get the web part zone from the web part instance itself – I had to use the web part manager (SPLimitedWebPartManager) to get it (took me longer than I’d like to admit to figure that out). This command is really quite simple – it takes in an url to a web part page, loads up an SPLimitedWebPartManager (for both the shared and personal views) and then loops through the WebParts collection outputting the results as XML.

I created three separate methods to get the XML details – one is verbose and essentially just uses the built in Export() method to get the XML (you can get these results via the -verbose parameter), another is a bit simpler and is constructed by hand (this is the default) and a third is actually for use by another command that I created which I’ll be documenting soon. The core code is shown 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    XmlDocument xmlDoc = new XmlDocument();
17    xmlDoc.AppendChild(xmlDoc.CreateElement("WebParts"));
18    xmlDoc.DocumentElement.SetAttribute("page", url);
19
20    using (SPSite site = new SPSite(url))
21    using (SPWeb web = site.OpenWeb()) // The url contains a filename so AllWebs[] will not work unless we want to try and parse which we don't
22    {
23        XmlElement shared = xmlDoc.CreateElement("Shared");
24        xmlDoc.DocumentElement.AppendChild(shared);
25
26        SPLimitedWebPartManager webPartMngr = web.GetLimitedWebPartManager(url, PersonalizationScope.Shared);
27
28        string tempXml = string.Empty;
29        foreach (WebPart wp in webPartMngr.WebParts)
30        {
31            if (Params["verbose"].UserTypedIn)
32                tempXml += GetWebPartDetails(wp, webPartMngr);
33            else
34                tempXml += GetWebPartDetailsSimple(wp, webPartMngr);
35        }
36        shared.InnerXml = tempXml;
37
38        XmlElement user = xmlDoc.CreateElement("User");
39        xmlDoc.DocumentElement.AppendChild(user);
40
41        webPartMngr = web.GetLimitedWebPartManager(url, PersonalizationScope.User);
42        tempXml = string.Empty;
43        foreach (WebPart wp in webPartMngr.WebParts)
44        {
45            if (Params["verbose"].UserTypedIn)
46                tempXml += GetWebPartDetails(wp, webPartMngr);
47            else
48                tempXml += GetWebPartDetailsSimple(wp, webPartMngr);
49        }
50        user.InnerXml = tempXml;
51
52    }
53
54    output += Utilities.GetFormattedXml(xmlDoc);
55
56    return 1;
57}
58 
59#endregion
60 
61/// <summary>
62/// Gets the web part details.
63/// </summary>
64/// <param name="wp">The web part.</param>
65/// <param name="manager">The web part manager.</param>
66/// <returns></returns>
67internal static string GetWebPartDetails(WebPart wp, SPLimitedWebPartManager manager)
68{
69    StringBuilder sb = new StringBuilder();
70
71    XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
72    xmlWriter.Formatting = Formatting.Indented;
73    manager.ExportWebPart(wp, xmlWriter);
74    xmlWriter.Flush();
75
76    XmlDocument xmlDoc = new XmlDocument();
77    xmlDoc.LoadXml(sb.ToString());
78
79    XmlElement elem = xmlDoc.DocumentElement;
80    if (xmlDoc.DocumentElement.Name == "webParts")
81    {
82        elem = (XmlElement)xmlDoc.DocumentElement.ChildNodes[0];
83
84        // We've found a v3 web part but the export method does not export what the zone ID is so we
85        // have to manually add that in.  Unfortunately the Zone property is always null because we are
86        // using a SPLimitedWebPartManager so we have to use the helper method GetZoneID to set the zone ID.
87        XmlElement property = xmlDoc.CreateElement("property");
88        property.SetAttribute("name", "ZoneID");
89        property.SetAttribute("type", "string");
90        property.InnerText = manager.GetZoneID(wp);
91        elem.ChildNodes[1].ChildNodes[0].AppendChild(property);
92    }
93
94    return elem.OuterXml.Replace(" xmlns=\"\"", ""); // Just some minor cleanup to deal with erroneous namespace tags added due to the zoneID being added manually.
95}

The syntax of the command I created can be seen below.

C:\>stsadm -help gl-enumpagewebparts

stsadm -o gl-enumpagewebparts

Lists all the web parts that have been added to the specified page.

Parameters:
        -url <web part page URL>
        [-verbose]

Here’s an example of how to list all the web parts on a given page and dump to a text file:

stsadm -o gl-enumpagewebparts -url "http://intranet/hr/pages/default.aspx" -verbose > webparts.xml