Sue Hernandez's SharePoint Blog

SharePoint and Related Stuff

Monthly Archives: January 2012

SharePoint 2007-2010 Web Part Migration Planning

Well, I know I need to post the rest of my “Gantt Chart” blogs, but let me side track for a minute…

We are migrating one of our clients from MOSS 2007 to SharePoint 2010 and we wanted to know what web parts are being used on what sites.  I found a multi-step process you’ll have to go through, and it will take a while, but you CAN get a 70%-90% list.

GET THE WEB PARTS PER WEB

The first step is to use the stsadm command “enumallwebs”.  This apparently has a parameter/switch called “includewebparts” which apparently was added during an Octbver 09 Cumulative Update.

stsadm -o enumallwebs -includewebparts > MyOutputFileName.txt

This will construct for you an XML file that looks approximately like below:

<Databases>
  <Database SiteCount="2" Name="your_sharepoint_content_database_name" DataSource="server">
    <Site Id="12345678-716d-44a3-a973-fffffffffff" OwnerLogin="domain\spadmin" InSiteMap="True">
      <Webs Count="2">
        <Web Id="12345678-17d9-4870-8d94-fffffffffff" Url="/sites/test1" LanguageId="1031"
             TemplateName="STS#0" TemplateId="1">
          <WebParts>
            <WebPart Id="e60f6c95-e86c-4717-2c0d-6d8563c9caf7" Count="1" Status="Missing" />
            <WebPart Id="293e8d0e-486f-e21e-40e3-75bfb77202de" Count="103" Status="Missing" />
            <WebPart Id="b9a7f972-708a-cd77-4ffd-a235dfed5c38" Count="1" Status="Missing" />
            <WebPart Id="2242cce6-491a-657a-c8ee-b10a2a993eda" Count="182" Status="Missing" />
          </WebParts>
        </Web>
        <Web Id="12345678-3bda-4109-aacd-fffffffffff" Url="/sites/test1/subweb1" LanguageId="1031"
             TemplateName="STS#1" TemplateId="1">
          <WebParts>
            <WebPart Id="ce9aa113-48cf-ddee-0c03-597445e5b7ab" Count="1" Status="Missing" />
            <WebPart Id="293e8d0e-486f-e21e-40e3-75bfb77202de" Count="9" Status="Missing" />
            <WebPart Id="2242cce6-491a-657a-c8ee-b10a2a993eda" Count="7" Status="Missing" />
          </WebParts>
        </Web>
      </Webs>
    </Site>
    <Site Id="12345678-730c-46fd-a114-fffffffffff" OwnerLogin="domain\spadmin" InSiteMap="True">
      <Webs Count="1">
        <Web Id="12345678-7cd6-447d-8107-fffffffffff" Url="/sites/test2" LanguageId="1031"
             TemplateName="STS#0" TemplateId="1">
          <WebParts>
            <WebPart Id="d55b3b6b-6281-707b-73d0-0c49581475ad" Count="1" Status="Missing" />
            <WebPart Id="293e8d0e-486f-e21e-40e3-75bfb77202de" Count="83" Status="Missing" />
            <WebPart Id="9f030319-fa14-b625-4892-89f6f9f9d58b" Count="1" Status="Missing" />
            <WebPart Id="c9b34b5d-bf06-dc91-d23e-94ecad31cd0a" Count="2" Status="Missing" />
            <WebPart Id="2242cce6-491a-657a-c8ee-b10a2a993eda" Count="83" Status="Missing" />
            <WebPart Id="669602d9-e116-ccb8-eea3-e37ad589b14b" Count="1" Status="Missing" />
            <WebPart Id="f5897322-ddd4-c990-d012-f9d4fe2180ad" Count="2" Status="Missing" />
          </WebParts>
        </Web>
      </Webs>
    </Site>
  </Database>
</Databases>

* You may have every web part in a “Missing” status – I think you need to run this command on a WFE that has ALL the features installed properly.  Please see one of the following posts for more information:

GET A LIST OF WEB PARTS AND THEIR ASSEMBLIES AND CLASSES

Well see that WebPart “ID” there?  That’s actually not an ID that you can use, even searching in the database, to find the name of the web part.  That is a made up GUID-that’s-not-a-GUID that just happens to “fit” inside a GUID.  More info in the next heading section.

For now, just go to your “Add a Web Part” page, or

https://yourserver.someurl.com/_layouts/newdwp.aspx

From here, you’re going to get a list which shows you 2 pieces of information you need: the full Class Name and the full Assembly Name. 

If you have Excel installed, you “should” be able to right-click inside the list and say “Export to Microsoft Excel”.  From Excel, go ahead and delete columns A and C (Blank and “File Name”).  Now we need to turn this file into an XML file so we can both crunch it through a program, as well as transform it using XSLT.  I personally had a hard time turning it into XML so what I did was I took this schema and saved it as .xml and opened it in Excel:

<WebPartTypeIDs>
   <WebPartTypeID>
      <AssemblyName>Assembly A</AssemblyName>
      <TypeName>Class A</TypeName>
      <GeneratedWebPartTypeID>GUID A</GeneratedWebPartTypeID>
   </WebPartTypeID>
   <WebPartTypeID>
      <AssemblyName>Assembly B</AssemblyName>
      <TypeName>Class B</TypeName>
      <GeneratedWebPartTypeID>GUID B</GeneratedWebPartTypeID>
   </WebPartTypeID>
</WebPartTypeIDs>

Then I copied and pasted everything from the Exported spreadsheet into this XML file.  I think you have to make sure that you have something in the GeneratedWebPartTypeID for at least one of the rows, in order to make sure it doesn’t get rid of that column when you save.

If you’re better at Excel (most will be) and you know an easier way, please let me know.

RUN THE OUTPUT THROUGH A PROGRAM

Next we have to get the generated ID.  Here’s the crazy part.  Here’s what Microsoft does:

  • Take the Assembly Name plus “|” plus the Class / Type Name and put it together in one string
  • Change the string to a Byte array
  • Run the Byte array through an MD5 Hash

So I wrote a little program to ingest in the xml file that we created through Excel.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.Xml;
using System.Xml.Xsl;
using System.IO;

namespace GetWebPartTypeIDs
{
 public partial class Form1 : Form
 {
  public Form1()
  {
   InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
   try
   {
    Cursor = Cursors.WaitCursor;
    XmlDocument xDoc = new XmlDocument();
    xDoc.Load(textBox1.Text);

    XmlNodeList nodes = xDoc.SelectNodes("//WebPartTypeID");
    foreach (XmlNode node in nodes)
    {
     string assemblyName = node.SelectSingleNode("AssemblyName").InnerText;
     string typeName = node.SelectSingleNode("TypeName").InnerText;

     string s = GenerateID(assemblyName, typeName);

     XmlNode idNode = node.SelectSingleNode("GeneratedWebPartTypeID");
     idNode.InnerText = s;
    }

    xDoc.Save(textBox1.Text);
   }
   catch (Exception ex)
   {
    richTextBox1.Text = ex.ToString();
   }
   finally
   {
    Cursor = Cursors.Default;
    richTextBox1.AppendText(Environment.NewLine + Environment.NewLine + "*************** DONE **************");
   }
  }

  private string GenerateID(string assemblyName, string typeName)
  {
   string s = assemblyName + "|" + typeName;
   byte[] bytes = Encoding.Unicode.GetBytes(s);
   byte[] b = new MD5CryptoServiceProvider().ComputeHash(bytes);
   return new Guid(b).ToString();
  }
 }
}

ADD THAT GENERATED XML OUTPUT TOGETHER WITH THE STSADM OUTPUT

So now, put the 2 XML’s together, with some made-up root, doesn’t matter what it is.

<DatabasesAndWebPartTypeIDs>
   <WebPartTypeIDs>
      ...
   </WebPartTypeIDs>
   <Databases>
      ...
   </Databases>
</DatabasesAndWebPartTypeIDs>

RUN IT THROUGH XSLT

