1

I'm trying to create a plugin for D365. I have package dependencies including

  • Azure.DigitalTwins.Core
  • Azure.Core
  • Azure.Identity

These .dll dependencies must be merged into a single .dll to register a plugin. I'm using an ILMerge batch file script to combine these dlls into a single merged.dll output.

Here's the script I'm using:

@echo off
:: this script needs https://www.nuget.org/packages/ilmerge

:: Set build, used for directory. Typically Release or Debug
SET ILMERGE_BUILD=bin\Debug\ilmerge

:: set your NuGet ILMerge Version, this is the number from the package manager install, for example:
:: PM> Install-Package ilmerge -Version 3.0.29
:: to confirm it is installed for a given project, see the packages.config file
SET ILMERGE_VERSION=3.0.41

:: the full ILMerge should be found here:
SET ILMERGE_PATH=%USERPROFILE%\.nuget\packages\ilmerge\%ILMERGE_VERSION%\tools\net452
:: dir "%ILMERGE_PATH%"\ILMerge.exe

SET PROJECT_ROOT=%CD%
SET ASSEMBLY_DIR=%PROJECT_ROOT%\bin\Debug\net462

::/targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0" ^
::/target:library ^
::/copyattrs ^
::/closed ^
::/allowDup ^

echo Merging adtplugin...
:: add project DLL's starting with replacing the FirstLib with this project's DLL
"%ILMERGE_PATH%"\ILMerge.exe ^
/keyfile:%PROJECT_ROOT%\adtplugin.snk ^
/out:%ILMERGE_BUILD%\merged.dll ^
/target:library ^
/nDebug ^
/closed ^
/allowDup ^
/copyattrs ^
/targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2" ^
%ASSEMBLY_DIR%\adtplugin.dll ^
%ASSEMBLY_DIR%\Azure.Core.dll ^
%ASSEMBLY_DIR%\Azure.DigitalTwins.Core.dll ^
%ASSEMBLY_DIR%\Azure.Identity.dll ^
%ASSEMBLY_DIR%\Microsoft.Bcl.AsyncInterfaces.dll ^
%ASSEMBLY_DIR%\Microsoft.Identity.Client.dll ^
%ASSEMBLY_DIR%\Microsoft.Identity.Client.Extensions.Msal.dll ^
%ASSEMBLY_DIR%\Microsoft.Win32.Primitives.dll ^
%ASSEMBLY_DIR%\netstandard.dll ^
%ASSEMBLY_DIR%\System.Memory.dll ^
%ASSEMBLY_DIR%\System.Threading.Tasks.Extensions.dll 

pause
:Done

I've played around quite a bit with the /option flags passed to ILMerge.exe. Currently with the config shown, I'm able to merge the .dlls listed into a single merged.dll output.

Next, I'm trying to use the Plug-in registration tool to register a new assembly using the merged.dll output file. When I try to register the plugin I'm getting errors:

Plug-in registration tool

Here's the error dump:

Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: Unable to load plug-in assembly.
Detail: <OrganizationServiceFault xmlns="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <ActivityId>9e6ed737-8374-464e-956a-d70b46a2c686</ActivityId>
  <ErrorCode>-2147204719</ErrorCode>
  <ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
    <KeyValuePairOfstringanyType>
      <a:key>ApiExceptionSourceKey</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Plugin/Microsoft.Crm.ObjectModel.PluginAssemblyService</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiSourceActivityKey</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Microsoft.Crm.Extensibility.PipelineStep</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiExceptionOwnerKey</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">COMMONDATASERVICECDS\CDSAPI-Reliability</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiOriginalExceptionKey</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Microsoft.Crm.CrmException: Unable to load plug-in assembly. ---&gt; Microsoft.Crm.CrmException: Unable to load plug-in assembly.
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.LoadCrmPluginAssemblyMetadata(IBusinessEntity pluginAssembly, IExecutionContext context, Boolean loadAllMetadata)
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.RetrieveAssemblyMetadata(IBusinessEntity pluginAssembly, ExecutionContext context, Boolean retrieveFromExisting, Boolean forSystemAssembly)
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.ValidateOperation(String operationName, IBusinessEntity entity, ExecutionContext context)
   at Microsoft.Crm.ObjectModel.SdkEntityServiceBase.CreateInternal(IBusinessEntity entity, ExecutionContext context, Boolean verifyAction)
   --- End of inner exception stack trace ---
   at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.PipelineInstrumentationHelper.Execute(Boolean instrumentationEnabled, String stopwatchName, ExecuteWithInstrumentation action, PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.Pipeline.&lt;&gt;c__DisplayClass7_0.&lt;RunStep&gt;b__0()</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiStepKey</a:key>
      <a:value i:type="b:guid" xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/">3ecabb1b-ea3e-db11-86a7-000a3a5473e8</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiDepthKey</a:key>
      <a:value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">1</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiActivityIdKey</a:key>
      <a:value i:type="b:guid" xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/">9e6ed737-8374-464e-956a-d70b46a2c686</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiPluginSolutionNameKey</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">System</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiStepSolutionNameKey</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">System</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiExceptionCategory</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">SystemFailure</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiExceptionMessageName</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">UnableToLoadPluginAssembly</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>ApiExceptionHttpStatusCode</a:key>
      <a:value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">500</a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>0</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Failed to load plugin assembly with exception Microsoft.Crm.CrmException: GetAssemblyMetadata: expected mdtAssemblyRef or mdtAssembly
   at Microsoft.Crm.CrmException.Assert(Boolean condition, String message)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.GetAssemblyMetadata(UInt32 token)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessTypeRef(UInt32 typeRef)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessType(UInt32 token)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessTypeDef(UInt32 typeDef)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessType(UInt32 token)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadata()
   at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadataFromAssemblyContent(ArraySegment`1 assemblyContents, Boolean loadAllMetadata)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadataFromAssemblyContent(String content, Boolean loadAllMetadata)
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.LoadCrmPluginAssemblyMetadata(IBusinessEntity pluginAssembly, IExecutionContext context, Boolean loadAllMetadata). PluginInfo =&gt; Crm plugin assembly info : sourcetype = 0, description = , ismanaged = False, pluginassemblyid = b77380cb-8676-45b6-9e5c-63a5960ed2ac, ispasswordset = False, publickeytoken = 7F66FA42F6885FDC, name = merged, culture = neutral, isolationmode = 2, version = 1.0.0.0, </a:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <a:key>1</a:key>
      <a:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Microsoft.Crm.CrmException: GetAssemblyMetadata: expected mdtAssemblyRef or mdtAssembly
   at Microsoft.Crm.CrmException.Assert(Boolean condition, String message)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.GetAssemblyMetadata(UInt32 token)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessTypeRef(UInt32 typeRef)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessType(UInt32 token)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessTypeDef(UInt32 typeDef)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.ProcessType(UInt32 token)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadata()
   at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadataFromAssemblyContent(ArraySegment`1 assemblyContents, Boolean loadAllMetadata)
   at Microsoft.Crm.CrmPluginAssemblyMetadata.LoadMetadataFromAssemblyContent(String content, Boolean loadAllMetadata)
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.LoadCrmPluginAssemblyMetadata(IBusinessEntity pluginAssembly, IExecutionContext context, Boolean loadAllMetadata)</a:value>
    </KeyValuePairOfstringanyType>
  </ErrorDetails>
  <HelpLink>http://go.microsoft.com/fwlink/?LinkID=398563&amp;error=Microsoft.Crm.CrmException%3a80044191&amp;client=platform</HelpLink>
  <Message>Unable to load plug-in assembly.</Message>
  <Timestamp>2021-04-29T17:46:08.9958593Z</Timestamp>
  <ExceptionRetriable>false</ExceptionRetriable>
  <ExceptionSource i:nil="true" />
  <InnerFault>
    <ActivityId>9e6ed737-8374-464e-956a-d70b46a2c686</ActivityId>
    <ErrorCode>-2147204719</ErrorCode>
    <ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
    <HelpLink i:nil="true" />
    <Message>Unable to load plug-in assembly.</Message>
    <Timestamp>2021-04-29T17:46:08.9958593Z</Timestamp>
    <ExceptionRetriable>false</ExceptionRetriable>
    <ExceptionSource i:nil="true" />
    <InnerFault i:nil="true" />
    <OriginalException i:nil="true" />
    <TraceText i:nil="true" />
  </InnerFault>
  <OriginalException i:nil="true" />
  <TraceText i:nil="true" />
</OrganizationServiceFault>

Server stack trace: 
   at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]: 
   at Microsoft.Crm.Tools.Libraries.RegistrationHelper.RegisterAssembly(CrmOrganization org, String pathToAssembly, CrmPluginAssembly assembly) in D:\a\1\s\src\GeneralTools\PluginRegistrationV3\PluginRegistration\CrmLibraries\RegistrationHelper.cs:line 723
   at Microsoft.Crm.Tools.AssemblyRegistration.PluginRegistrationViewModel.RegisterAssembly() in D:\a\1\s\src\GeneralTools\PluginRegistrationV3\RegistrationTools\AssemblyRegistration\ViewModels\PluginRegistrationViewModel.cs:line 649

If anyone has had success using a .bat script to run ILMerge, merging .dll files, and using the output to register a D365 Plugin, please let me know! (extremely specific... I know haha)

Update

