Deleting Orphaned Event Receivers using PowerShell
While looking through the event logs at a client of mine the other day I came across an odd error that was occurring regularly. Apparently they had developed a Feature which contained an Event Receiver which was programmatically bound to a series of lists. As time went on their requirements changed so they deleted the event receiver class. Problem was that the binding to the lists still exists so every time an item was updated an error would be dumped to the event log about a missing type in the specified assembly.
Fortunately it’s pretty easy to fix this using some simple PowerShell:
$site = get-spsite -url http://portal foreach ($web in $site.AllWebs) { $lists = $web.Lists | where {$_.EventReceivers.Count -gt 0} foreach ($list in $lists) { $evts = $list.EventReceivers | where {$_.Class -eq MyCompany.SharePoint.MyFeature.EventReceivers.MyEventReceiver"} if ($evts.Count -gt 0) { foreach ($evt in $evts) { Write-Host("Deleting..." + $list.RootFolder.ServerRelativeUrl + ", " + $evt.Type) $evt.Delete() } } } }
The only thing that threw me off when I was putting this little snippet together was the fact that I couldn’t work with the collections directly due to errors about the collection being modified during the enumeration (so a “for” loop rather than a “foreach” loop would have worked just as easily as doing filtering as I am above).
The code above is great for when you know the class name of a specific event receiver that needs to be pulled. If you don’t know the name then you’ll have to add in some reflection to look for the assembly and type – just be careful as the assembly may or may not be in the GAC.
Listing Event Receivers using STSADM
This post wraps up my event receiver posts. I just finished documenting the gl-addeventreceiver and gl-deleteeventreceiver commands and this final command would be gl-enumeventreceivers.
This command is the simplest of the three as it's just looping through all the event receivers belonging to the specified target and dumping the results out as XML. I've tried to structure the XML so that it conforms to the CAML schema therefore allowing the results to be put into a Feature if desired.
1: /// <summary>
2: /// Gets the XML.
3: /// </summary>
4: /// <param name="url">The URL.</param>
5: /// <param name="contentTypeName">Name of the content type.</param>
6: /// <param name="target">The target.</param>
7: /// <returns></returns>
8: public static string GetXml(string url, string contentTypeName, TargetEnum target)
9: {
10: using (SPSite site = new SPSite(url))
11: using (SPWeb web = site.OpenWeb())
12: {
13: SPEventReceiverDefinitionCollection eventReceivers;
14: if (target == TargetEnum.List)
15: {
16: SPList list = Utilities.GetListFromViewUrl(web, url);
17:
18: if (list == null)
19: {
20: throw new Exception("List not found.");
21: }
22: eventReceivers = list.EventReceivers;
23: }
24: else if (target == TargetEnum.Site)
25: eventReceivers = web.EventReceivers;
26: else
27: {
28: SPContentType contentType = null;
29: try
30: {
31: contentType = web.AvailableContentTypes[contentTypeName];
32: }
33: catch (ArgumentException)
34: {
35: }
36: if (contentType == null)
37: throw new SPSyntaxException("The specified content type could not be found.");
38:
39: eventReceivers = contentType.EventReceivers;
40: }
41: StringBuilder sb = new StringBuilder();
42: XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
43: xmlWriter.Formatting = Formatting.Indented;
44:
45: xmlWriter.WriteStartElement("Receivers");
46: foreach (SPEventReceiverDefinition erd in eventReceivers)
47: {
48: xmlWriter.WriteStartElement("Receiver");
49:
50: xmlWriter.WriteElementString("Name", erd.Name);
51: xmlWriter.WriteElementString("Type", erd.Type.ToString());
52: xmlWriter.WriteElementString("Assembly", erd.Assembly);
53: xmlWriter.WriteElementString("Class", erd.Class);
54: xmlWriter.WriteElementString("SequenceNumber", erd.SequenceNumber.ToString());
55:
56: xmlWriter.WriteEndElement();
57: }
58: xmlWriter.WriteEndElement();
59:
60: return sb.ToString();
61: }
62: }
The help for the command is shown below:
C:\>stsadm -help gl-enumeventreceivers
stsadm -o gl-enumeventreceivers
Enumerates all event receivers associated with the specified target object and outputs the list of receivers as XML.
Parameters:
-url <web or list URL>
-target <site | list | contenttype>
[-contenttype <content type name if target is ContentType>]
|
The following table summarizes the command and its various parameters:
| Command Name | Availability | Build Date |
|---|---|---|
| gl-enumeventreceivers | WSS v3, MOSS 2007 | Released: 9/13/2008
|
| Parameter Name | Short Form | Required | Description | Example Usage |
|---|---|---|---|---|
| url | Yes | The URL to the web or list to display the event receivers. | -url http://portal/pages | |
| target | No | The target object to display the event receivers from. Must be either "list", "site", or "contenttype". If omitted defaults to "list". | -target list | |
| contenttype | ct | No, unless target is contenttype | The name of the content type to to get the event receivers from. | -contenttype "Page"
-ct "Page" |
The following is an example of how to display all the event receivers associated with a pages library:
stsadm -o gl-enumeventreceivers -url http://portal/pages -target list
The following is an example output from running the above command:
<Receivers><Receiver><Name /><Type>ItemUpdating</Type><Assembly>Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08</Assembly><Class>Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver</Class><SequenceNumber>10000</SequenceNumber></Receiver><Receiver><Name>PagesListEventReceiverName</Name><Type>ItemDeleting</Type><Assembly>Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly><Class>Microsoft.SharePoint.Publishing.PagesListCPVEventReceiver</Class><SequenceNumber>1005</SequenceNumber></Receiver><Receiver><Name>PagesListEventReceiverName</Name><Type>ItemAdded</Type><Assembly>Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly><Class>Microsoft.SharePoint.Publishing.PagesListCPVEventReceiver</Class><SequenceNumber>1004</SequenceNumber></Receiver><Receiver><Name>PagesListEventReceiverName</Name><Type>ItemUpdated</Type><Assembly>Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly><Class>Microsoft.SharePoint.Publishing.PagesListCPVEventReceiver</Class><SequenceNumber>1002</SequenceNumber></Receiver><Receiver><Name /><Type>ItemUpdated</Type><Assembly>Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08</Assembly><Class>Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver</Class><SequenceNumber>10000</SequenceNumber></Receiver><Receiver><Name>PagesListEventReceiverName</Name><Type>ItemDeleted</Type><Assembly>Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly><Class>Microsoft.SharePoint.Publishing.PagesListCPVEventReceiver</Class><SequenceNumber>1006</SequenceNumber></Receiver><Receiver><Name>PagesListEventReceiverName</Name><Type>ItemCheckedIn</Type><Assembly>Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly><Class>Microsoft.SharePoint.Publishing.PagesListCPVEventReceiver</Class><SequenceNumber>1003</SequenceNumber></Receiver><Receiver><Name /><Type>ItemCheckedIn</Type><Assembly>Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08</Assembly><Class>Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver</Class><SequenceNumber>10000</SequenceNumber></Receiver></Receivers>
Deleting an Event Receiver using STSADM
I had some time so I decided to add the counterparts to the gl-addeventreceiver command that I just published. I created two new commands, gl-deleteeventreceiver and gl-enumeventreceivers. This post will cover the delete command and I'll go over the enum command in my next post.
From a code perspective the delete command is very similar to the add command. The main difference (aside from the obvious call to Delete instead of Add) is that I've made the assembly, class, and type parameters optional (though at least one must be provided) so the code to get the event receiver to delete needed to be expanded so that it now evaluates each receiver and checks to see if a valid match is found. Once all the receivers have been identified then we simply delete them all.
1: /// <summary>
2: /// Deletes an event receiver from the specified target
3: /// </summary>
4: /// <param name="url">The URL.</param>
5: /// <param name="contentTypeName">Name of the content type.</param>
6: /// <param name="target">The target.</param>
7: /// <param name="assembly">The assembly.</param>
8: /// <param name="className">Name of the class.</param>
9: /// <param name="type">The type.</param>
10: /// <param name="typeNotProvided">if set to <c>true</c> [type not provided].</param>
11: public static void Delete(string url, string contentTypeName, TargetEnum target, string assembly, string className, SPEventReceiverType type, bool typeNotProvided)
12: {
13: using (SPSite site = new SPSite(url))
14: using (SPWeb web = site.OpenWeb())
15: {
16: SPEventReceiverDefinitionCollection eventReceivers;
17: if (target == TargetEnum.List)
18: {
19: SPList list = Utilities.GetListFromViewUrl(web, url);
20:
21: if (list == null)
22: {
23: throw new Exception("List not found.");
24: }
25: eventReceivers = list.EventReceivers;
26: }
27: else if (target == TargetEnum.Site)
28: eventReceivers = web.EventReceivers;
29: else
30: {
31: SPContentType contentType = null;
32: try
33: {
34: contentType = web.AvailableContentTypes[contentTypeName];
35: }
36: catch (ArgumentException)
37: {
38: }
39: if (contentType == null)
40: throw new SPSyntaxException("The specified content type could not be found.");
41:
42: eventReceivers = contentType.EventReceivers;
43: }
44: Delete(eventReceivers, type, assembly, className, typeNotProvided);
45: }
46: }
47:
48: /// <summary>
49: /// Deletes the event receiver matching the provided values from the passed in collection.
50: /// </summary>
51: /// <param name="eventReceivers">The event receivers.</param>
52: /// <param name="eventReceiverType">Type of the event receiver.</param>
53: /// <param name="assembly">The assembly.</param>
54: /// <param name="className">Name of the class.</param>
55: /// <param name="typeNotProvided">if set to <c>true</c> [type not provided].</param>
56: private static void Delete(SPEventReceiverDefinitionCollection eventReceivers, SPEventReceiverType eventReceiverType, string assembly, string className, bool typeNotProvided)
57: {
58: List<SPEventReceiverDefinition> toDelete = new List<SPEventReceiverDefinition>();
59: foreach (SPEventReceiverDefinition erd in eventReceivers)
60: {
61: if ((erd.Assembly == assembly || assembly == null) &&
62: (erd.Class == className || className == null) &&
63: ((erd.Type == eventReceiverType && !typeNotProvided) || typeNotProvided))
64: {
65: toDelete.Add(erd);
66: }
67: }
68: foreach (SPEventReceiverDefinition erd in toDelete)
69: {
70: Log("Deleting the {0} event for class {1} in assembly {2}.", erd.Type.ToString(), erd.Class,
71: erd.Assembly);
72:
73: erd.Delete();
74: }
75: }
The help for the command is shown below:
C:\>stsadm -help gl-deleteeventreceiver
stsadm -o gl-deleteeventreceiver
Deletes an event receiver from a list, web, or content type.
Parameters:
-url <web or list URL>
-target <site | list | contenttype>
[-assembly <assembly>]
[-class <class name>]
[-type <itemadding | itemupdating | itemdeleting | itemcheckingin | itemcheckingout | itemuncheckingout | itemattachmentadding | itemattachmentdeleting | itemfilemoving | fieldadding | fieldupdating | fielddeleting | sitedeleting | webdeleting | webmoving | itemadded | itemupdated | itemdeleted | itemcheckedin | itemcheckedout | itemuncheckedout | itemattachmentadded | itemattachmentdeleted | itemfilemoved | itemfileconverted | fieldadded | fieldupdated | fielddeleted | sitedeleted | webdeleted | webmoved | emailreceived | contextevent | invalidreceiver>]
[-contenttype <content type name if target is ContentType>]
|
The following table summarizes the command and its various parameters:
| Command Name | Availability | Build Date |
|---|---|---|
| gl-deleteeventreceiver | WSS v3, MOSS 2007 | Released: 9/13/2008
|
| Parameter Name | Short Form | Required | Description | Example Usage |
|---|---|---|---|---|
| url | Yes | The URL to the web or list to remove the event receiver from. | -url http://portal/pages | |
| assembly | a | No | The fully qualified assembly name containing the event receiver class to delete from the target. | -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08"
-a "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" |
| class | c | No | The fully qualified class name of the event receiver to delete from the target. | -class Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver
-c Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver |
| type | No | The event type to delete. | -type itemupdated | |
| target | No | The target type of which the receiver will be deleted. Must be either "list", "site", or "contenttype". If omitted defaults to "list". | -target list | |
| contenttype | ct | No, unless target is contenttype | The name of the content type to remove the event receiver from if the target is contenttype. | -contenttype "Page"
-ct "Page" |
The following is an example of how to delete all the event receivers belonging to the web part page history assembly:
stsadm -o gl-deleteeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -target list
Note that you should be particularly careful when deleting event receivers and not specifying the assembly and class attributes as you could inadvertently delete event receivers that are critical to the normal functioning of the list.
Adding Event Receivers using STSADM
A few months ago I created a CodePlex project that allows you to configure a library to allow the storing of web part page history. The project at its core used three item event receivers to listen to certain key events and either backup the page or restore the page based on the event. Recently I was thinking about what might be the best way to allow people to script the settings that enable the storing of page history. I decided that I would start off by providing a simple STSADM command that would allow you to script the adding of event receivers to a list (fortunately making it work for webs and content types didn't really require any additional work so I covered those as well). The new command I created is called gl-addeventreceiver.
I'll probably eventually come up with something else but this at least is a start - I primarily don't like it because it requires that people know how the web part history project works so I'll probably create a command specific to enabling the history so that it abstracts out the technical details which could always change (there just hasn't been a whole lot of interest in the project so I've not really spent much time on it). In the meantime this command is a nice generic tool that could be used by developers and administrators for various reasons (I'll probably create an enum and remove command to supplement this one - just haven't gotten around to it yet).
Adding an event receiver via code is really very simple - just a matter of calling the Add method of the SPEventReceiverDefinitionCollection object which you can get via the EventReceivers property of either the SPList, SPContentType, or SPWeb object. To get an existing event receiver (to check for existence as we don't want to add twice) we have to loop through the collection comparing the assembly name, class name, and event receiver type.
1: /// <summary>
2: /// Adds an event receiver to the specified target
3: /// </summary>
4: /// <param name="url">The URL.</param>
5: /// <param name="contentTypeName">Name of the content type.</param>
6: /// <param name="target">The target.</param>
7: /// <param name="assembly">The assembly.</param>
8: /// <param name="className">Name of the class.</param>
9: /// <param name="type">The type.</param>
10: /// <param name="sequence">The sequence.</param>
11: /// <param name="name">The name.</param>
12: public static void Add(string url, string contentTypeName, TargetEnum target, string assembly, string className, SPEventReceiverType type, int sequence, string name)
13: {
14: using (SPSite site = new SPSite(url))
15: using (SPWeb web = site.OpenWeb())
16: {
17: SPEventReceiverDefinitionCollection eventReceivers;
18: if (target == TargetEnum.List)
19: {
20: SPList list = Utilities.GetListFromViewUrl(web, url);
21:
22: if (list == null)
23: {
24: throw new Exception("List not found.");
25: }
26: eventReceivers = list.EventReceivers;
27: }
28: else if (target == TargetEnum.Site)
29: eventReceivers = web.EventReceivers;
30: else
31: {
32: SPContentType contentType = null;
33: try
34: {
35: contentType = web.AvailableContentTypes[contentTypeName];
36: }
37: catch (ArgumentException)
38: {
39: }
40: if (contentType == null)
41: throw new SPSyntaxException("The specified content type could not be found.");
42:
43: eventReceivers = contentType.EventReceivers;
44: }
45: SPEventReceiverDefinition def = Add(eventReceivers, type, assembly, className, name);
46: if (sequence >= 0)
47: {
48: def.SequenceNumber = sequence;
49: def.Update();
50: }
51: }
52: }
53:
54: /// <summary>
55: /// Adds an event receiver to a the specified event receiver definition collection.
56: /// </summary>
57: /// <param name="eventReceivers">The event receivers.</param>
58: /// <param name="eventReceiverType">Type of the event receiver.</param>
59: /// <param name="assembly">The assembly.</param>
60: /// <param name="className">Name of the class.</param>
61: /// <param name="name">The name.</param>
62: /// <returns></returns>
63: private static SPEventReceiverDefinition Add(SPEventReceiverDefinitionCollection eventReceivers, SPEventReceiverType eventReceiverType, string assembly, string className, string name)
64: {
65: if (GetEventReceiver(eventReceivers, eventReceiverType, assembly, className) == null)
66: {
67: eventReceivers.Add(eventReceiverType, assembly, className);
68: SPEventReceiverDefinition def = GetEventReceiver(eventReceivers, eventReceiverType, assembly, className);
69: if (!string.IsNullOrEmpty(name))
70: {
71: def.Name = name;
72: def.Update();
73: }
74: return def;
75: }
76: return null;
77: }
78:
79: /// <summary>
80: /// Gets the event receiver.
81: /// </summary>
82: /// <param name="eventReceivers">The event receivers.</param>
83: /// <param name="eventReceiverType">Type of the event receiver.</param>
84: /// <param name="assembly">The assembly.</param>
85: /// <param name="className">Name of the class.</param>
86: /// <returns></returns>
87: private static SPEventReceiverDefinition GetEventReceiver(SPEventReceiverDefinitionCollection eventReceivers, SPEventReceiverType eventReceiverType, string assembly, string className)
88: {
89: foreach (SPEventReceiverDefinition erd in eventReceivers)
90: {
91: if (erd.Assembly == assembly && erd.Class == className && erd.Type == eventReceiverType)
92: {
93: return erd;
94: }
95: }
96: return null;
97: }
The help for the command is shown below:
C:\>stsadm -help gl-addeventreceiver
stsadm -o gl-addeventreceiver
Adds an event receiver to a list, web, or content type.
Parameters:
-url <web or list URL>
-assembly <assembly>
-class <class name>
-type <itemadding | itemupdating | itemdeleting | itemcheckingin | itemcheckingout | itemuncheckingout | itemattachmentadding | itemattachmentdeleting | itemfilemoving | fieldadding | fieldupdating | fielddeleting | sitedeleting | webdeleting | webmoving | itemadded | itemupdated | itemdeleted | itemcheckedin | itemcheckedout | itemuncheckedout | itemattachmentadded | itemattachmentdeleted | itemfilemoved | itemfileconverted | fieldadded | fieldupdated | fielddeleted | sitedeleted | webdeleted | webmoved | emailreceived | contextevent | invalidreceiver>
-target <site | list | contenttype>
[-contenttype <content type name if target is ContentType>]
[-sequence <sequence number>]
[-name <the name to give to the event receiver>]
|
The following table summarizes the command and its various parameters:
| Command Name | Availability | Build Date |
|---|---|---|
| gl-addeventreceiver | WSS v3, MOSS 2007 | Released: 9/13/2008
|
| Parameter Name | Short Form | Required | Description | Example Usage |
|---|---|---|---|---|
| url | Yes | The URL to the web or list to add the event receiver to. | -url http://portal/pages | |
| assembly | a | Yes | The fully qualified assembly name containing the event receiver class to add. | -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08"
-a "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" |
| class | c | Yes | The fully qualified class name of the event receiver to add. | -class Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver
-c Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver |
| type | Yes | The event type to add. The command does not validate that you are adding the correct type for the specified target or that the specified class contains handlers for the type specified. | -type itemupdated | |
| target | No | The target type to add the event receiver to. Must be either "list", "site", or "contenttype". If omitted defaults to "list". | -target list | |
| contenttype | ct | No, unless target is contenttype | The name of the content type to add the event receiver to if the target is contenttype. | -contenttype "Page"
-ct "Page" |
| sequence | seq | No | The sequence number specifies the order of execution of the event receiver. | -sequence 1000
-seq 1000 |
| name | n | No | The name to give to the event receiver. The name has no significance but can be useful when later listing the event receivers. | -name "Handle Saving of Page History"
-n "Handle Saving of Page History" |
The following is an example of how to add three event receivers to a pages library - the three commands illustrated below constitute the required event receivers that must be enabled to turn on the web part page history:
stsadm -o gl-addeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -class "Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver" -target list -sequence 1000 -type itemcheckedin
stsadm -o gl-addeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -class "Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver" -target list -sequence 1000 -type itemupdating
stsadm -o gl-addeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -class "Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver" -target list -sequence 1000 -type itemupdated
Check out the books I've contributed to at Amazon.com: