Pnp-sites-core: Load New-PnPExtensbilityHandlerObject assembly from path

Created on 14 Jun 2017  路  14Comments  路  Source: pnp/PnP-Sites-Core

Category

[ ] Bug
[x] Enhancement

Environment

[x] Office 365 / SharePoint Online
[ ] SharePoint 2016
[ ] SharePoint 2013

Expected or Desired Behavior

When using New-PnPExtensbilityHandlerObject I would like to be able to specify a path name for the Assembly parameter, so that non-GAC dll's can be loaded and used.

Observed Behavior

Providing an assembly which is not installed in the GAC fails when using the New-PnPExtensbilityHandlerObject command, and using this as ExtensibilityHandler for Apply-PnPProvisioningTemplate.

Steps to Reproduce

  1. Try loading an extensibility handler from a local path. E.g.: $pageHandler = New-PnPExtensbilityHandlerObject -Assembly "MyHandlers.Provisioning" -Type MyHandlers.Provisioning.ClearPageLayoutWebPartsProvisioningExtension
  2. Use the extension in the provisioning command. E.g.: Apply-PnPProvisioningTemplate -Path PageLayouts.xml -ExtensibilityHandlers $pageHandler

An error will be thrown saying:

Apply-PnPProvisioningTemplate : There was an exception invoking the custom extensibility provider. Assembly: MyHandlers.Provisioning, Type: MyHandlers.Provisioning.ClearPageLayoutWebPartsProvisioningExtension. Exception System.IO.FileNotFoundException: Could not load file or assembly 'MyHandlers.Provisioning' or one of its dependencies. The
system cannot find the file specified.
File name: 'MyHandlers.Provisioning'
at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr p
PrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPt
r pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Activator.CreateInstance(String assemblyString, String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Obje
ct[] activationAttributes, Evidence securityInfo, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(String assemblyName, String typeName)
at OfficeDevPnP.Core.Framework.Provisioning.Extensibility.ExtensibilityManager.GetProviderInstance(ExtensibilityHandler handler)
at OfficeDevPnP.Core.Framework.Provisioning.Extensibility.ExtensibilityManager.ExecuteTokenProviderCallOut(ClientContext ctx, ExtensibilityHandler provider, ProvisioningTemplat
e template)
=== Pre-bind state information ===
LOG: DisplayName = MyHandlers.Provisioning
(Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: MyHandlers.Provisioning | Domain ID: 1
WRN: A partial bind occurs when only part of the assembly display name is provided.
WRN: This might result in the binder loading an incorrect assembly.
WRN: It is recommended to provide a fully specified textual identity for the assembly,
WRN: that consists of the simple name, version, culture, and public key token.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
LOG: Appbase = file:///C:/Windows/System32/WindowsPowerShell/v1.0/
LOG: Initial PrivatePath = NULL
Calling assembly : OfficeDevPnP.Core, Version=2.15.1705.0, Culture=neutral, PublicKeyToken=3751622786b357c2.
\===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/Windows/System32/WindowsPowerShell/v1.0/MyHandlers.Provisioning.DLL.
LOG: Attempting download of new URL file:///C:/Windows/System32/WindowsPowerShell/v1.0/MyHandlers.Provisioning/MyHandlers.Provisioning.DLL.
LOG: Attempting download of new URL file:///C:/Windows/System32/WindowsPowerShell/v1.0/MyHandlers.Provisioning.EXE.
LOG: Attempting download of new URL file:///C:/Windows/System32/WindowsPowerShell/v1.0/MyHandlers.Provisioning/MyHandlers.Provisioning.EXE.
LOG: Attempting download of new URL file:///C:/Users/sevensn/Documents/WindowsPowerShell/Modules/SharePointPnPPowerShellOnline/2.15.1705.0/MyHandlers.Provisioning.DLL.
LOG: Attempting download of new URL file:///C:/Users/sevensn/Documents/WindowsPowerShell/Modules/SharePointPnPPowerShellOnline/2.15.1705.0/MyHandlers.Provisioning/ALXS.Intranet.Scri
pts.DLL.
LOG: Attempting download of new URL file:///C:/Users/sevensn/Documents/WindowsPowerShell/Modules/SharePointPnPPowerShellOnline/2.15.1705.0/MyHandlers.Provisioning.EXE.
LOG: Attempting download of new URL file:///C:/Users/sevensn/Documents/WindowsPowerShell/Modules/SharePointPnPPowerShellOnline/2.15.1705.0/MyHandlers.Provisioning/ALXS.Intranet.Scri
pts.EXE.
At line:1 char:1
+ Apply-PnPProvisioningTemplate -Path ....\Provisioning\Inputs\PageLay ...
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (:) [Apply-PnPProvisioningTemplate], ExtensiblityPipelineException
+ FullyQualifiedErrorId : EXCEPTION,SharePointPnP.PowerShell.Commands.Provisioning.ApplyProvisioningTemplate

Most helpful comment

I ran into the same issue where I could load the assembly using Add-Type and the FQDN name but the Extract method wasn't getting called.

My issue turned out to be caused by two different versions of the OfficeDevPnP.Core DLL getting loaded.

The extensibility handler (loading the DLL through NuGet) was referencing a slightly older version than the PowerShell cmdlets (installed through Install-Module). Once both were upgraded to the latest version, the Extract method was called correctly.

The line in ExtensibilbityManager.cs that @luismanez mentions above was where the problem was happening.

if (_instance is IProvisioningExtensibilityHandler)

Even though my handler implemented the IProvisioningExtensibilityHandler interface, because it was referencing that interface from a different assembly the .NET runtime would always treat it as a completely different type. Which means that section of code just fails silently.

This issue can be detected by adding the following line to the end of your PowerShell script.

[System.AppDomain]::CurrentDomain.GetAssemblies() | % { $_.FullName } | Sort-Object

If it outputs OfficeDevPnP.Core multiple times, the issue will probably occur. i.e.

OfficeDevPnP.Core, Version=2.13.1703.0, Culture=neutral, PublicKeyToken=3751622786b357c2
OfficeDevPnP.Core, Version=2.20.1711.0, Culture=neutral, PublicKeyToken=3751622786b357c2

Note: This issue also occurs if the versions are identical but one DLL is signed and the other isn't (which might occur if running a custom build of the PowerShell cmdlets).

Not sure if there's a way to build the extensibility handler DLL so it always just references whatever version of the OfficeDevPnP.Core is already loaded.

All 14 comments

Hi @luismanez thank you for the info.
However, I believe the main issue is that PowerShell tries to load the DLL(s) from the wrong path.
Instead of loading from the current folder, it tries to load either from the powershell.exe directory, or from the SharePointPnPPowerShellOnline directory.

Ideally, there would be a way to specify the directory you want the assembly to be loaded from. If this is not possible, the only solution seems to be a GAC installed assembly?

Agreed! I'll try to do some research and see what I can find. Thanks!

hey @NickSevens no luck here.
I've been able to avoid the error, but then, the Handler Provision/Extract method is never called.
You can avoid the error and load the dll if you do:

Add-Type -Path "C:\_lab\PnPExtensibilityHandlerPowerShell\PnPExtensibilityHandlerPowerShell\bin\Debug\PnPExtensibilityHandlerPowerShell.dll"

but later, when the PnP.Core is about to call the Extract/Provision method, it checks this (ExtensibilbityManager.cs):
c# if (_instance is IProvisioningExtensibilityHandler)
and I think is not passing that check (in the Log I can see it finds the assembly, I even added a constructor in the custom Handler and added some log there and it works), but the Extract/Provision is never called (I'm pretty sure because the previous if).

Adding @erwinvanhunen to the discussion, just in case he has any idea about this...

Thanks.

@luismanez thanks for the investigation.
So, basically the only option at this point is to use .net code instead of powershell?

I'd say so. it'd be good to see if the one and only PS master @erwinvanhunen has some thoughts here, but seems a pretty weird Type conversion from PowerShell...

I was able to workaround the issue.

In my scripts, I use the FQDN name of the assembly, even if not signed:

Add-Type -Path \Path\To\MyHandler.dll

$extHandler = New-PnPExtensbilityHandlerObject `
     -Type MyHandler.MyHandler`
     -Assembly "MyHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

The important part here, is the assembly name of the extensbility handler, with includes version and public key token (which is null as I didn't signed the assembly).

This syntax works in my environment (which is SP2013). See our real life example.

Hope that helps

Hi @stevebeauge
thanks for the info. Just a question: Did you Apply a template with the custom Handler?? I'm asking cos I tried something like that, and although the syntax was OK and didn麓t raise the original issue raised by @NickSevens , the Apply-Provisioning template never called the custom Handler (no error, but the custom Handler was ignored).
To be accurate, I tested it using the Get-PnPProvisioningTemplate method, but I guess is the same.

Thanks!

@luismanez, yes, I have both Get-PnPProvisioningTemplate and Apply-PnPProvisioningTemplate working with the handler.

Do you provide properly the argument ExtensibilityHandlers to the command ?

Here is the scripts I use in production:

Deployment:

$now = Get-Date -Format "yyyyMMdd-hhmmss"
Set-PnPTraceLog -On -LogFile "$CurrentDirectory\pnp-import.$now.log" -Level Debug -AutoFlush $true
Connect-PnPOnline -Url $siteUrl -CurrentCredentials

$extHandlers = @(
    New-PnPExtensbilityHandlerObject -Type SoSP.PnPProvisioningExtensions.Core.MetadataNavigationHandler `
                                     -Assembly "SoSP.PnPProvisioningExtensions.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
    New-PnPExtensbilityHandlerObject -Type SoSP.PnPProvisioningExtensions.Core.DocumentSetHomePageHandler `
                                     -Assembly "SoSP.PnPProvisioningExtensions.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
)

Apply-PnPProvisioningTemplate -InputInstance $pnpTemplate -ClearNavigation -ExtensibilityHandlers $extHandlers
Set-PnPTraceLog -Off

and export:

$now = Get-Date -Format "yyyyMMdd-hhmmss"
Set-PnPTraceLog -On -LogFile "$CurrentDirectory\pnp-export.$now.log" -Level Debug -AutoFlush $true
Connect-PnPOnline -Url $config.sourceSite -CurrentCredentials

$extHandlers = @(
    New-PnPExtensbilityHandlerObject -Type SoSP.PnPProvisioningExtensions.Core.MetadataNavigationHandler `
                                     -Assembly "SoSP.PnPProvisioningExtensions.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
    New-PnPExtensbilityHandlerObject -Type SoSP.PnPProvisioningExtensions.Core.DocumentSetHomePageHandler `
                                     -Assembly "SoSP.PnPProvisioningExtensions.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
)

Get-PnPProvisioningTemplate -Out (Join-Path $OutDir $config.pnpTemplatePath) -ExtensibilityHandlers $extHandlers  -Force 
Set-PnPTraceLog -Off

I don't know if it matters, but I'm working with the DEV branch of the engine, not the releases.

Also, I've seen in the code a deprecated way to build extentions (using provider). I use the IExensibilityHandler interface to build my extentions... Can you check that ?

Thanks for the detailed info @stevebeauge
It麓s pretty much what I did, only that I tried only 1 Ext provider (yeah, using the IExensibilityHandler). It麓s weird, cos I was able to print data in the constructor of the Handler (meaning it was invoked from PnP Core), but the Extract/Provision methods were never called.
Perhaps was an issue with the Core version, or that I麓m using SP Online. Anyway, glad it works for you, let麓s see if it works for @NickSevens too.

@stevebeauge thank you indeed for the information. This did help me getting by the assembly loading errors.
Unfortunately, I seem to have the same issue as @luismanez right now.
I can now see the extensibility handler getting into the constructor of my custom handler, but not in Provision(), GetTokens() or Extract() functions.

For reference: I tried using Apply-PnpProvisioningTemplate, with version 2.16.1706.1 (June Intermediate Release 1).
To check if a function was called I logged some arbitrary text using Trace.TraceInformation()

Strangely, when trying the same with a c# console application, I only seem to enter contructor and GetTokens(). Not the Provision() or Extract() functions. Any idea why that could be as well? :/

_edit_: this only seems to be the case when using the ProvisioningTemplateApplyingInformation argument. Adding the ExtensibilityProvider to the XML template _DOES_ work!
(however, still only when using C# console app, not in powershell)

Hi all

I'm experiencing the same issues, I have a custom extensibility provider the gets called find if I apply the template via c# but not from PowerShell. I even pulled down the dev branches and built from there but it never went into the provider

I gave up in the end as timescales dictated that it needed to work, so I just wrapped it up in C#. Shame because PowerShell would have been a far easier, would love to see it working properly

I ran into the same issue where I could load the assembly using Add-Type and the FQDN name but the Extract method wasn't getting called.

My issue turned out to be caused by two different versions of the OfficeDevPnP.Core DLL getting loaded.

The extensibility handler (loading the DLL through NuGet) was referencing a slightly older version than the PowerShell cmdlets (installed through Install-Module). Once both were upgraded to the latest version, the Extract method was called correctly.

The line in ExtensibilbityManager.cs that @luismanez mentions above was where the problem was happening.

if (_instance is IProvisioningExtensibilityHandler)

Even though my handler implemented the IProvisioningExtensibilityHandler interface, because it was referencing that interface from a different assembly the .NET runtime would always treat it as a completely different type. Which means that section of code just fails silently.

This issue can be detected by adding the following line to the end of your PowerShell script.

[System.AppDomain]::CurrentDomain.GetAssemblies() | % { $_.FullName } | Sort-Object

If it outputs OfficeDevPnP.Core multiple times, the issue will probably occur. i.e.

OfficeDevPnP.Core, Version=2.13.1703.0, Culture=neutral, PublicKeyToken=3751622786b357c2
OfficeDevPnP.Core, Version=2.20.1711.0, Culture=neutral, PublicKeyToken=3751622786b357c2

Note: This issue also occurs if the versions are identical but one DLL is signed and the other isn't (which might occur if running a custom build of the PowerShell cmdlets).

Not sure if there's a way to build the extensibility handler DLL so it always just references whatever version of the OfficeDevPnP.Core is already loaded.

Was this page helpful?
0 / 5 - 0 ratings