Home > SharePoint > How To: Replace all instances of a SharePoint Web Part

How To: Replace all instances of a SharePoint Web Part


I recently had the task of automating the replacement of all instances of one web part with another new web part throughout all site collections in SharePoint. There may have been thousands of instances of the web part in hundreds of thousands of site collections spread over more than 100 content databases, and all needed to be replaced with the new web part in exactly the same places where the original web parts were placed.

Here I will describe the approach used to replace the web part, explaining the alternative options that were considered and the reasons why the final solution was used.

A web part can be easily replaced with another if the new web part has exactly the same footprint as the original web part, i.e. has the same namespace and class name, the same properties (or any new properties have valid default values), the same assembly filename, the same DWP file, and the assembly has been signed with the same Strong Name Key as the original web part. If the only differences are within the code inside the class, e.g. a bug fix to an existing web part, then the assembly can simply be replaced in the Global Assembly Cache (GAC) on each front-end SharePoint server, then following an IISReset, the new web part will automatically take place of the old web part in all instances. However if the new web part does not match in all of these cases, we cannot use this approach as the footprint is different and SharePoint will consider it a different web part.

Web parts may be placed on pages either in a Shared View, or a Personal View, which are described below:

  • Shared View: Placing a web part on the Shared View makes the web part visible to all users who have access to the page who do not subsequently close the web part in their Personal View.
  • Personal View: The Personal View allows web parts to be added which will be visible only to the user who placed them, and allows web parts on the Shared View to be personalized or closed (hidden). Personal Views of a page cannot be accessed by any other user.

The usual first thoughts on how this sort of thing may be done typically involves programmatically replacing instances of one web part with another using the SharePoint API. This however has significant limitations. Apart from being an extremely time consuming and resource intensive operation on a large installation, to traverse all SharePoint webs and pages looking for instances of the old web part, then delete them and add the new one in their place, with the risk of missing some of the web parts, this would not be at all feasible with the Personal View. To programmatically replace a web part in all Personal Views of just a single web page would require individually impersonating each of the users who may have personalized the view and then looking for the old web part to see if it exists and then replace it. This is not at all feasible. Therefore another approach is required.

Investigating the SharePoint Content Database revealed two tables that are relevant to web parts:

  • WebParts
  • Personalization

Every instance of a web part placed onto any page has a corresponding record in the WebParts table. This contains details about the location of the web part (site, page, web part zone etc), whether it was placed on the Personal View or Shared View, and all of the web part’s properties which are stored together in a binary field.

If a web part was placed on a Shared View, then any personalizations of this web part in the Personal View by any user will result in a related record added to the Personalization table, one per web part per user who personalized the web part, containing the updated web part properties that were changed in the user’s Personal View as well as the web part zone where it may have been moved to or whether it has been closed in the Personal View.

As it appears that placement of a web part on a page results in such easily identifiable records in the database, it seemed sensible to explore the options for replacing the web part using a SQL query. After-all there didn’t appear to be any other options.

The WebParts.tp_WebPartTypeId field relates the record with the actual web part code that must be executed on the page. However there is no simple lookup table of web part id to web part class. Further investigation revealed that the Web Part Type Id is actually a GUID representing an MD5 hash of the web part assembly’s full name (which includes the version, culture, and Public Key Token produced using the Strong Name Key file) combined with the web part’s namespace and class name. This information was found here. As this lookup information is not persisted to the database, it would seem that during start-up, SharePoint must inspect all of the web part assemblies to identify all web parts and works out their Web Part Type Ids to prepare an in-memory lookup so that the appropriate web part code can be located for each web part in the WebParts table.

If when producing the new web part any of the items that form the Web Part Type Id are different, the Web Part Type Id of the new web part will not match the existing one. If however all the web part properties remain the same, or any new properties have valid defaults defined, then replacing all instances of a web part in Shared Views and all Personal Views, including Personalizations of web parts in the Shared View can all be achieved by uninstalling the old web part, installing the new web part, then executing a simple SQL query against each SharePoint content database. This query will need to simply update all WebParts records which have the old web part’s Web Part Type Id, and set them with the new Web Part Type Id.

In my case I was able to create the new web part with the same namespace and class name, and kept the same properties (although they did nothing), so only the Strong Name Key was different. Therefore updating the SharePoint content databases to use the new Web Part Type Id in the WebParts.tp_WebPartTypeId field was a simple procedure.

A new web part that has different properties to the original means that the binary properties fields in the WebPart and Personalizations tables may no longer correlate with the new web part’s properties and will likely result in errors if these fields are not updated as well. However it would be difficult to know how to change these binary fields to match the new properties, let alone to carry over any personalizations of these properties.

An untried suggestion if it is necessary to work around differing properties is to deploy the new web part in all possible scenarios (place on a Shared View, another on a Personal View, and another on a Shared View and then Personalize it), then copy the binary properties database field values (WebParts.tp_AllUserProperties, WebParts.tp_PerUserProperties, Personalizations.tp_PerUserProperties) from the new web part in each of these scenarios and update all instances of the original web part by setting the new Web Part Type Id and also the appropriate binary properties field values for the matching scenario. This may be tricky and prone to errors, but it can be worked out based on the combinations of data in the Web Parts and Personalization table. It may also be necessary to check whether the WebParts.tp_Cache field is used by the old and new web parts, and determine whether to update that in a similar manner too, as I am not sure yet what populates this field as so far I have always seen this set to Null. While it is undesirable to have to go to these lengths to replace one web part throwughout all sites with another, and is a little tricky, it does look feasible, but will require a lot more testing to ensure it is implemented correctly for all possible scenarios.

