BinaryFormatter used in Interop assemblies throws UnsupportedException - c#

Microsoft discourages the use of BinaryFormatter because it poses security problems. See: BinaryFormatter Obsoletion Strategy.
I have a .NET 6.0 WinForms code which uses the Microsoft.Office.Interop.Access.Dao interop assembly. I need it to insert an image into the Data field the Microsoft Access' system table MSysResources. This field has an Attachment Data Type. This is a multi-valued field. Using DAO is the only way of writing to this field. My (somewhat shortened) code goes like this (note: this code did work before I migrated to .NET 6.0):
using Microsoft.Office.Interop.Access.Dao;
namespace CySoft.RibbonPro.Services;
public class AccessImageResourceLoader : IAccessImageResourceLoader
{
public void UpdateImages(string accdbFile, IEnumerable<KeyValuePair<string, Image>> images)
{
var dbe = new DBEngine(); // <====== This line throws the UnsupportedException =====
Database db = dbe.OpenDatabase(accdbFile);
Recordset rs = rs = db.OpenRecordset("SELECT * FROM MSysResources WHERE 0=1", R
ecordsetTypeEnum.dbOpenDynaset, 0, LockTypeEnum.dbOptimistic);
rs.AddNew();
rs.Fields["Type"].Value = "img";
rs.Fields["Name"].Value = name;
rs.Fields["Extension"].Value = ext;
Recordset2 rsAttachment = (Recordset2)rs.Fields["Data"].Value;
rsAttachment.AddNew();
Field2 dataField = (Field2)rsAttachment.Fields["FileData"];
dataField.LoadFromFile(imageInfo.Key);
rsAttachment.Update();
rs.Update();
rs.Close();
db.Close();
}
}
The details are for illustration only. The first code line creating the DBEngine throws the exception:
BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.
The call stack is:
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.DeserializeUsingBinaryFormatter(StreamWrapper wrappedStream, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.Deserialize(Stream o, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.RuntimeLicenseContext.GetSavedLicenseKey(Type type, Assembly resourceAssembly)
at System.ComponentModel.LicenseManager.LicenseInteropHelper.GetCurrentContextInfo(Type type, Boolean& isDesignTime, String& key)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Internal.Runtime.InteropServices.LicenseInteropProxy.GetCurrentContextInfo(RuntimeTypeHandle rth, Boolean& isDesignTime, IntPtr& bstrKey)
at CySoft.RibbonPro.Services.AccessImageResourceLoader.UpdateImages(String accdbFile, IEnumerable`1 images) in C:\Users\Oli\Documents\Proj\CySoft\CySoft.RibbonPro\CySoft.RibbonPro\Services\AccessImageResourceLoader.cs:line 21
Where AccessImageResourceLoader.cs:line 21 is var dbe = new DBEngine();
Microsoft wants people to use another type of serialization like JSON or XML. This is not an option in this case, because I am not using it directly. It is Microsoft's own COM library which uses it.
Question:
How can I insert or update a record using Access' Attachment data type in .NET 6+?
My Attempts
I have tried to do it with System.Data.OleDb. I can read the Attachment with OleDb. But any attempt to write to this field using OleDb throws an exception.
Setting the <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization> tag in the project file does not help.
Settings the same configuration property in runtimeConfig.template.json does not help either.
I know that I could solve the problem by using Access automtation via an interop assembly. But it has the disadvantage to open the Microsoft Access application. Inserting the image through a database connection is much more elegant and did work before I migrated to .NET 6.0.

You can see here there is a switch to allow the binary serializer for the licenses file
https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContextSerializer.cs#L20
which is being read by the GetSavedLicenseKey method here
https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContext.cs#L84-L89
You can set this switch earlier on before initializing the DBEngine object by calling this:
AppContext.SetSwitch("System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization", true);
I haven't tried it myself but it should work.
This runtime switch might also be settable in the csproj file as described here
https://github.com/dotnet/runtime/blob/main/docs/workflow/trimming/feature-switches.md
Any feature-switch which defines property can be set in csproj file or on the command line as any other MSBuild property. Those without predefined property name the value can be set with following XML tag in csproj file.
<RuntimeHostConfigurationOption Include="<AppContext-Setting>"
Value="false"
Trim="true" />
Final words: There is even more detail on upgrading to .NET 6.0 at this blog which has another method for this flag explained.
https://www.textcontrol.com/blog/2021/12/21/migrate-a-windows-forms-desktop-application-to-dotnet-6/?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral

Related

Cannot deserialize ProtoBuf message in child process, works fine in parent

I'm trying to send a protobuf message to a child process. Deserialization works fine in the parent, like this:
var deserialized = Serializer.Deserialize(typeof(ChildProcessTestMessage), new MemoryStream(new byte[] { 8, 1 }));
[ProtoContract]
public class ChildProcessTestMessage
{
[ProtoMember(1)]
public int Int { get; set; }
}
I have hardcoded the bytes 0x0801 in this sample code and in the child process in order to make sure that the bytes are correct. I obtained these bytes by serializing a test instance.
Deserialization code in the child is different in one important way: The parent communicates the type to use for deserialization over a pipe. The type can be loaded successfully in the child. I then call protobuf-net like this:
var (assemblyName, typeName, methodName, argument) = protocol.ReadExecuteMethod();
Assembly assembly;
try
{
var name = new AssemblyName(assemblyName);
assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Directory.GetCurrentDirectory(), name.Name + ".dll"));
}
catch (Exception ex)
{
//...
}
var type = assembly.GetType(typeName);
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);
var parameterType = method.GetParameters().Single().ParameterType;
var message = Serializer.Deserialize(new MemoryStream(new byte[] { 8, 1 }), parameterType);
The child reports the following error:
Failed to invoke method 'TestMethod': ProtoBuf.ProtoException: Invalid
wire-type (Varint); this usually means you have over-written a file
without truncating or setting the length; see
Using Protobuf-net, I suddenly got an exception about an unknown wire-type at
ProtoBuf.ProtoReader.State.ThrowProtoException(String message) in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 764
at ProtoBuf.ProtoReader.State.ThrowWireTypeException() in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 758
at
ProtoBuf.Internal.PrimaryTypeProvider.ProtoBuf.Serializers.ISerializer<System.Type>.Read(State&
state, Type value) in
//src/protobuf-net.Core/Internal/PrimaryTypeProvider.Primitives.cs:line
292 at
ProtoBuf.ProtoReader.State.g__ReadFieldOne|102_0[T](State&
state, SerializerFeatures features, T value, ISerializer1 serializer) in /_/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1075 at ProtoBuf.ProtoReader.State.ReadAsRoot[T](T value, ISerializer1
serializer) in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1059
at ProtoBuf.ProtoReader.State.DeserializeRoot[T](T value,
ISerializer`1 serializer) in
//src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1036
at ProtoBuf.Serializer.Deserialize[T](Stream source, T value, Object
userState, Int64 length) in
//src/protobuf-net/Serializer.Deserialize.cs:line 43 at
ChildProcess.Program.Main(String[] args) in ...\Program.cs:line 324
I have added debug output to make sure that parameterType == typeof(ChildProcessTestMessage).
I'm currently at a loss at why it's not working and how to debug this. There must be a difference in the child process somehow but I have no lead what it could be.
One idea was that maybe assembly loading is different. Could this manifest as this kind of failure?
This is on .NET Core 5.
I'd appreciate your help.
Update: I have now hardcoded a copy of that proto contract into the child. Everything coming over the wire is now ignored. The deserialization call looks like in the parent. Same error as before...
The child process exe is a project in this solution. I'm invoking it from a unit test project, and it lives in the same directory as the unit test DLL and current directory. Could this mess up the child? There are lots of files there which the child maybe didn't expect.
OK, this was a pretty nasty but interesting bug. I kept bisecting by trying to remove differences between the parent and the child. So I kept hardcoding more deserializing code pasting it as the first line in Main. That worked, yet at a later point in the program the (seemingly!) same code didn't.
When I brought the working and the non-working code closer and closer, until the lines were adjacent, I finally spotted the issue:
When you say Serializer.Deserialize(type, stream) it picks a different overload than when you say Serializer.Deserialize(stream, type). Both will compile, but the latter will use type as the "prototype" object. This will cause the library to try to deserialize stream as a System.Type instance. This is a system type that cannot be deserialized. But protobuf-net attempted to do so anyway finding that the serialized bytes and the type structure are incompatible.
So in the end, all of this was caused by switched argument order. It had nothing to do with being in a child process.

How to store DataTable in Apache Ignite?

Based on our current code implementation, we need to store System.Data.DataTable in cache. It works fine when using HttpRuntime.Cache, but not in Apache Ignite.
The following is the code snippet.
IIgnite ignite = Ignition.Start();
ICache<string, object> cache = ignite.GetOrCreateCache<string, object>("cache");
DataTable table = new DataTable();
cache.Put("1", table);
It will throw "Unable to cast object of type 'Apache.Ignite.Core.Impl.Binary.BinaryWriter' to type 'System.IConvertible'" error.
Based on the info at https://apacheignite-net.readme.io/docs/serialization,
DataTable implements ISerializable and has Serializable attribute. It should be able to serialize. I am not sure why I got this error. Any thoughts?
Environment: Ignite.NET 2.1, Visual Studio 2015
I've reproduced the issue, and I would say that this is a bug in System.Data.DataTable. Here is the code:
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
SerializationFormat remotingFormat = RemotingFormat;
bool isSingleTable = context.Context != null ? Convert.ToBoolean(context.Context, CultureInfo.InvariantCulture) : true;
SerializeDataTable(info, context, isSingleTable, remotingFormat);
}
Exception comes from
Convert.ToBoolean(context.Context, CultureInfo.InvariantCulture)
The assumption that Context can be converted to bool does not look correct, see MSDN:
additional: Any additional information to be associated with the
StreamingContext.
Ignite uses this to store BinaryWriter object for internal purposes.
Anyway, .NET framework is not going to be fixed, so I've filed an Ignite.NET bug: https://issues.apache.org/jira/browse/IGNITE-5927

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

COMException (Catastrophic Failure) while accessing BeginConnectedShape and EndConnectedShape of nested connector shapes

I'm creating a PowerPoint AddIn, which is supposed to read information from an external file and integrate that information into the current slide which contains shapes, mostly textbox shapes and connector shapes connecting the text boxes. Each connector is located within a group, containing the connector shape and a textbox, with acts as a label for the connector. The following image depicts this scenario.
My task at hand involves accessing properties of connector shapes contained within groups as described above, namely BeginConnectedShape and EndConnectedShape. Here's some code which is supposed to do just that:
Shape groupShape = Globals.ThisAddIn.Application.ActiveWindow.Selection.ShapeRange[1];
//check if groupshape actually is a group
if (groupShape.Type == Microsoft.Office.Core.MsoShapeType.msoGroup) {
//iterate over all elements within groupShape
foreach (Shape childShape in groupShape.GroupItems)
{
if (childShape.Connector == Microsoft.Office.Core.MsoTriState.msoTrue)
{
//if the shape is a connector, retrieve source and target of the connector
Shape source = childShape.ConnectorFormat.BeginConnectedShape;
Shape target = childShape.ConnectorFormat.EndConnectedShape;
//Do something with source and target.
}
}
}
Running this code while having a group containing a properly connected connector shape (that is, both ends are connected to anchor points of other shapes) selected yields the following exception.
System.Runtime.InteropServices.COMException wurde nicht von Benutzercode behandelt.
HResult=-2147418113
Message=Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))
Source=HandoverAddin
ErrorCode=-2147418113
StackTrace:
at Microsoft.Office.Interop.PowerPoint.ConnectorFormat.get_BeginConnectedShape()
at MyAddin.Ribbon.button1_Click(Object sender, RibbonControlEventArgs e) in Ribbon.cs:line 56
at Microsoft.Office.Tools.Ribbon.RibbonPropertyStorage.ControlActionRaise(IRibbonControl control)
at Microsoft.Office.Tools.Ribbon.RibbonPropertyStorage.ButtonClickCallback(RibbonComponentImpl component, Object[] args)
at Microsoft.Office.Tools.Ribbon.RibbonManagerImpl.Invoke(RibbonComponentCallback callback, Object[] args)
at Microsoft.Office.Tools.Ribbon.RibbonMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Office.Tools.Ribbon.RibbonManagerImpl.System.Reflection.IReflect.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParameters)
InnerException:
This exception is raised when executing childShape.ConnectorFormat.BeginConnectedShape. childShape.ConnectorFormat.EndConnectedShape raises this exception as well. Other operations within ConnectorFormat work fine (i.e. BeginDisconnect successfully disconnects the start of the connector).
shape.ConnectorFormat.BeginConnectedShape works fine, if the connector is not contained within a group. In my case tough, connectors and associated labels have to be grouped together.
I've also observed that accessing BeginConnectedShape on a connector with a disconnected start raises an UnauthorizedAccessException, regardless of whether that connector is contained within a group or not. This seems to be expected behavior.
The exception listed above seems to be some sort of internal exception which is not supposed to get raised under normal circumstances. Therefore, I've checked for any available updates, but none were found for Office 2010 and Visual Studio 2010 Professional.
Any help, possible fixes or workarounds on this issue are greatly appreciated.
Edit: Implementing the same functionality using VBA also results in an error with code 8000FFFF (same as above).
Here's a crude VBA version of this that might help:
It's essentially the same as yours, I think, except for the added test:
If osh.Connector
Without that, it raised errors because it's looking at each shape in the group, but only the connector has the .ConnectorFormat and its associated properties.
Dim groupshape As Shape
Dim oSh As Shape
Dim oBgnShape As Shape
Dim oEndShape As Shape
Set groupshape = ActiveWindow.Selection.ShapeRange(1)
If groupshape.Type = msoGroup Then
For Each oSh In groupshape.GroupItems
**If oSh.Connector Then**
Set oBgnShape = oSh.ConnectorFormat.BeginConnectedShape
Debug.Print oBgnShape.Left
Set oEndShape = oSh.ConnectorFormat.EndConnectedShape
End If
Next
End If

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 ^_^

Categories