Replacing event receivers in SharePoint

I’m currently migrating a SharePoint 2007 to SharePoint 2013. For this particular environment, a custom solution was made which involves a number of event receivers. The customer wanted to retain this functionality, so I had to port this solution to SharePoint 2013. One problem though… the source code was not available. We had to revert to reverse engineering the solution using ILSpy to recreate the source code and build a new solution. We made sure that all feature ID’s were the same and that our namespaces and class names were also the same. After deploying and testing the solution, it worked.

During the migration, we attached the content database to the SharePoint 2013 web application and that’s when we noticed something.
When you add an event receiver to a SharePoint list, the “Assembly” property of the event receiver contains the assembly signature of the DLL which contains the event receiver class. When we attached the database, SharePoint complained it was missing an assembly. The assembly of the old solution. When we compared the assembly signature of the old solution with the signature of our new solution, we saw it had a different publickeytoken. We completely overlooked this. This was one of those “Doh!” moments.

It seems that it’s not that straightforward to change the publickeytoken. I found a way to extract this publickeytoken from a DLL and generate a strong name key (SNK) file.

sn.exe -e myassembly.dll mykey.snk

But this strong name key is missing one crucial piece of information. The private key. If you want to sign your solution with this strong name, you need to do this using delay signing. Your solution will build and the signature matches the one from the old assembly, but when you try to deploy it to SharePoint, it fails because it can’t add the assembly to the GAC due to the missing private key.

I figured that instead of looking for workarounds, the most easy way to solve this, is to replace the old event receivers with new ones which have the correct signature. This proved to be an easy solution. I created 2 scripts which helped me with this.

Get all event receivers with a specific signature

This scripts returns all event receivers which have a specific signature.

<#
.SYNOPSIS
    Get eventreceivers with a specific assembly signature

.DESCRIPTION
    Get eventreceivers with a specific assembly signature.

.NOTES
    File Name: Get-EventReceiver.ps1
    Author   : Bart Kuppens
    Version  : 1.0
	
.PARAMETER Signature
    Specifies the assembly signature of the eventreceivers to be returned.
	
.EXAMPLE
    PS > .\Get-EventReceiver.ps1 -Signature "westeros.sharepoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d944c1e5ac03aeaa" 
                  | Export-CSV -Path "c:\temp\eventreceivers.csv" -Delimiter ";" -NoTypeInformation
#>
param(
    [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)]
    [string]$Signature
)

# Load the SharePoint PowerShell snapin if needed
if ((Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null)
{
    Add-PSSnapin Microsoft.SharePoint.PowerShell
}

$sites = Get-SPSite -limit All
foreach ($site in $sites)
{
    $webs = $site.AllWebs
    foreach ($web in $webs)
    {
        $lists = $web.Lists | ? {$_.EventReceivers.Count -gt 0}
        foreach ($list in $lists)
        {
            foreach ($eventreceiver in $list.EventReceivers)
            {
                if ($eventreceiver.Assembly -eq $Signature)
                {
                    $receiver = [ordered]@{
                        "Web" = $web.Url
                        "List" = $list.Title
                        "ID" = $eventreceiver.Id
                        "Assembly" = $eventreceiver.Assembly
                        "Class" = $eventreceiver.Class
                        "Type" = $eventreceiver.Type
                        "Name" = $eventreceiver.Name
                        "SequenceNumber" = $eventreceiver.SequenceNumber
                        "Synchronization" = $eventreceiver.Synchronization
                    }
                    New-Object PSObject -Property $receiver
                }
            }
        }
        $web.Dispose()
    }
    $site.Dispose()
}

You can export this output to a CSV file, which can be used in the next script. All information which is needed to replace these eventreceivers is included in the output.

PS> .\Get-EventReceiver.ps1 -Signature "westeros.sharepoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d944c1e5ac03aeaa" | Export-CSV -Path "c:\temp\eventreceivers.csv" -Delimiter ";" -NoTypeInformation

Delete and recreate event receivers

Using the .CSV file which can be created from the previous script, the script below deletes the old eventreceivers and replaces them with new ones. It uses the information from the old eventreceivers which is included in the CSV and uses the signature which is passed in as a parameter, as the new assembly signature for the new event receivers.

<#
.SYNOPSIS
    Replace eventreceivers with an updated assembly signature

.DESCRIPTION
    Replace eventreceivers with an updated assembly signature.

.NOTES
    File Name: Replace-EventReceiver.ps1
    Author   : Bart Kuppens
    Version  : 1.0
	
.PARAMETER CSVFile
    Specifies the path and name of the CSV file with the eventreceivers. This CSV can be created from the output of the
    Get-EventReceiver.ps1 script.
    Layout:
        "Web";"List";"ID";"Assembly";"Class";"Type";"Name";"SequenceNumber";"Synchronization"
    Where:
        - Web             : Url of the Web
        - List            : Title of the List
        - ID              : ID of the eventreceiver
        - Class           : Class of the eventreceiver
        - Type            : Type of the eventreceiver
        - Name            : Name of the eventreceiver
        - SequenceNumber  : SequenceNumber of the eventreceiver
        - Synchronization : Synchronization of the eventreceiver 
	
.PARAMETER Delimiter
    Specifies the delimiter used in the CSV file.

.PARAMETER NewSignature
    Specifies the new assembly signature.

.EXAMPLE
    PS > .\Replace-EventReceiver.ps1 -CSVFile "c:\temp\eventreceivers.csv" -Delimiter ";" 
              -NewSignature "westeros.sharepoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d944c1e5ac03aeaa" 
		
#>
param(
    [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)]
    [string]$CSVFile,
    [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false)]
    [string]$Delimiter,
    [parameter(Position=2,Mandatory=$true,ValueFromPipeline=$false)]
    [string]$NewSignature
)

# Load the SharePoint PowerShell snapin if needed
if ((Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null)
{
    Add-PSSnapin Microsoft.SharePoint.PowerShell
}

# Check if file exists
if (!(Test-Path $CSVFile))
{
    Write-Host "File $CSVFile does not exist, halting execution!"
    break
}
$CSVData = Import-Csv $CSVFile -Delimiter $Delimiter

foreach ($CSVRow in $CSVData)
{
    $web = Get-SPWeb $CSVRow.Web
    if ($web -ne $null)
    {
        $list = $web.Lists[$CSVRow.List]
        if ($list -ne $null)
        {
            $list.EventReceivers[[Guid]$CSVRow.ID].Delete()
            $ev = $list.EventReceivers.Add()
            $ev.Assembly = $NewSignature
            $ev.Class = $CSVRow.Class
            $ev.Type = $CSVRow.Type
            $ev.Name = $CSVRow.Name
            $ev.SequenceNumber = $CSVRow.SequenceNumber
            $ev.Synchronization = $CSVRow.Synchronization
            $ev.Update()
        }
        else
        {
            Write-Host "List '$($CSVRow.List)' not found on $($CSVRow.Web)"
        }
        $web.Dispose()
    }
    else
    {
        Write-Host "Web with URL $($CSVRow.Web) not found."
    }
}

You can find these scripts in my PowerShell repository on GitHub.

By Bart

Bart is a certified SharePoint consultant / architect at CTG Belgium NV with a broad professional experience in IT, a background in software development with a specialisation in Microsoft products and technologies and a solid knowledge and experience in Microsoft SharePoint Products and Technologies. He started as a COBOL developer on a mainframe environment and grew into software development for Windows platforms. Participated in projects varying from migrations of existing applications to development of Web applications and Windows applications. Became fascinated by the SharePoint 2007 platform and strongly believed in the added business value of this platform. Is since then fully committed to SharePoint and focuses on SharePoint implementations, migrations, integrations, design and coaching. Stays on top of new developments within the SharePoint technology stack and related technologies.