SSIS Script Task cant find reference to assembly - c#

I have an SSIS package that uses a script task to populate a datatable with data from a variety of different file types including excel.
I am using NPOI to read in the data from Excel and have put the NPOI.dll file in the same folder as the SSIS package and added it as a reference in the script task. I'm a Noob when it comes to NPOI so I'm only tinkering at the moment, but even still I have fallen at the first hurdle!
My script contains the code below (which i copied from this SA answer):
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
HSSFWorkbook wb;
using (FileStream file = new FileStream(FilePath, FileMode.Open, FileAccess.Read))
{
wb = new HSSFWorkbook(file);
}
but fails with the following error message: Could not load file or assembly 'NPOI, Version=2.1.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1' or one of its dependencies. The system cannot find the file specified
But when I go into the script task, the reference is there and there are no errors.
If I comment out everything except the first line where I declare a HSSFWorkBook called wb it runs fine.
Have I added the references incorrectly or is adding references to a SSIS script task notoriously difficult?
As always any help is greatly appreciated.

For custom assemblies to be referenced and executed in Script Task, you have to add them to GAC. Here is an article with workflow.
Alternative approach - provide your own AssemblyResolver in Script task code.

Here is an example for the custom AssemblyResolver Approach, mentioned by Ferdipux.
The given solution was not working with user variables, so you would have to deal with the comments in the documentation to find a "non static" approach.
Additionally the posted solutions would not be working anymore when you deploy to a SQL Server 2017 instance and try to read the assemblies from a network share (System.NotSupportedException).
Therefore I replaced the LoadFile(path) with the UnsafeLoadFrom(path) call as workaround. Please use it only for your own or other wellknown assemblies, not downloaded assemblies from unknown authors, because this would be a security issue.
Here is the working code, the referenced DLL is "System.Web.Helpers.dll" and the network share path gets configured in the user variable "LibPath" (VS 2015,SQL Server 2017):
public System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string path = Variables.LibPath.ToString();
if (args.Name.Contains("System.Web.Helpers"))
{
return System.Reflection.Assembly.UnsafeLoadFrom(System.IO.Path.Combine(path, "System.Web.Helpers.dll"));
}
return null;
}
/// <summary>
/// This method is called once, before rows begin to be processed in the data flow.
/// </summary>
public override void PreExecute()
{
base.PreExecute();
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
...

static ScriptMain()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
//(string)Dts.Variables["User::CustomDLL"].Value;
if (args.Name.Contains("HtmlAgilityPack"))
{
string path = #"C:\Temp\";
return System.Reflection.Assembly.LoadFile(System.IO.Path.Combine(path, "HtmlAgilityPack.dll"));
//return System.Reflection.Assembly.UnsafeLoadFrom(System.IO.Path.Combine(path, "HtmlAgilityPack.dll"));
}
return null;
}

Related

Is there an way to add reference(dll) in parent path in C#?

== compile command ==
csc -r:"../Newtonsoft.Json.dll" test.cs
== exec command ==
mono test.exe
== exec result : dependency error ==
System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=11.0.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies.
"Newtonsoft.Json.dll" this file is located in parent path. so I added a reference about dll and compile succeeded, but when I executed the exe file, it failed to get dll reference I added.
And when I put cs file and dll file together in the same directory, it worked very well, but that's not what I wanted.
Is there a solution to add a reference from dll file which is located in parent path using command line interface?
I used csc for compiler and mono for execution.
Thanks.
References are pathless. What that means is that wherever the assembly resides, all your program knows is that it has a reference to Newtonsoft.Json, Version=x.x.x.x, Culture=... and so on. You can do some things with the application configuration (application.config or myprogram.exe.config) to organize things into subfolders (using the probing setting) or specify a URL location for the file (using the codebase setting). You can set up the environment to change the search path and so on.
Or you can add some runtime code that allows you to override the default behavior and the call Assembly.LoadFrom to provide a full path to the file. You can either do it as part of your initialization or in a handler for the AppDomain.AssemblyResolve event - which is generally better method since it will only be called when the assembly is actually needed.
For example:
using System.IO;
using System.Reflection;
static class ParentPathResolver
{
public static void Init()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(Resolve);
}
private static Assembly? Resolve(object? sender, ResolveEventArgs args)
{
var filename = new AssemblyName(args.Name).Name + ".dll";
var fullname = Path.GetFullPath(Path.Combine("..", filename));
if (File.Exists(fullname))
return Assembly.LoadFrom(fullname);
return null;
}
}
Of course you can add your own code to the Resolve method to search pretty much anywhere, just as long as you don't get caught in a resolution loop. I've used the AssemblyResolve event to do fun things like loading assemblies from compressed resources.

