How to access a temporary Exe or DLL from a DLL Custom action (C++ DLL) in MSI/WIX project? - wix

I have two use cases: 1) loading a temporary DLL during a custom action and 2) executing a temporary EXE from a custom action. The custom action DLL is unmanaged C++. I cannot figure out how to get this working correctly. Including the DLL is easy enough but LoadLibrary is failing as it cannot find the DLL. I also cannot seem to get the physical path of the extracted DLL in order to specify full path in LoadLibrary. Any help is appreciated. I'm using WIX btw for this work.

If you have included the dll and the exe in the Binary Table of the msi, the files will be physically present in the %Temp% folder of the currently logged in user which gets mapped to SUPPORTDIR property of Windows Installer.
You need to use MsiGetProperty to get the SUPPORTDIR and use that in the LoadLibrary.

One thing to remember - Windows Installer usually extracts files from Binary table to %TEMP%, however - the current work directory is often set to c:\windows\installer.
My suggestion - extract the temporary .dll from Binary table yourself when you need it. This gives you the control of when it's saved to. Just remember that you need write permission to the location, so usually some subdir of %temp% is the best choice.

Related

Getting out of DLL Hell with Microsoft.VC90.CRT?

I've built a inproc com server dll which I can package as 1 file or many via the build utility py2exe. When I allow all the dependencies to remain external, I have no issues, but bundling as 1 file produces problems.
When the dll is utilized (either registering it or instantiating a com object from it), it immediately loads MSVCR90.DLL from the path c:\windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6871_none_50944e7cbcb706e5\MSVCR90.DLL no matter what I do, I can't change that. There is no information that I can find (using Dependency Walker) to indicate what is causing that to load. It just happens magically...
Then, later on it loads that dll again via an explicit call to LoadLibraryA("MSVCR90.dll") (part of some py2exe black box?), but this time it does not look into the winsxs manifests / directory. Instead it looks to the system path and/or will respect a dll redirection. That's when the problem occurs. If I set the system path to start with c:\windows\winsxs\x86_microsoft.vc90.crt...\ it will load the exact same dll and be happy - but if ANY other file is utilized - inclusive of a copy of the EXACT same dll - but at a different path - then the whole thing blows up. It can't handle using two different files.
How can I fix this? Ideally, I've love to make the initial magic loading of the dll draw upon a private assembly, but no matter what I do with manifests or .dll.local etc it will not respect that until this second dll loading takes place.
Note that with the non-bundled dll (external dependencies) it always uses the winsxs MSVCR90.DLL.
I can "fix" my failure to use the dll by forcing the system path to load the winsxs copy, but that is pretty useless for a deployable com server!
The reason is that you DLL has a manifest that tells the module loader to search also in the SxS storage.
You have several choices
Build your DLL using static linkage. Not using any of the MFC-DLLs (see project settings)
Don't use a side by side manifest for the DLL and still use the MFC DLLs. But beware you have to ship those DLL with your DLL in the local path (see DLL search sequence docs)
Use a later build of VS. Later versions of VS don't use the SxS storage any more and there are no manifests for those DLLs any more.
For the 2. see this article in code project. There is an update for VS-2008 [here].
2
Build your DLL

Is File.Open directory behavior different in x86 vs x64

I am working on a application built in VB.Net that allows a document to be uploaded and saved into a database. I did not build this application, but I do maintain it, put enhancements in it here and there. The target framework is .Net4
One of the functionalities within this process when uploading and saving the document it uses the method File.Open() to access the file and run other methods to compress it. The method that uses File.Open takes in a parameter that passes just the filename, not the entire path of where it came from.
When this application is running on an x64 machine I receive an error (System.IO.FileNotFoundException) when the code hits the File.Open method, complaining that it cannot find the file to open. It is expecting the file to be in the programs executing directory, which does make sense because it is only given the filename to go off, not the entire directory that it came from.
What's getting to me, is that this exact same application (exact same built assemblies) will run fine when run on an x86 system. It does not fail on File.Open() It still passes just the filename, but somehow, it will know the directory information.
How is this possible?
It's worth noting, that the method that contains the File.Open() method is in a different project in the same solution. It's a referenced DLL. e.g. MyApp.exe (Windows Form Application) references MyUtil.dll (Class Library). I have built against x86, x64 and AnyCPU configurations.
I understand that the fix to this would be to just pass the entire directory to the method, but what I need to know is how this is even possible? I want to better understand why this would happen, and hopefully this would help someone else better understand how assemblies may differ between different system environments.
EDIT: Using an absolute path did fix the underlying issue. See the comments below for some good information on this scenario
Windows has special handling for certain folder names on 64bit systems depending on whether you have a 32bit or 64bit process. Notably, the Program Files folder and the System32 folders map differently depending on what kind of process you have.
Note that this is a difference in Windows itself. It's not a behavior that is unique to .Net or Visual Basic. Any program platform that uses Windows native file handling will give you these results.
This is why you should use appropriate relative paths or the SpecialFolders enumeration, rather than hard-coding full path names, and be careful about where you put things you expect to reference later; you might find they end up in a different location than you expected. Often, the AppData or ProgramData folders are the more correct location, instead of the Windows or Program Files folders.

WIX: COM registration fails when installing COM dll into another folder

I am using heat to harvest the COM dll and tlb files (let's call them MyLib.*, developed in VB.NET) to do the COM registration. Everything was working fine, when I install MyLib.dll and MyLib.tlb into my application's installation folder, i.e., INSTALLDIR. However, since we want to allow different versions of our SW to be installed on the same machine, and if they are using the same version of COM component, only one copy of the dll (I think each version of our SW should have its own tlb, please correct me if I am wrong) should be installed, we now want to install MyLib.dll into another folder, specifically PROGRAM_FILES\Common Files\SHARED_FOLDER_NAME, so now if one version of our SW is uninstalled, the MyLib.dll will not be removed and can still be used by other versions.
But here comes the problem: my COM registration is just simply not working any more after I install this dll into this another folder, and it keeps saying that can't find file specified when I am calling the COM function which indicates registration failure. In the WIX installer project, everything is the same except this folder for MyLib.dll.
Here is the registry structure after installation:
Firstly I have HKCR\CLSID{MYCLSIDs}, each of them represents one of my COM class. in the sub-key named "InprocServer32", I have Assembly, Class, CodeBase, RuntimeVersion, threadingModel. And the CodeBase is either common file folder (not working) or MyApp's installation folder(working), which is the different locations I put the dll. I thought there would be another sub-key TypeLib under {MYCLSIDs}, since Access only sees the TypeLib and I think there should be some link from the TypeLib to the actual dll, however, at both cases this sub-key is missing but in the second case it is still working. Is there a problem of it?
Secondly I have HKLM\Software\Classes\CLSID{MYCLSIDs}, these keys are of course the same structure as described above.
Thirdly, the HKCR{MYPROGIDs}, these are just ProgIDs of my classes
Fourthly, HKCR\Typelib{LibID}, which includes the information from tlb file, and this ID is from the Assembly GUID of COM component project.
Finally, the HKEY_CLASSES_ROOT\Interface{InterfaceID}, there is sub-keys named ProxyStubClsid32 with value {00020424-0000-0000-C000-000000000046}, and the one named TypeLib and the value is my LibID.
As I mentioned, the only difference is the CodeBase, which stores where MyLib.dll is located. In order to verify that, I did two tests: after I install MyLib.dll into the shared folder, the COM calling fails. But if I replace all the CodeBase values for SHARED_FOLDER\MyLib.dll to INSTALLDIR\MyLib.dll, and copy MyLib.dll into INSTALLDIR, it actually works. Vise versa, after I install MyLib.dll into INSTALLDIR(in which case COM is working), I change the CodeBase values from INSTALLDIR\MyLib.dll to SHARED_FOLDER\MyLib.dll, and make a copy to SHARED_FOLDER, this time it fails. So it seems that it is exactly the installation location's problem, which is the opposite to my understanding of COM. And I don't think there is a permission issue for the SHARED_FOLDER(I could be wrong) since it is in a folder that my installer creates.
Please help, thanks!
It turned out that MyLib.dll is using some other libraries, which are still installed in the MyApp installation folder. And so in that case that MyLib.dll is installed in the shared folder, it tries to find those libraries in the same libraries, which of course fails. When I install those libraries in the shared folder too, it is working.
BTW I found fulogvw.exe very helpful when tracking down the assembly loading problem. For example in my case in the failed log it says can't load file xxx.dll in SHARED_FOLDER, the xxx.dll is some library that MyLib.dll is using, and I had no idea that MyLib.dll needs it until I see the log.

Sharing a common dll between binaries in different directories

I have multiple dll, living in different subdirectories, which depend on another 3rd party dll called common.dll. I would like to set up those dlls so that they know where to find common.dll without making common.dll visible to other programs to avoid clashes.
For example:
foo/bar.dll
foo/bar/fubar.dll
common/common.dll
With both bar.dll and fubar.dll depending on common.dll
I have the following constraints:
While I have complete control on the dlls, I don't have control of the calling executable, so modifying PATH is not an option.
it has to work on every windows from XP up to 7
I cannot install the common.dll in a "known" location used by windows
modifying the dlls code to set up paths through an API is not desirable
IOW, something like unix rpath + ORIGIN on my dlls would be the ideal solution
I was hoping that using manifest could help, but it looks very complicated or not availble for windows xp.
Assuming that that you're using windows a common approach I use is to create a symbolic link to the physical location of the file, in some directory. Then the the caller only has to know some 'known directory' where you put the link and windows magically redirects to the correct file...
real file c:\blah\common\xxx.dll
link : c:\MyDir\xxx.dll
now callers can use c:\MyDir\xxx.dll and they get to c:\blah\common\xxx.dll
syntax on windows is : mklink c:\MyDir\xxx.dll c:\blah\common\xxx.dll

vb.net: how do i build to just one file?

i did a build in vb.net and got one exe file
however, when a user runs the file, it says it is missing one of the libraries (itextsharp).
so the question is, if there is actually a build option in vb.net, why does it not include the library in the same exe file?
You can distribute the iTextSharp DLL with your application. The easiest way to do this is to simply include it in the same folder as your EXE. The DLL should be output to your Project's Debug/Release folder each time you build assuming you've added it as a Reference in your project and the Reference's 'Copy Local' property is set to True.
If you want to distribute one EXE and include the iTextSharp in that, you can use the ILMerge tool (or alternately Gilma from SourceForge) after you build your EXE.
in the properties for the reference set the Copy To Output to Always
ITextSharp is not a library linked in your project output; it's an assembly referenced by your project output. And while VB.Net builds one executable from your source code, the CLR still needs all the referenced assemblies in the same folder as your executable.
To make everything work, you can distribute ITextSharp assemblies along with your app. Alternatively, if you indeed need only one file, you can use ILMerge on your project output and the assemblies you want included. However, you might need to determine all the correct assemblies you need merged. I wouldn't revommend using this tool, unless you understand how it works.
Note: If you want to use ILMerge with .Net v4.0, read this page.