MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Tuesday, October 2, 2007

Enumerate Page Web Parts

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>
   8: public 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>
  67: internal 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

5 comments:

srikanth said...

Hi gary,
your posts are great,and helped us in almost all the issues we faced during migration.
For a quite few days we are fighting with an issue on migrating mysites.When we migrate mysites the webparts on private page of sps2003 goes to MySide(old) page after migration.But our client want them to be on MyHome after migration.For this our approach is to export webparts that are on sps2003 private page and import the same on MyHome page after migration.
We were able to import webparts on MyHome using objectmodel.
But we could not export webparts on the private page of sps2003 using code.can you please help us in this regard.After trying for all these days i found you are the only hope.

Thanks
srikanth

srikanth said...

Hi gary,
your posts are great,and helped us in almost all the issues we faced during migration.
For a quite few days we are fighting with an issue on migrating mysites.When we migrate mysites the webparts on private page of sps2003 goes to MySide(old) page after migration.But our client want them to be on MyHome after migration.For this our approach is to export webparts that are on sps2003 private page and import the same on MyHome page after migration.
We were able to import webparts on MyHome using objectmodel.
But we could not export webparts on the private page of sps2003 using code.can you please help us in this regard.After trying for all these days i found you are the only hope.

Thanks
srikanth

Gary Lapointe said...

Srikanth - take a look at the gl-exportlistitem2 and gl-addlistitem commands - I've built in a lot of stuff to handle migrating web part pages. You may also have some luck with the exportlistitem and importlistitem commands which use Microsoft's deployment API (you may run into issues with these because you are reparenting the pages).

john said...

I'm trying to get a list of webparts on a moss2007 page via powershell -
My script *mostly* works, but on some pages it throws multiple of these errors:
"System.NullReferenceException: Object reference not set to an instance of an object.
at ar.a()"
when I call the getLimitedWebpartManager line
It does display the info for some of the webparts on the page, but doesn't for others.

Any idea why I get that error? or how to resolve?
I'm running powershell 1.0 at the server (ckbox removed from 'run with restricted access'), logged in as the farm administrator

I'm trying to find a way of finding every page where we have certain webparts in our farm.

Thanks

script below:
$web=get-web http://server/siteurl;
$pageurl="default.aspx";
$page=$web.GetFile($pageurl);
$webpartManager=$web.getLimitedWebPartManager($page.url,[System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
Write-host "Error occurs before here"
$webpartManager.webparts | select-object Title, ID

Gary Lapointe said...

Most likely the error you are getting is due to the lack of an HTTP Context. Many web parts require one.