Forcing .Extensions namespace to load when embedded DLL will need it

I'm building a utility that uses Microsoft's DACPAC libraries. For the purpose of this tool, I want to embed all requisite libraries in the executable. It appears that when I execute DacServices.GenerateDeployScript() it's trying to use the Microsoft.SqlServer.Dac.Extensions library. The library is also embedded, but perhaps isn't being resolved with my EventHandler the way other DLLs are. My EventHandler is like this:
private static Assembly ResolveEventHandler(Object sender, ResolveEventArgs args)
{
//Debugger.Break();
String dllName = new AssemblyName(args.Name).Name + ".dll";
var assem = Assembly.GetExecutingAssembly();
String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName));
if (resourceName == null) return null;
using (var stream = assem.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
This works for resolving other items, but I believe that the likely issue is that the Microsoft.SqlServer.Dac namespace is making an execution time call to the .Extensions namespace and isn't able to resolve the namespace or the methods in it. I could be wrong, but I'm not sure what else could be the cause.
The calls to methods and classes in .Dac itself are being handled fine, so I know the EventHandler is working properly. I'm not really sure what to do and would appreciate any guidance. I've tried using Microsoft.SqlServer.Dac.Extenions at the top of the .cs file, but since I don't directly call anything in that namespace, it's grey and probably is ignored by the compiler.
Thanks!
Update:
I made a call to the .Extensions namespace in the code to force it to be read into memory prior to the failing call, though it appears that it already was. I set a breakpoint where the resolver kicks off. Just prior to it failing, it's trying to resolve .resource for each DLL, e.g. Microsoft.SqlServer.Dac.resource and Microsoft.SqlServer.TransactSql.ScriptDom.resource - all for DLLs embedded in the executable. The resolver doesn't see anything because there are no .resource files in the project, so nothing compiled into the manifest. Aren't these supposed to just be resident in memory while a DLL is being utilized? When the DLLs are all present in the same directory as the .exe, it functions fine, and also doesn't create temporary .resource files in the directory, so I'm unsure what I'm looking to resolve.
Update 2:
Using a PDB of the DAC libraries, it appears the failing line is:
IOperation operation = DacServices.CreateDeploymentArtifactGenerationOperation(OperationResources.GenerateDeployScriptCaption, (ErrorManager errorManager) => this.CreatePackageToDatabaseDeployment(package.PackageSource, targetDatabaseName, dacDeployOption, errorManager), (IDeploymentController controller, DeploymentPlan plan, ErrorManager errorManager) => DacServices.WriteDeploymentScript(streamWriter, controller, plan, errorManager), cancellationToken1, dacLoggingContext);
And the resulting exceptions are:
The extension type Microsoft.SqlServer.Dac.Deployment.Internal.InternalDeploymentPlanExecutor could not be instantiated.
and
The extension type Microsoft.SqlServer.Dac.Deployment.Internal.InternalDeploymentPlanModifier could not be instantiated.

DLL cannot be found because the file name is different than the one referenced?

I have been baffled on how can this be happening.
So heres the deal, im trying to play a V2M chiptune with a dll called NV2.dll
Its referenced, and I used its functions like normal.
BUT when i try to run the application i get
Unable to load DLL 'V2.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
The referenced DLL i used is called NV2, I have checked it, the assembly goes by NV2, not V2, why is it trying to load a dll with a different name?!
So obviously, there is a reference to V2.dll inside the NV2.dll. Either the documentation is just shitty or you didn't notice that part, but inside the constructor of the NV2 class they try to write that V2.dll, which is saved inside the Resources of the dll as a byte[] V2 to disk, namely to the directory C:\Windows\system32. Code:
public NV2()
{
List<WeakReference> _ENCList = NV2.__ENCList;
Monitor.Enter(_ENCList);
try
{
NV2.__ENCList.Add(new WeakReference(this));
}
finally
{
Monitor.Exit(_ENCList);
}
//Here comes the part that writes the resources
FileStream fileStream = new FileStream(string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.System), "\\V2.dll"), FileMode.OpenOrCreate);
fileStream.Write(Resources.V2, 0, checked((int)Resources.V2.Length));
fileStream.Close();
}
Meaning that either in your application you first have to do a
var engine = new NV2(); //triggers the constructor code
to trigger that, or you go hardcore on that and dump the byte[] from their dll.
I've dumped that file here for you and zipped it: http://www.file-upload.net/download-11263190/V2.zip.html
(You could have done that by saving the project using teleriks decompiler, fixing the errors in the ressources, changing the project to a console project, then coding a Main function like)
using System.IO;
using NV2.My.Resources;
namespace NV2
{
class MainClass
{
static void Main(string[] args)
{
FileStream fileStream = new FileStream("V2.dll", FileMode.OpenOrCreate);
fileStream.Write(Resources.V2, 0, checked((int)Resources.V2.Length));
fileStream.Close();
}
}
}

c# Class Library Project - Load DLL from same folder?

I'm working on a plugin for a existing C# .NET Program. It's structured in a manner where you put your custom .dll file in Program Root/Plugins/your plugin name/your plugin name.dll
This is all working well, but now I'm trying to use NAudio in my project.
I've downloaded NAudio via Nuget, and that part works fine, but the problem is that it looks for the NAudio.dll in Program Root, and not in the folder of my plugin.
This makes it hard to distribute my plugin, because it would rely on users dropping the NAudio.dll in their Program Root in addition to putting the plugin into the "Plugins" folder.
Source:
SettingsView.xaml:
<Button HorizontalAlignment="Center"
Margin="0 5"
Width="120"
Command="{Binding SoundTestCommand,
Source={StaticResource SettingsViewModel}}"
Content="Sound Test" />
SettingsViewModel.cs:
using NAudio.Wave;
.
.
.
public void SoundTest()
{
IWavePlayer waveOutDevice;
WaveStream mainOutputStream;
WaveChannel32 inputStream;
waveOutDevice = new WaveOut();
mainOutputStream = new Mp3FileReader(#"E:\1.mp3");
inputStream = new WaveChannel32(mainOutputStream);
inputStream.Volume = 0.2F;
waveOutDevice.Init(mainOutputStream);
waveOutDevice.Play();
}
How can I get C# to look for NAudio in Program Root/Plugins/my plugin name/NAudio.dll instead of looking for it in Program Root/NAudio.dll ?
I'm using VS Express 2013, Target Framework is 4.5 and Output type is Class Library.
Edit:
I found 2 ways to make this work ( I'm not sure what the pros and cons of both methods are - if anyone knows I would appreciate additional information ).
Using the NuGet Package Costura.Fody.
After installing the NuGet package, I simply had to set all other References "Copy Local" to "False" and then set "Copy Local" for NAudio to "True".
Now when I build, the NAudio.dll is compressed and added to my own DLL.
Using the AssemblyResolver outlined below.
It didn't work right away though, so here is some additional information that may help anyone facing the same issue:
I put Corey's code as he posted it into the Helpers folder.
My entry point is Plugin.cs, the class is public class Plugin : IPlugin, INotifyPropertyChanged
In there, the entry method is public void Initialize(IPluginHost pluginHost), but simply putting PluginResolver.Init(path) did not work.
The host program uses WPF and is threaded and I had to use a dispatcher helper function of the host program to get it to work: DispatcherHelper.Invoke(() => Resolver.Init(path));
As mentioned, I'm currently unsure which method to use, but I'm glad I got it to work. Thanks Corey!
You can use the PATH environment variable to add additional folders to the search path. This works for native DLLs, but I haven't tried to use it for .NET assemblies.
Another option is to add a hook to the AssemblyResolve event on the current application domain and use a custom resolver to load the appropriate assembly from wherever you find it. This can be done at the assembly level - I use it in NAudio.Lame to load an assembly from a resource.
Something along these lines:
public static class PluginResolver
{
private static bool hooked = false;
public static string PluginBasePath { get; private set; }
public static void Init(string BasePath)
{
PluginBasePath = BasePath;
if (!hooked)
{
AppDomain.CurrentDomain.AssemblyResolve += ResolvePluginAssembly;
hooked = true;
}
}
static Assembly ResolvePluginAssembly(object sender, ResolveEventArgs args)
{
var asmName = new AssemblyName(args.Name).Name + ".dll";
var assemblyFiles = Directory.EnumerateFiles(PluginBasePath, "*.dll", SearchOption.AllDirectories);
var asmFile = assemblyFiles.FirstOrDefault(fn => string.Compare(Path.GetFileName(fn), asmName, true) == 0);
if (string.IsNullOrEmpty(asmFile))
return null;
return Assembly.LoadFile(asmFile);
}
}
(Usings for the above: System.IO, System.Reflection, System.Linq)
Call Init with the base path to your plugins folder. When you try to reference an assembly that isn't loaded yet it will search for the first file that matches the base name of the assembly with dll appended. For instance, the NAudio assembly will match the first file named NAudio.dll. It will then load and return the assembly.
No checking is done in the above code on the version, etc. and no preference is given to the current plugin's folder.

.NET Reference dll from other location

I'm making a program depending on some DLLs included in a third party program. I'm not allowed to distribute these DLLs myself. The third party program must be installed for my program to work.
How can i make a reference to these DLLs? I know the exact location of them through a registry key set by the program.
I have tried to add the files in Project->References and set CopyLocal to false but when i start i then get a FileNotFoundException "Could not load file or assembly".
I have tried to add an event to AppDomain.CurrentDomain.AssemblyResolve and load the files there but the problem is that i get the exception before my program even starts. Even if i put a breakpoint on the first line the exception will be thrown before the breakpoint is hit.
From C# 3.0 in a Nutshell, 3rd edition, by Joseph and Ben Albahari, p. 557-558:
Deploying Assemblies Outside the Base Folder
Sometimes you might choose to deploy assemblies to locations other than the application base directory [...] To make this work, you must assist the CLR in finding the assemblies outside the base folder. The easiest solution is to handle the AssemblyResolve event.
(We can ignore the fact that in your case, someone other than you is deploying the assemblies.)
Which you tried. But a very important clue follows somewhat later. Read the two code comments:
public static void Loader
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += FindAssem;
// We must switch to another class before attempting to use
// any of the types in C:\ExtraAssemblies:
Program.Go();
}
static Assembly FindAssem(object sender, ResolveEventArgs args)
{
string simpleName = new AssemblyName(args.Name).Name;
string path = #"C:\ExtraAssemblies\" + simpleName + ".dll";
if (!File.Exists(path)) return null;
return Assembly.LoadFrom(path);
}
}
public class Program
{
public static void Go()
{
// Now we can reference types defined in C:\ExtraAssemblies
}
}
As you see, the class where you resolve the external assemblies must not refer to any type from any of the external DLLs anywhere. If it did, code execution would stop way before your AssemblyResolve ever gets a chance to run.
Your app bombs because the JIT compiler is the first one that needs to load the assembly. You'll need to carefully avoid using types from the assembly in your Main() method. That's not hard to do, just write another Main method and give it an attribute that tells the jitter that it should never inline that method. Without the attribute it may still bomb in the Release build when the optimizer inlines the method. Like this:
using System;
using System.Runtime.CompilerServices;
class Program {
static void Main(string[] args) {
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
DelayedMain(args);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void DelayedMain(string[] args) {
// etc..
}
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
// etc...
}
}
Before you commit doing it this way, do contact the vendor and ask for recommendations. There has to be an easier way to exercise your license rights. The GAC would be a common choice, maybe you just need to run gacutil on your dev machine.

Categories