I am going to include 2 XSLT’s here.  The first one uses XSLT 2.0 so you might need to download the Saxon Parser (http://saxon.sourceforge.net/) if you don’t have XML Spy or if your file is too big for XML Spy to handle, which is what happened to me (250 MB xml file).

Here’s one that outputs an HTML file.  The nice thing about it is it goes “both ways” – it shows on the top which web parts are not being used and it shows on the bottom which web parts you couldn’t ID.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
 <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
 <xsl:template match="/">
   <html>
    <body>
     <h1>By Web Part Name - Unused are highlighted in Red</h1>
     <xsl:for-each select="//WebPartTypeID">
      <xsl:sort select="AssemblyName" />
      <xsl:sort select="TypeName" />
      <xsl:variable name="generatedId" select="GeneratedWebPartTypeID" />
      <xsl:variable name="count" select="count(//Databases//Web[WebParts/WebPart/@Id = $generatedId])" />
      <font face="Courier New" style="font-size:12px">
       <xsl:if test="number($count) = 0">
        <xsl:attribute name="color" select="'Red'" />
       </xsl:if>
       <xsl:value-of select="substring-before(AssemblyName, ',')" /> | <xsl:value-of select="TypeName" />:
<xsl:value-of select="$count" />
      </font><br/>
     </xsl:for-each>
     <hr/>
     <h1>By Web Part Type Id - Those with under 15 entries show the webs they're in</h1>
     <xsl:for-each-group select="//WebPart" group-by="@Id">
      <xsl:sort select="current-grouping-key()" />
      <xsl:variable name="count" select="count(//Web[WebParts/WebPart/@Id = current-grouping-key()])" />
      <font face="Courier New" style="font-size:12px">
       <xsl:value-of select="current-grouping-key()" />: <xsl:value-of select="$count" /> --&gt;
<font color="Red"><b><xsl:value-of
select="substring-before(//WebPartTypeID/AssemblyName[../GeneratedWebPartTypeID = current-grouping-key()], ',')"
 /></b> | <xsl:value-of
select="//WebPartTypeID/TypeName[../GeneratedWebPartTypeID = current-grouping-key()]" /></font>
      </font>
      <br/>
      <xsl:if test="number($count) &lt; 15">
       <xsl:for-each select="//Web/@Url[../WebParts/WebPart/@Id = current-grouping-key()]">
        <font face="Courier New" style="font-size:12px">..........<xsl:value-of select="." /></font><br/>
       </xsl:for-each>
      </xsl:if>
     </xsl:for-each-group>
    </body>
   </html>
 </xsl:template>
</xsl:stylesheet> 

The next XSLT is 1.0 compliant.  This one makes a “flat” xml file that you can then pull into Microsoft Access and do Pivot stuff on it to find out what web templates are being used, etc.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
 <xsl:template match="/">
  <ReportRoot>
   <xsl:for-each select="//Database">
    <xsl:variable name="db" select="@Name" />
    <xsl:for-each select="Site">
     <xsl:variable name="site" select="@Id" />
     <xsl:for-each select="Webs/Web">
      <xsl:variable name="webID" select="@Id" />
      <xsl:variable name="webURL" select="@Url" />
      <xsl:variable name="webTemplate" select="@TemplateName" />
      <xsl:variable name="webTemplateID" select="@TemplateId" />
      <xsl:for-each select="WebParts/WebPart">
       <xsl:variable name="webPartTypeID" select="@Id" />
       <xsl:variable name="nameNode" select="//WebPartTypeID[GeneratedWebPartTypeID = $webPartTypeID]" />
       <xsl:variable name="assembly" select="$nameNode/AssemblyName" />
       <xsl:variable name="class" select="$nameNode/TypeName" />
       <xsl:element name="WebPartType">
        <xsl:element name="WebPartTypeID">
         <xsl:value-of select="$webPartTypeID" />
        </xsl:element>
        <xsl:element name="AssemblyName">
         <xsl:value-of select="substring-before($assembly, ',')" />
        </xsl:element>
        <xsl:element name="FullAssemblyName">
         <xsl:value-of select="$assembly" />
        </xsl:element>       
        <xsl:element name="ClassName">
         <xsl:value-of select="$class" />
        </xsl:element>
        <xsl:element name="Database">
         <xsl:value-of select="$db" />
        </xsl:element>
        <xsl:element name="SiteCollection">
         <xsl:value-of select="$site" />
        </xsl:element>
        <xsl:element name="WebID">
         <xsl:value-of select="$webID" />
        </xsl:element>
        <xsl:element name="WebURL">
         <xsl:value-of select="$webURL" />
        </xsl:element>
        <xsl:element name="WebTemplateName">
         <xsl:value-of select="$webTemplate" />
        </xsl:element>
        <xsl:element name="WebTemplateID">
         <xsl:value-of select="$webTemplateID" />
        </xsl:element>
        <xsl:element name="WebCount">
         <xsl:value-of select="@Count" />
        </xsl:element>
       </xsl:element>
      </xsl:for-each>
     </xsl:for-each>
    </xsl:for-each>
   </xsl:for-each>
  </ReportRoot>
 </xsl:template> 
</xsl:stylesheet>

~Sue

Advertisements