I have created a simple application to identify the Web Part Type Id using the information obtained in the forum post mentioned earlier. Alternatively you could simply add a new web part to a page and look for the new database records and get the Web Part Type Id from there, but I prefer to use both methods and make sure the values match to be confident.

Here is a method written in C# to determine the Web Part Type Id given the assembly file path, the namespace and class name:

private string GetWebPartTypeId(
string webPartAssemblyFile,
string webPartNamespace,
string webPartClassName)
{
// Prepare the web part type information
Assembly assembly = Assembly.LoadFile(webPartAssemblyFile);
string data = String.Format(
"{0}|{1}.{2}",
assembly.FullName,
webPartNamespace,
webPartClassName);

// Convert the web part info to bytes then work out the MD5 hash
byte[] dataBytes = Encoding.Unicode.GetBytes(data);
MD5 hashAlgo = new MD5CryptoServiceProvider();
byte[] hash = hashAlgo.ComputeHash(dataBytes);

// Convert the MD5 hash to a Guid
// This represents the WebPartTypeId
Guid hashGuid = new Guid(hash);
return hashGuid.ToString().ToUpper();
}

The following simple SQL script shows how easy it is to update the Web Part Type Id of all instances of an old web part to the new web part, where the @OldWebPartTypeId and @NewWebPartTypeId must be set to the appropriate Web Part Type Id values.

DECLARE @OldWebPartTypeId uniqueidentifier
DECLARE @NewWebPartTypeId uniqueidentifier

SET @OldWebPartTypeId = 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA'
SET @NewWebPartTypeId = 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB'

UPDATE [WebParts]
SET [tp_WebPartTypeId] = @NewWebPartTypeId
WHERE [tp_WebPartTypeId] = @OldWebPartTypeId

This script must be executed against each of the SharePoint Content Databases in the SharePoint farm.

Please note that any direct manipulation of the SharePoint databases is not supported by Microsoft, and can result in damaging the entire SharePoint system. Therefore if you do this, test it thoroughly, and you do so entirely at your own risk.

Advertisements
  1. TheFrogPrince
    November 16, 2015 at 01:55

    CREATE FUNCTION GetHash
    (
    — Add the parameters for the function here
    @Input varchar(max)
    )
    RETURNS varchar(max)
    AS
    BEGIN

    DECLARE @UnicodeString nvarchar(max)
    SET @UnicodeString = CAST( @Input as nvarchar(max))
    DECLARE @ByteArray varbinary(max)
    SET @ByteArray = CONVERT( varbinary(max), @UnicodeString, 0)
    DECLARE @Hashed varbinary(max)
    SET @Hashed = HASHBYTES( ‘MD5’, @ByteArray)
    DECLARE @RetVal varchar(max)
    SET @RetVal = CONVERT(VARCHAR(MAX), @Hashed, 2)
    SET @RetVal = SUBSTRING( @RetVal, 7,2) + SUBSTRING( @RetVal, 5,2) + SUBSTRING( @RetVal, 3,2) + SUBSTRING( @RetVal, 1,2) + ‘-‘ +
    SUBSTRING( @Retval, 11, 2) + SUBSTRING( @Retval, 9, 2) + ‘-‘ +
    SUBSTRING( @Retval, 15, 2) + SUBSTRING( @retVal, 13,2) + ‘-‘ +
    SUBSTRING( @RetVal, 17, 4) + ‘-‘ +
    SUBSTRING( @Retval, 21, 12)
    SET @Retval = lower(@Retval)

    — Return the result of the function
    RETURN @RetVal
    END
    GO

  2. TheFrogPrince
    November 16, 2015 at 01:58

    In our case, we had to restack namespaces for webparts we created, and needed a simple way to update all of the tp_class fields. After updating our tp_class field to the correct classname, we call this function with tp_assembly + ‘|’ + tp_class as the parameter, and stick the result in tp_webparttypeid.

    If someone knows how Microsoft provided to accomplish this task inside of managed code, do please post a comment here.

    CREATE FUNCTION GetHash
    (
    — Add the parameters for the function here
    @Input varchar(max)
    )
    RETURNS varchar(max)
    AS
    BEGIN

    DECLARE @UnicodeString nvarchar(max)
    SET @UnicodeString = CAST( @Input as nvarchar(max))
    DECLARE @ByteArray varbinary(max)
    SET @ByteArray = CONVERT( varbinary(max), @UnicodeString, 0)
    DECLARE @Hashed varbinary(max)
    SET @Hashed = HASHBYTES( ‘MD5’, @ByteArray)
    DECLARE @RetVal varchar(max)
    SET @RetVal = CONVERT(VARCHAR(MAX), @Hashed, 2)
    SET @RetVal = SUBSTRING( @RetVal, 7,2) + SUBSTRING( @RetVal, 5,2) + SUBSTRING( @RetVal, 3,2) + SUBSTRING( @RetVal, 1,2) + ‘-‘ +
    SUBSTRING( @Retval, 11, 2) + SUBSTRING( @Retval, 9, 2) + ‘-‘ +
    SUBSTRING( @Retval, 15, 2) + SUBSTRING( @retVal, 13,2) + ‘-‘ +
    SUBSTRING( @RetVal, 17, 4) + ‘-‘ +
    SUBSTRING( @Retval, 21, 12)
    SET @Retval = lower(@Retval)

    — Return the result of the function
    RETURN @RetVal
    END
    GO

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: