ContentDeploymentJobCollection GetEnumerator Not Implemented?

Mike Hodnick just posted about programmatically executing content deployment jobs and I figured I’d skip the console app in favor of PowerShell:


And much to my surprise, I was greeted with a NotImplementedException:
NotImplementedException from ContentDeploymentJob.GetAllJobs() in PowerShell

However, the following works fine:

$cdj = [Microsoft.SharePoint.Publishing.Administration.ContentDeploymentJob]
$cdj::GetAllJobs().GetEnumerator() | %{ $_.Name; $_.Run() }

What’s going on here? Well first, let’s see if we can replicate the exception in the console app:
NotImplementedException from ContentDeploymentJob.GetAllJobs() in Console Application

Why would I think to explicitly cast the collection to IEnumerable in the first place? Well when PowerShell sees an enumerable object in the pipeline, it “unwraps” it to pass its members along to the next command in line. To facilitate this, I’m guessing the runtime does something to this effect:

if(currentItemInPipeline is IEnumerable)
  HandleEnumerable(currentItemInPipeline as IEnumerable);

And HandleEnumerable probably has a method signature like this:

void HandleEnumerable(IEnumerable enumerableItem) { ...

If we apply that pattern to our collection, we see the exception again:
NotImplementedException from ContentDeploymentJob.GetAllJobs() in HandleEnumerable

So what’s the big deal? Well, reflecting the object heirarchy above ContentDeploymentJobCollection we find CollectionBase<T>, which has two implementations of GetEnumerator:

public IEnumerator<T> GetEnumerator() {
    return new StandardEnumerator<T>((CollectionBase<T>) this);

IEnumerator IEnumerable.GetEnumerator() {
    throw new NotImplementedException();

The former is pretty standard; the latter is an explicit implementation of the interface method. When our ContentDeploymentJobCollection object is used as an IEnumerable, like my test variable and in HandleEnumerable (and presumably in the PowerShell runtime), the explicit implementation is used and throws the exception. On the other hand, if we make PowerShell use the implemented GetEnumerator, everything works as expected.

And for completeness, Mike’s foreach compiles into the following IL, calling the desired method:

callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [Microsoft.SharePoint.Publishing]Microsoft.SharePoint.Publishing.CollectionBase`1<class [Microsoft.SharePoint.Publishing]Microsoft.SharePoint.Publishing.Administration.ContentDeploymentJob>::GetEnumerator()

So if you’re getting a NotImplementedException when using a collection that inherits from Microsoft.SharePoint.Publishing.CollectionBase<T>, make sure you’re calling the correct GetEnumerator.

Collections affected:

  • Microsoft.SharePoint.Publishing.Administration.ContentDeploymentJobCollection
  • Microsoft.SharePoint.Publishing.Administration.ContentDeploymentJobReportCollection
  • Microsoft.SharePoint.Publishing.Administration.ContentDeploymentPathCollection
  • Microsoft.SharePoint.Publishing.Administration.MigrationReportCollection
  • Microsoft.SharePoint.Publishing.PageLayoutCollection
  • Microsoft.SharePoint.Publishing.PublishingPageCollection

Microsoft.SharePoint.Publishing.CollectionBase<T> Derived Types