I've now switched to ILRepack and ILRepack.Lib.MSBuild.Task. I'm using the following config in a ILRepack.targets file:

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="ILRepacker" AfterTargets="Build">
    <ItemGroup>
      <InputAssemblies Include="$(OutputPath)$(TargetName)$(TargetExt)" />
      <!--<InputAssemblies Include="$(OutputPath)\*.dll" />-->
      <InputAssemblies Include="$(OutputPath)adtplugin.dll" />
      <InputAssemblies Include="$(OutputPath)Azure.Core.dll" />
      <InputAssemblies Include="$(OutputPath)Azure.DigitalTwins.Core.dll"/>
      <InputAssemblies Include="$(OutputPath)Azure.Identity.dll"/>
      <InputAssemblies Include="$(OutputPath)Microsoft.Bcl.AsyncInterfaces.dll"/>
      <InputAssemblies Include="$(OutputPath)System.Memory.dll"/>
      <InputAssemblies Include="$(OutputPath)System.Threading.Tasks.Extensions.dll"/>
      <!--<InputAssemblies Include="$(OutputPath)Microsoft.Identity.Client.dll"/>
      <InputAssemblies Include="$(OutputPath)Microsoft.Identity.Client.Extensions.Msal.dll"/>
      <InputAssemblies Include="$(OutputPath)Microsoft.Win32.Primitives.dll"/>
      <InputAssemblies Include="$(OutputPath)netstandard.dll"/>-->
      
    </ItemGroup>

    <ILRepack
        Parallel="true"
        Internalize="false"
        InternalizeExclude="@(DoNotInternalizeAssemblies)"
        InputAssemblies="@(InputAssemblies)"
        LibraryPath="$(OutputPath)"
        Wildcards="false"
        TargetKind="SameAsPrimaryAssembly"
        DebugInfo="false"
        KeyFile="adtplugin.snk"
        OutputFile="$(OutputPath)Merged\$(AssemblyName).dll"
        LogFile="$(OutputPath)Merged\ILRepack.log"
    />
  </Target>
</Project>

The merge succeeds, but the plugin registration is still failing with the same error dump shown above.

Cory Crowley
  • 304
  • 1
  • 12
  • My advice: stop using ILMerge. Instead use ILRepack, it is now easy to add to your project. See [this article on SO](https://stackoverflow.com/questions/66815639/crm-using-ilmerge-to-merge-framework-library-with-plugin-projects/66816173#66816173). – Henk van Boeijen Apr 30 '21 at 08:46
  • I switched over the ILRepack (and updated my question info), but I'm still getting the same plugin registration error : / – Cory Crowley Apr 30 '21 at 17:27
  • Are you merging .NET Core assemblies? Assemblies must be based on .NET Framework 4 and cannot be from the System namespace. – Henk van Boeijen Apr 30 '21 at 17:50
  • Looking at your assemblies I think an option could be creating an Azure component (e.g. web API) and call it from your plugin using a plain HttpClient. – Henk van Boeijen Apr 30 '21 at 17:55

2 Answers2

2

You might want to avoid merging anything that's part of the .NET Framework (which I assume that libraries like System.Memory and System.Threading.Tasks are).

That being said, based on the DLLs you're merging, even if you get it to register, depending on what the code actually does, the limitations of the sandbox environment may prevent the plugin from running.

If that turns out to be the case, you might want to look into an Azure Aware Plugin instead.

I once had a plugin where I had to do RSA signature verification. I tried using the .NET libraries but the sandbox prohibited the RSA-related code from running. I wound up having to "roll my own" using an open source RSA library I found called EZ-RSA.

Thanks for the update. I looked into the error message a little more. I found this article and this question where the developer seems to conclude that ILMerging an Identity library is a showstopper.

As far as the merge/repack path goes, you might want to use a blank project and merge the DLLs one-by-one, testing the plugin registration after each one to see which library or libraries break the registration.

The last I heard, even when it works, ILMerge is technically unsupported. What is supported is an Azure Aware Plugin.

You can register your plugin to to call out to a service endpoint or webhook when it fires:
reg1

For the Service Endpoint it can hit various types of listeners in an Azure Service Bus Queue (via a connection string or this configuration dialog). service endpoint

And calling out to a Webhook is also an option: webhook

Aron
  • 3,877
  • 3
  • 14
  • 21
  • Strangely, when I don't explicitly include `System.Memory` and `System.Threading.Tasks`, the plugin registration fails with an error saying those packages could not be found. That's why they are on this list, but I agree. Those shouldn't need to be included, so I'm not sure why this is happening. – Cory Crowley Apr 30 '21 at 17:30
  • I've switched over to ILRepack and updated my question, but the plugin registration is still failing with the same error dump. – Cory Crowley Apr 30 '21 at 17:31
  • Thanks for the update. I enhanced my answer. – Aron Apr 30 '21 at 18:27
0

Try to use Fody package to merge your .dll-s after build action. First, you need to install Fody and Costura.Fody via nuget. It will add some references and the file FodyWeavers.xml which you can fill this way

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura />
</Weavers>

Then in the Solution Explorer select the references for the assemblies which you want to merge together and in the Properties Window change Copy Local to True. For those assemblies you don't want to include in the merge process select Copy Local - False

Rebuild your solution and in the output you'll got merged dll.

Arsen
  • 382
  • 4
  • 14