Monday, March 23, 2015

BadImageFormatException Error after upgrading eConnect import to GP 2015: Don't forget to update your config file!

By Steve Endow

I recently upgraded a .NET eConnect Dynamics GP integration from GP 2013 to GP 2015.

In Dynamics GP 2015, the eConnect libraries were updated to use .NET 4.5.1, so when you upgrade any existing eConnect projects to GP 2015, you will need to use Visual Studio 2012 or higher.  I personally recommend skipping Visual Studio 2012 and going to Visual Studio 2013, which has a better user interface and some nice auto complete features, and also has native support for Git, which I recently discussed.

After you open your project, replace your eConnect references, and update to .NET 4.5.1, you can rebuild and produce your new eConnect integration for Dynamics GP 2015.

Easy peasy lemon squeezee, right?

In theory.

After completing this upgrade process for my integration (a scheduled Console application), I copied my upgraded EXE and DLL files to the client's workstation.  We then tested the integration.  As soon as we tried to run the EXE using Task Scheduler, the import crashed and we got an error message.

The only meaningful error text that was displayed was "BadImageFormatException".

Huh.  When you see the BadImageFormatException error, that almost always means that you have an issue mixing 32-bit and 64-bit libraries or projects.  So if I reference a 64-bit DLL but have my app targeted to 32-bit in Visual Studio, that can cause the BadImageFormat error.

Puzzled, I double checked my settings, and everything was consistently 32-bit.  My projects targeted x86, and I was definitely referencing only 32-bit DLLs. Hmmm.

As a test, I converted my projects and references to 64-bit.  I rebuilt and tested on the client's machine, but we got the same BadImageFormatException error.  Argh.

After some more research, I then opened the Visual Studio Configuration Manager to see if it might be overriding my Target Platform settings and mixing 32-bit and 64-bit elements.  Sure enough, when I opened the window, the settings were a mess--I don't know why, as I normally never touch that window.

I finally figured out how to clean up all of the configuration settings, and thought I had found and resolve the issue, for sure!

All nice and purrrrdy now...
I confidently delivered the new EXE and DLL files and we tested and...

It crashed right away with the same error.

Un. Be. Lievable.

I had run out of ideas.  I finally tried testing via the Command Prompt window to see if I could get any additional error info.

Unhandled Exception:  System.BadImageFormatException: Could not load file or assembly 'myapp.exe' or one of its dependencies.  This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.

Hmmm, this additional information would seem to indicate that the problem is a .NET version issue, and not a typical 32 vs. 64 bit conflict. I went back and triple checked my .NET versions.  My projects were definitely using .NET 4.5.1.  The eConnect libraries were definitely GP 2015 and definitely required .NET 4.5.1, so that wasn't the issue.  A third party library that I was using was definitely 4.5.x compatible.  And I couldn't reproduce the issue on my development machine, so clearly the components worked together.

I then tested my app on...not four...not five...but SIX different computers on my network.  Windows 7, Windows 8, and Server 2008, and Server 2012.  Of course it worked perfectly on all six.  This would seem to rule out a .NET version issue in my application.


I then asked the client if they had another computer we could test with.  Fortunately they did, so we tested on that computer.  I was hoping the issue was workstation specific, and while I might never know the cause of the problem, as long as we could get it working, I would be happy.

We tested on the other computer...but...crash.  BadImageFormatException.

Lacking any other options, I dug in deep.  I downloaded the Sysinternals Process Monitor and the client recorded the activity from the import from launch to crash.  I also ran Process Monitor on my server and recorded the activity when it worked successfully.

Finally, I had a small clue.  It wasn't obvious or specific, but it definitely told me that something was different on the workstation.  I put the two Process Monitor data files side by side in Excel and saw an obvious issue.

My server is on the left.  The client's workstation is on the right.  Notice how my machine is clearly using .NET 4, while the client machine is clearly using .NET 2.  That would explain the error message and the problem.

So I had found the "cause" of the error, but didn't have an explanation as to why this was happening.

The .NET version is set in Visual Studio, and Windows just follows instructions and loads the appropriate .NET version.  It's not like Windows will randomly choose the wrong .NET version.

I then searched for "windows runs wrong .net version" or something like that.  I didn't see any obvious results relating to my issue, but somehow "config file" popped into my head.  Hmmm.

When I provided the new GP 2015 version of the import, I only updated the DLL and EXE files.  The exe.config configuration file was already setup, so we didn't need to make any changes.

And what is contained in a .NET configuration file?

That is an excellent question, I'm glad you asked!

Behold, it is a .NET version setting!

< startup>< supportedRuntime version="v2.0.50727"/ >< /startup>

And so that is what was still in the exe.config file, and that is why Windows was dutifully trying to launch the application with .NET 2.0, which is in turn why the application crashed.

Face.  Palm.

While it isn't an excuse, I at least have an explanation.

I have certainly upgraded integrations before and changed .NET versions, but with upgrades from .NET 2 to .NET 3.5, no change was required in the configuration file--it stayed at v2.0.

But with an upgrade to .NET 4 or higher, the reference in the configuration file needs to be changed.  While I have developed and deployed .NET 4+ integrations, I believe this was the first time that I upgraded an existing integration from .NET 2 to .NET 4.  It just didn't occur to me that the configuration file would have to be updated.

Here is what my configuration file should have looked like:

< startup>< supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/ >< /startup>

As soon as I modified the config file on the client workstation, the GP 2015 integration ran fine.

And I shook my head.

But hey, good news...I eventually figured it out...

Steve Endow is a Microsoft MVP for Dynamics GP and a Dynamics GP Certified IT Professional in Los Angeles.  He is the owner of Precipio Services, which provides Dynamics GP integrations, customizations, and automation solutions.

You can also find him on Google+ and Twitter

No comments: