Deserialization in a CLR Function fails - c#

I have written some routines for the phonetic conversion of a text in C#. These routines require a bunch aof defined rules for the conversion (search-string/replace-string). The idea was to store thes rules as an embedded ressource within the assembly and then read the rules from it. The starting point for the the deserialization is as follows
public static phonet42n.Core.Rules Deserialize(phonet42n.Core.Rules.Ressources ressource)
{
string ressourceName;
phonet42n.Core.Rules returnValue;
System.Xml.XmlReader reader;
System.Xml.Serialization.XmlSerializer xmlSerializer;
phonet42n.Core.SerializableRules serializeableRules;
returnValue = new phonet42n.Core.Rules();
switch (ressource)
{
case Ressources.German_01:
ressourceName = RESSOURCE_XML_GERMAN_01;
break;
case Ressources.German_02:
ressourceName = RESSOURCE_XML_GERMAN_02;
break;
default:
ressourceName = RESSOURCE_XML_GERMAN_01;
break;
}
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ressourceName))
{
using (reader = System.Xml.XmlReader.Create(stream))
{
xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(phonet42n.Core.SerializableRules));
serializeableRules = (phonet42n.Core.SerializableRules)xmlSerializer.Deserialize(reader);
}
}
foreach (phonet42n.Core.Rule entry in serializeableRules.Rules)
{
if (entry.SearchString != null && entry.SearchString.Length > 0)
{
returnValue.Add(entry.Index, entry);
}
}
return returnValue;
}
The application works fine when executed in an regular executable.
When executing the registered function in SQL Server I get the following error:
SELECT [dbo].[Phonet42n]('mayer', 1)
produces...
Meldung 6522, Ebene 16, Status 1, Zeile 22
.NET Framework-Fehler beim Ausführen der benutzerdefinierten Routine oder des benutzerdefinierten Aggregats 'Phonet42n':
System.InvalidOperationException: Fehler im XML-Dokument (3,4). ---> System.MethodAccessException: Fehler beim Versuch der Methode "Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read8_SerializableRules(Boolean, Boolean)", auf Methode "phonet42n.Core.Rule..ctor()" zuzugreifen.
System.MethodAccessException:
bei System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
bei System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
bei System.Activator.CreateInstance(Type type, Boolean nonPublic)
bei System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)
bei System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
bei System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read8_SerializableRules(Boolean isNullable, Boolean checkType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read19_Rules()
System.InvalidOperationException:
bei System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
bei System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
bei phonet42n.Core.Rules.Deserialize(Ressources ressource)
bei phonet42n.Core.Rules..ctor(Ressources ressource, Characters characters)
bei phonet42n.Core.HashTable..ctor(Ressources ressource)
bei phonet42n.Core.Match..ctor(Re...
Any idea?

Whenever there are security exceptions, you can first try setting the Assembly to PERMISSION_SET = EXTERNAL_ACCESS, and if that doesn't work you can try UNSAFE. However, if one is attempting to dynamically load an Assembly, then that is supposed to be forbidden even for Assemblies marked as UNSAFE.
Since the issue here is of wanting to include a set of rules, that could probably be done in another Assembly. Then the main Assembly can reference the one containing the rules, and you just load the one containing the rules into SQL Server first. This would allow both Assemblies to remain marked as SAFE.
Of course, if there is no pressing need to keep the rules separate, then you could also just place them directly into a Collection in a Class.

I did an undo on evreything except the constructor part (private/public). With or without signing the assembly I get the following message (10327):
CREATE ASSEMBLY for assembly 'phonet42n.Core' failed because assembly
'phonet42n.Core' is not authorized for PERMISSION_SET =
EXTERNAL_ACCESS. The assembly is authorized when either of the
following is true: the database owner (DBO) has EXTERNAL ACCESS
ASSEMBLY permission and the database has the TRUSTWORTHY database
property on; or the assembly is signed with a certificate or an
asymmetric key that has a corresponding login with EXTERNAL ACCESS
ASSEMBLY permission.
Unfortunately I'm not familiar with signing an assembly: I just checked the checkbox in the property pages of the project properties:
Don't know whether additional steps are missing.
Anyhow, as suggested in the message I then tried to create an asymmetric key using the following statements:
USE master;
GO
IF EXISTS (SELECT * FROM [sys].[syslogins] WHERE [sid] = SUSER_SID('SQLCLRTestLogin'))
BEGIN
PRINT 'Dropping Login...'
DROP LOGIN [SQLCLRTestLogin]
PRINT 'End'
END
GO
IF EXISTS (SELECT * FROM [sys].[asymmetric_keys] WHERE [name] = 'SQLCLRTestKey')
BEGIN
PRINT 'Dropping Asymmetric Key...'
DROP ASYMMETRIC KEY [SQLCLRTestKey]
PRINT 'End'
END
GO
BEGIN
PRINT 'Creating Login...'
CREATE ASYMMETRIC KEY [SQLCLRTestKey] FROM EXECUTABLE FILE = 'D:\phonet42n.net\Core\bin\Debug\phonet42n.Core.dll'
PRINT 'End'
PRINT 'Creating Asymmetric Key...'
CREATE LOGIN [SQLCLRTestLogin] FROM ASYMMETRIC KEY [SQLCLRTestKey]
PRINT 'End'
PRINT 'Granting Access...'
GRANT EXTERNAL ACCESS ASSEMBLY TO [SQLCLRTestLogin];
PRINT 'End'
END
GO
In case of a signed assembly these statements succeed, whereas in case of an unsigned assembly I get the following Error message (15208):
The certificate, asymmetric key, or private key file does not exist or
has invalid format.
By the way.. don't know whether this matters. The target .NET Framework aof the assemly is 4.5. Finally... coming to the initial error message: Adwaenyth was right with the missing public default construtor. The message was so far misleading, as the missing constructor does not affect the registrion of the assembly. You get the message only after a successfull registration at runtime, when making a call for the function phonet42n.
I really don't know, whether this is the solution by design. And I cannot not tell whether I opend the door with that for any potential threads.
I just can emphasize the the Stairway series articles especially on on SQLCLR. Excellent! It helps to get a understanding how SQLCLR works and what it is good for.
And (really) finally: the overall outcome for me is, that I will extract the rules from the XML and put load them directly by populating a collection as suggested by srutzky. Will be probably much more performant.

Related

UWP: ILT0032: Failed to compile serialization code

When I compile my UWP app in Release mode, I get the following errors:
System.ArgumentException: An item with the same key has already been
added. at System.ThrowHelper.ThrowArgumentException(ExceptionResource
resource) at System.Collections.Generic.Dictionary2.Insert(TKey key,
TValue value, Boolean add) at
System.Collections.Generic.Dictionary2.Add(TKey key, TValue value) at
System.Xml.Serialization.XmlSerializationReaderCodeGen.WriteLiteralStructMethod(StructMapping
structMapping) at
System.Xml.Serialization.XmlSerializationReaderCodeGen.WriteStructMethod(StructMapping
structMapping) at
System.Xml.Serialization.XmlSerializationReaderCodeGen.GenerateMethod(TypeMapping
mapping) at
System.Xml.Serialization.XmlSerializationCodeGen.GenerateReferencedMethods()
at
System.Xml.Serialization.XmlSerializationReaderCodeGen.GenerateEnd(String[]
methods, XmlMapping[] xmlMappings, Type[] types) at
System.Xml.Serialization.TempAssembly.GenerateAssembly(XmlMapping[]
xmlMappings, Type[] types, String defaultNamespace, Evidence evidence,
XmlSerializerCompilerParameters parameters, Hashtable assemblies,
String outputDir, String assemblyNameBase, IEnumerable1
referenceDirectories, String intermediateDir, Boolean loadAssembly) at
System.Xml.Serialization.XmlSerializer.GenerateSerializer(Type[]
types, XmlMapping[] mappings, CompilerParameters parameters, String
outputDir, String assemblyNameBase, IEnumerable1
referenceDirectories, String intermediateDir, Boolean loadAssembly) at
System.Xml.Serialization.XmlSerializer.GenerateSerializer(Type[]
types, String outputDir, String assemblyNameBase, IEnumerable1
referenceDirectories, String intermediateDir, List1 wcfSerializers,
Boolean loadAssembly) at
SerializationAssemblyGenerator.Program.Main(String[] args) ILT0032:
Failed to compile serialization code. See the build log for error
details.
I found this link describing the same problem. But the proposed solution to remove KnownType Attributes of primitive types, did not apply to my problem.
The error disappears, when I uncheck "Compile with .NET Native tool chain", but then the binary analyzer fails with the following errors:
dotnet-Microsoft.XmlSerializer.Generator.dll has failed the
AppContainerCheck check.
The generated XmlSerialization assemblies don't work (no faster serialization) for me and I would turn the generation of these assemlies off, if I know how.
Does anyone know how to fix this Release compile problem with UWP?

Using reflection to instantiate a class from an external assembly

I am currently trying to develop a method of running test classes in external projects programmatically using reflection. Here is a simplified chunk of code that should showcase my problem.
string pathToDLL = #"C:\Path\To\Test\Project\UnitTests.dll";
IEnumerable<Type> testClasses = assembly.GetExportedTypes();
Type testClass = testClasses.First();
object testClassInstance = assembly.CreateInstance(testClass.FullName);
This code throws the following exception:
'assembly.CreateInstance(testClass.FullName)' threw an exception of type 'System.Reflection.TargetInvocationException'
Data: {System.Collections.ListDictionaryInternal}
HResult: -2146232828
HelpLink: null
InnerException: {System.IO.FileNotFoundException: Could not load file or assembly 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Project.UnitTests.TestClass..ctor()}
Message: "Exception has been thrown by the target of an invocation."
Source: "System.Private.CoreLib"
StackTrace: " at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)\r\n at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, Boolean nonPublic)\r\n at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n at System.Reflection.Assembly.CreateInstance(String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n at System.Reflection.Assembly.CreateInstance(String typeName)"
In the stack trace it states that it "Could not load file or assembly 'Project.Core...'".
This project is one that the target DLL references directly (one that it tests). Does anyone know why this won't be able to pick up these DLLs automatically?
I've investigated ways of solving this problem:
It could be the way that the dlls have been compiled - this can be changed as I am in control of this - which is currently by running dotnet build */*/project.json at solution level. This successfully compiles everything, and all of the relevant DLLs seem to be populated in the bin folder. I've also investigated whether or not changing to dotnet publish or dotnet build */*/project.json --configuration Release though neither seem to have helped.
I've also looked into using different methods of compilation like Activator.CreateInstance again no dice.
I don't seem to see a way to load multiple DLLs into the same Assembly class so that I can control the references. Since AppDomains have been removed from .NET Core this doesn't look like it is possible, though I may be mistaken/looking in the wrong area.
If what I'm doing doesn't seem like it will be possible, does anyone know if this kind of functionality can be achieved using a different method? I.e. Roslyn?
I just thought that I would update this question with the solution that I managed to find, just in case someone else was having the same problem as I was. Though I would like to thank #Emrah Süngü for pointing me in the right direction.
Emrah drew my attention to the fact that I needed to import the dependencies of the DLL that I wanted to load in order to invoke the classes stored within it. One way to do this is to extend your app.config in order to import those dependencies - however I wanted to do this at runtime (with projects that I didn't know I was going to run prior starting the program) so I needed to look for another solution.
If you aren't using .NET Core this is relatively simple since AppDomains can be used to load all of the dependencies and execute your code. However, since this has been removed from .NET Core I needed to find another solution that would be compatible.
I toyed with the idea of running a separate process (or Powershell), and changing the working directory so that the process was running in the directory that stored all of the dependencies it needed. However, I couldn't find a way of doing this that allowed me to react to the outcome of running the methods.
Later I investigated manipulating the AssemblyLoadContext class, but (at the time of writing) there is little to no documentation on how this class. I did find this answer which was able to helped significantly... https://stackoverflow.com/a/37896162/6012159
In order for it to work I did have to make a slight change, instead of creating a new AssemblyLoader every time (which would cause exceptions to be thrown when trying to invoke methods within the Assembly), I reused the AssemblyLoader each time (Which removed this problem).
public class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
public AssemblyLoader(string folderPath)
{
this.folderPath = folderPath;
}
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
if (res.Count > 0)
{
return Assembly.Load(new AssemblyName(res.First().Name));
}
else
{
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(apiApplicationFileInfo.FullName))
{
return this.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
Which can be use to load assemblies like this:
string directory = #"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\";
string pathToDLL = #"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\project.dll";
AssemblyLoader al = new AssemblyLoader(directory);
Assembly assembly = al.LoadFromAssemblyPath(pathToDLL);
I am assuming that "UnitTests.dll" depends on (references) other dll(s) and your program does not know where to look for those referenced dll(s). You should (in fact have to) tell it to where to look for those dll(s) as well. By default is the same directory as your EXE. You can use app.config for telling where else to look. For Load() to succeed dependant dll(s) must be stored in your app's probing path.
That is the reason why you are getting an error.
Here you can find related article.
https://msdn.microsoft.com/en-us/library/823z9h8w.aspx

Hangfire + Crystal Reports email?

I'm trying to make it so that we can generate certain reports daily and email them to a bunch of people in a list.
I've tested out Hangfire for recurring jobs and it works well. So that is not an issue. But I'm trying to create a report from my existing Crystal Report file (.rpt). Basically I want to make it so that when this job gets executed, the code would create the report, save it to the disk in a specified path as a PDF, and then I can email it to people as an attachment. So there is no need to be able to see the report on a web page. The idea is to literally just generate the report in the code behind, save it as a PDF, and email it from the code behind after it is saved.
The issue I'm having has to do with the actual generating and saving of the crystal report. Btw, I'm generating an excel file in the test but I'd change it to PDF for the actual report. This is what I have so far for generating the report:
string path = #"Save folder relative-path";
//"report" is declared at the class level and instantiated below.
report = new ReportDocument();
report.SetDatabaseLogon(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"]);
report.Load(Server.MapPath("Relative path to the report"));
report.SetDataSource(GetDataSet()); //This gets the dataset filled with data for the report
try
{
ExportOptions options = new ExportOptions();
DiskFileDestinationOptions diskFileOptions = new DiskFileDestinationOptions();
ExcelFormatOptions excelOptions = new ExcelFormatOptions();
diskFileOptions.DiskFileName = path + "Test Report.xls";
options.ExportDestinationType = ExportDestinationType.DiskFile;
options.ExportFormatType = ExportFormatType.Excel;
options.ExportDestinationOptions = diskFileOptions;
options.ExportFormatOptions = excelOptions;
report.Export();
/*
This is where I would call a method to email the report to people
*/
}
catch (Exception ex)
{
Console.WriteLine("Error generating report: " + ex.Message);
}
This code is in a method which is being called at Application_Start in the global.asax file of the web application. When I run the application, the job fails and throws this error when I look under the failed jobs in the Hangfire dashboard even though I know the path in my code is correct:
System.IO.FileNotFoundException Could not load file or assembly
'App_global.asax.twm32qri, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null' or one of its dependencies. The system cannot
find the file specified.
System.IO.FileNotFoundException: Could not load file or assembly
'App_global.asax.twm32qri, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null' or one of its dependencies. The system cannot
find the file specified. File name: 'App_global.asax.twm32qri,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' at
System.RuntimeTypeHandle.GetTypeByName(String name, Boolean
throwOnError, Boolean ignoreCase, Boolean reflectionOnly,
StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean
loadTypeFromPartialName, ObjectHandleOnStack type) at
System.RuntimeTypeHandle.GetTypeByName(String name, Boolean
throwOnError, Boolean ignoreCase, Boolean reflectionOnly,
StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean
loadTypeFromPartialName) at System.Type.GetType(String typeName,
Boolean throwOnError, Boolean ignoreCase) at
Hangfire.Storage.InvocationData.Deserialize()
WRN: Assembly binding logging is turned OFF. To enable assembly bind
failure logging, set the registry value
[HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There
is some performance penalty associated with assembly bind failure
logging. To turn this feature off, remove the registry value
[HKLM\Software\Microsoft\Fusion!EnableLog].
EDIT:
I have another error I'm getting too. This one has something to do
with loading the report file.
Failed An exception occurred during performance of the job.
CrystalDecisions.CrystalReports.Engine.LoadSaveReportException
Invalid report file path.
CrystalDecisions.CrystalReports.Engine.LoadSaveReportException:
Invalid report file path. at
CrystalDecisions.CrystalReports.Engine.ExceptionThrower.ThrowEngineException(String
messageID, EngineExceptionErrorID id) at
CrystalDecisions.CrystalReports.Engine.ReportDocument.Load(String
filename, OpenReportMethod openMethod, Int16 parentJob) at
CrystalDecisions.CrystalReports.Engine.ReportDocument.EnsureLoadReport()
at
CrystalDecisions.CrystalReports.Engine.ReportDocument.SetDatabaseLogon(String
user, String password) at Intranet.Global.GenerateReport() in
path\Global.asax.cs:line 98
Figured out the issue. I apparently needed to use a CrystalReportViewer object and set the ReportDocument object as its source. The CrystalReportViewer class is in the CrystalDecisions.Web namespace.
using (ReportDocument report = new ReportDocument())
{
using (CrystalReportViewer viewer = new CrystalReportViewer())
{
string path = System.Web.Hosting.HostingEnvironment.MapPath(#"Destination path here");
report.Load(System.Web.Hosting.HostingEnvironment.MapPath(#"Path to .rpt file here"));
report.SetDatabaseLogon(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"]);
string file = path + "TestReport.xls";
//These two lines below are important. The report won't generate without them.
viewer.ReportSource = report;
viewer.RefreshReport();
//Just deleting the file if it exists.
if (File.Exists(file))
File.Delete(file);
report.ExportToDisk(ExportFormatType.Excel, diskFileOptions.DiskFileName);
}
}

How can I specify a [DllImport] path at runtime (embedded inside .msi file)

I want to import a dll into my C# project to call it's functions using DllImport.
I have my dll that needs to be imported as part of my .msi file.
It does work when I specify the full path to the DLL, but that is outside .msi file.
I am facing the dllNotFoundException problem.
<Binary Id="CustomAction2.CA.dll"
src="../artifacts/CustomAction2.CA.dll" />
<CustomAction Id="Install"
Execute="deferred"
BinaryKey="CustomAction2.CA.dll"
DllEntry="CustomAction1" />
<CustomAction Id="InstallWithProperty"
Property="Install"
Value="location=[DEFAULT_INSTALLDIR]$FULL_NAME;name=myDll.dll"
Execute="immediate"/>
<InstallExecuteSequence>
<Custom Action="InstallWithProperty" After="InstallFiles"/>
<Custom Action="Install" After="InstallWithProperty" />
</InstallExecuteSequence>
when Custom action is invoked it says
I get below exception
Exception thrown by custom action: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.DllNotFoundException: Unable to load DLL 'myDll.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E) at CustomAction2.CustomActions.ConfigDriver(IntPtr hwndParent, UInt16 fRequest, String lpszDriver, String lpszArgs, String lpszMsg, UInt16 cbMsgMax, Int64& pcbMsgOut) at CustomAction2.CustomActions.CustomAction1(Session session) --- End of inner exception stack trace --- at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner) at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture) at Microsoft.Deployment.WindowsInstaller.CustomActionProxy.InvokeCustomAction(Int32 sessionHandle, String entryPoint, IntPtr remotingDelegatePtr) CustomAction Install returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
Can somebody help. I want to use myDll.dll for further installation, which is part of .msi file.
If I understand you correctly, you have MSI file which has .DLL inside it and you want to pInvoke it?
Whatever you're doing this for, it sounds bad. You might rehink your approach,
however, for an actual answer:
To get you started, here is a little bit of C# code:
String inputFile = #"C:\\Install1.msi";
// Get the type of the Windows Installer object
Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
// Create the Windows Installer object
WindowsInstaller.Installer installer = (WindowsInstaller.Installer)Activator.CreateInstance(installerType);
// Open the MSI database in the input file
Database database = installer.OpenDatabase(inputFile, MsiOpenDatabaseMode.msiOpenDatabaseModeReadOnly);
// Open a view on the Property table for the version property
View view = database.OpenView("SELECT * FROM Property WHERE Property = 'ProductVersion'");
// Execute the view query
view.Execute(null);
// Get the record from the view
Record record = view.Fetch();
// Get the version from the data
string version = record.get_StringData(2);
Now mind you, it does not extract file from the MSI, but it's good start. In order to actually extract from MSI, you need to change the code a little.
Here is VB code that does it, note the SQL-syntax like query.
Function ExtractIcon(IconName, OutputFile)
Const msiReadStreamAnsi = 2
Dim oDatabase
Set oDatabase = Session.Database
Dim View
Set View = oDatabase.OpenView("SELECT * FROM Icon WHERE
Name = '" & IconName & "'")
View.Execute
Dim Record
Set Record = View.Fetch
Dim BinaryData
BinaryData = Record.ReadStream(2, Record.DataSize(2),
msiReadStreamAnsi)
Dim FSO
Set FSO = CreateObject("Scripting.FileSystemObject")
Dim Stream
Set Stream = FSO.CreateTextFile(OutputFile, True)
Stream.Write BinaryData
Stream.Close
Set FSO = Nothing
End Function
You need to convert it into C#, it's going to be easy task.
After that, you need to actually use dynamic pInvoke. Ps you can also use normal pInvoke if you extract the DLL into your projects folder, and use relative paths.
To use dynamic pInvoke:
follow this article; http://blogs.msdn.com/b/jmstall/archive/2007/01/06/typesafe-getprocaddress.aspx
It's little neat code:
using(UnmanagedLibrary lib = new UnmanagedLibrary("kernel32") // becomes call to LoadLibrary
{
Action<String> function = lib.GetUnmanagedFunction<Action<String>>("DeleteFile"); // GetProcAddress
function(#"c:\tmp.txt");
} // implict call to lib.Dispose, which calls FreeLibrary.
If you feel like a god, you can load the DLL into memory never extracting it anywhere. This is useful if you have amazing DLL that should be always hidden from anyone. It's just for information - it's pretty hard ^_^

Unknown name ComException on a referenced dll in a C# project

I'm writting a C# application which use one of the dll written in C++ as reference. I can use that dll namespace and my project compiles fine. However when I run it I keep getting error in one of the line where I assign a property its value. The exception error I got was the following:
A first chance exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll
2013-06-03 12:26:32 - Unknown name. (Exception from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))
2013-06-03 12:26:32 - at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters)
2013-06-03 12:26:32 - at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
2013-06-03 12:26:32 - at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
2013-06-03 12:26:32 - at sstObjTapLib._DsstObjTap.set_CapCode(String )
2013-06-03 12:26:32 - at hpOutput.CTapPagerCom.MessageLoop() in c:\shs\Arial 8.1\XmarkClient\hpOutput\CTapPagerCom.cs:line 225
I initiated the dll object using the following code:
m_ctlTap = new sstObjTapLib.ctlTap();
Below are the three screenshots:
The properties of the DLL I use
The object browser of the dll I use
A breakpoint that shows where the exception occurs.
Looking at the stack error above, it seems that .NET is trying to call some method that does not exist, but I'm just setting a property value. Can somebody point me in the right direction or what I might have missed?
A noted point: While in debugging, I'm looking at my loaded module view, however I don't see this dll name one in the list of my loaded module.
I'm not sure why but the computer I worked with had this same dll installed. I know this by viewing it using the third party tool called RegDllView .
So I went in, unregister the dll I was using, verify it is also gone from the registry and then re-register it again. Clean out my project and recompile everything, and got the exact code to work.
Looking at what I did I think for some reason the application did not recognize the GUID supplied to point to the proper COM.

Categories