WiX/MSI: could not deploy files harvested by heat tool when using MS Intune - wix

I've got a 32b MSI installer developed on 64b machine using WiX toolset (3.10).
It has been tested on both 32b and 64b platforms with different Windows (7, 8.1, 10) and it works without any issues.
The problem starts when I've tried to install my MSI via Microsoft Intune. (It works well with other tools for mass deployment)
While performing the fresh install, user is prompted to insert MSI file (after it is selected, the installation goes smoothly but that is not the purpose of mass deployment).
I've observed that the problem is not occurring when I remove from MSI files that are harvested using heat tool.
The command is (passed via Visual Studio):
"$(WIX)\bin\heat.exe" dir "C:\SOURCE_PATH" -ke -scom -frag -srd -ag
-var var.Resources -cg ResourcesGroupId -dr INSTALLDIR_RES
-out "$(ProjectDir)Source\Fragments\HarvestedResources.wxs"
This component group is added to main Feature containing all other components.
The destination directory is created as:
<DirectoryRef Id='INSTALLDIR_RES'>
<Component Id='cmp_ResDiR' Guid='{SOME_GUID}'>
<CreateFolder />
<RemoveFolder Id='INSTALLDIR_RES' On='uninstall' />
</Component>
</DirectoryRef>
INSTALLDIR_RES is a sibling path located under INSTALLDIR.
Logs are showing that ComponentRegistry is done with exit code 1 (success).
Install scope is per-machine, elevated installation is enabled. All properties used during install are marked as:
secure="yes" admin="yes"
And visible under AdminProperties and SecureCustomProperties (checked via Orca). I don't use any CustomAction to deploy this resources, though.
How can Intune affect my MSI? What am I missing?

The problem was only partially related to MS Intune and absolutely not related to heat.exe and harvesting files.
It turned out that there were two issues:
MSI was deployed using MS Intune but without providing the original MSI to the users. During installation the MSI has been copied to the temp directory and removed after app has been installed. It would be ok, although access to original MSI is helpful in case of self-repair, which leads to second issue:
On some machines, during initial run, the MSI was launching the self-repair procedure. As the original MSI wasn't available on hard disk, the user was prompted for selecting the MSI manually as described in first post. Thanks to the great description of self-healing issues in MSI by Stein Åsmul I was able to detect the root cause (advertised shortcut to main executable with regEntry in user specific path) and fix it.
As a summary: MS Intune doesn't affect deploying files harvested by heat.exe.

Related

Registering COM EXE with WIX

I created a solution with two new projects: ATLProject1 which is a COM dll and ATLProject2 which is a COM EXE. To both projects added same simple class with single method.
Added both to WIX setup project (this is an existing setup project that already installs other COM dlls. I'm just using it for this test).
Added following lines to product.wxs:
<Component Id="ATLProject1.dll" Guid="{...}">
<File Id="ATLProject1.dll" Name="ATLProject1.dll" KeyPath="yes" SelfRegCost="0" Source="$(var.TargetDir)\ATLProject1.dll" />
</Component>
<Component Id="ATLProject2.exe" Guid="{...}">
<File Id="ATLProject2.exe" Name="ATLProject2.exe" KeyPath="yes" SelfRegCost="0" Source="$(var.TargetDir)\ATLProject2.exe" />
</Component>
and also
<ComponentRef Id="ATLProject1.dll" />
<ComponentRef Id="ATLProject2.exe" />
The file also has these lines:
<EnsureTable Id="PublishComponent"/>
<EnsureTable Id="Condition"/>
<EnsureTable Id="TypeLib"/>
<EnsureTable Id="Class"/>
<EnsureTable Id="Extension"/>
When running the setup I get error: "Module ATLProject2.exe failed to register. HRESULT -2147024769" (hex 0x8007007f the specified procedure cannot be found).
If I remove ATLProject2 from setup, it succeeds and ATLProject1 is correctly registered in registry (this is without generating registry information e.g. using heat, it just works).
Should exe component be treated differently?
I found this 10 year old post suggesting heat.exe does not treat COM exe as COM. If this is the problem, Not sure if this is still the case?
The Windows Installer does not recommend using SelfReg to register at install time. Instead, adding the registration to your .wxs code or capturing the registration at build time is highly recommended.
To add the registration manually, you don't use EnsureTable, you use the COM related elements (like Class, ProgId, TypeLib). It can be tedious but will be far more robust than trying to selfreg during installation.
Unfortunately, the alternative to capture the registration during build using heat.exe (provided in the WiX toolset) does not support capturing from executables. If you are open to a commercial solution, we (FireGiant) developed an alternative to heat.exe that can capture executable registration (and much more). That advanced harvesting solution has more documentation on the FireGiant site.
RegServer Switch: COM EXE files are normally self-registered via the /RegServer switch as in:
MyBinary.exe /RegServer
In other words EXE files are not registered via the normal regsvr32.exe mechanism. This is the tool used to register COM dll's and OCX files, but it does not handle EXE files. There is also an /UnRegServer switch to unregister EXE COM files - for the executables that support /RegServer (which is not all COM EXE files - it could be missing as a feature).
Self-Registration: Self-Registration is not ideal to register COM files, and here is a write-up of why this is the case: MSI register dll - Self-Registration considered harmful. In MSI one extracts the COM registry data and populates a number of special COM-tables to allow the registration of the COM server in a way that supports advanced features such as rollback. I don't like the COM extract either (risk of self-repair problems, more on self-repair problems), but it helps in most cases - especially when there are dependencies that can trigger registration problems. Moreover it is the way COM files are supposed to be registered in MSI. It is the standard. I should note that some COM settings go into the Registry table still - since there are no dedicated COM-related tables for them.
heat.exe: WiX's own heat.exe tool now can extract COM data from dll files and ocx files (32-bit). But it does not seem to work for EXE COM files - I am not sure why:
heat.exe file MyCOMDll.dll -out MyCOMDll.wxs
RegSpy2.exe: There is a tool you can use to extract COM registration information from both DLL, OCX and EXE files. It can be downloaded from here: http://www.installsite.org/files/iswi/RegSpy2.zip. Here is the main page listing numerous tools. The RegSpy tool is written by Phil Wilson - MSI Expert and author of The Definitive Guide to Windows Installer (APress).
Here is how to extract the COM data from a COM executable (if you get no data, try unregistering the file first and then running regspy.exe):
RegSpy.exe MyBinary.exe /RegServer >> RegistryOutput.reg
The exported *.reg file can then be converted to WiX elements. This is not an easy process. Using the WiX tool heat.exe does not populate the proper COM tables, but rather puts everything in the Registry table (which will work though, barring MSI validation errors):
heat.exe reg MyCOMRegistryData.reg -out MyWiXFile.wxs -sfrag -suid
There used to be a tool called Tallow that converted reg files to WiX COM registration, but this tool is no longer anywhere to be found. I am not aware of any other ways to generate it short of writing it yourself, or download another deployment tool and import the COM data or extract it and decompile the generated MSI with dark.exe and take out the WiX markup. Or figure out how heat.exe writes its WiX XML output with COM data and adapt that to process the output from RegSpy.exe.
UPDATE: Throwing in a link to Paraffin: https://github.com/Wintellect/Paraffin. This is supposedly a "better Tallow". I am not sure what it supports in terms of COM-extract. My quick test seemed to indicate it doesn't support COM extraction at all, but supports auto-generating WiX markup and add and remove files for updates.
Custom Actions: It is possible to register your COM EXE by means of a custom action that calls the /RegServer switch as well, though this is not recommended for all the reasons listed in the link above (self-registration considered harmful).
Some Links:
Adding a .reg file to registry WIX
How to generate WiX XML from a .reg file?

How do I stop removal of files during uninstallation using Wix

When uninstalling my application, I'd like to configure the Wix setup to NOT to remove few files that were added as part of the installation. It seems like the uninstaller removes all the files that were originally installed from the MSI file. How do I do that?
Here are my files which I wish to keep it forever
<Binary Id="RootCABinary" SourceFile="Assets\Certificates\RootCA.cer" />
<Binary Id="SubCABinary" SourceFile="Assets\Certificates\SubCA.cer" />
I have used WixIIsExtension.dll to install these certificates to the windows store.
Overwrite: Is it important that the file never gets overwritten? If so, add
"Never Overwrite" to your component. WiX attribute: NeverOverwrite="yes". Remember to test upgrade scenarios!
Permanent Component: As stated by Pavel, you can set a component permanent:
<Component Permanent="yes">
<File Source="License.rtf" />
</Component>
Blank GUID: Another way to do it is to set a blank component GUID. It essentially means "install and then leave alone". No repair or uninstall should be done (remember to add NeverOverwrite="yes" if the file should never be overwritten):
<Component Guid="" Feature="MainApplication">
<File Source="SubCA.cer" KeyPath="yes" />
</Component>
Read-Only Copy: I sometimes install files to a per-machine path (for example somewhere under program files) and then copy them to a per-user location (somewhere in the user-profile) on application launch, and then do operations on them that entail that the files should not be deleted. This is good in cases where you want to do something that change the files in question (they need to be writable). Then it is good to "untangle" them from deployment concerns (files will not be meddled with by installer operations at all - the installer has no knowledge of them). The original read-only copy installed to program files can be uninstalled though (no longer needed?).
Other approaches: You can also create such files using custom actions during installation (usually text files only), you can download the file in question from your web site on application launch (makes the file easy to manage and update / replace? Vulnerable to connection problems - firewalls, etc...) and the application can create the file using "internal defaults" in the main executable on launch. And there are no doubt further approaches that I can't recall.
Put your binaries in a separate WiX component and make it permanent. Have a look at this thread as well

My CustomAction is not working

I have the following CustomAction:
<CustomAction Id="CopyToSystem32" ExeCommand="[INSTALLFOLDER]copy.bat"
Directory="INSTALLFOLDER" Execute="deferred" Return="asyncWait" />
<InstallExecuteSequence>
<Custom Action="CopyToSystem32" After="InstallFiles" >NOT Installed</Custom>
</InstallExecuteSequence>
The .bat itself tries to copy some files into System32 folder. But it's not copying them. The log says the following:
CustomAction CopyToSystem32 returned actual error code 1 (note this may not be 100% accurate if translation happened inside sandbox)
MSI (s) (A4:DC) [15:58:46:812]: Executing op: End(Checksum=0,ProgressTotalHDWord=0,ProgressTotalLDWord=313699) 1: CopyToSystem32 2: 1603
Why isn't my CustomAction working?
Try setting Impersonate to no on your custom action
<CustomAction Id="CopyToSystem32" ExeCommand="[INSTALLFOLDER]copy.bat"
Directory="INSTALLFOLDER" Impersonate="no" Execute="deferred"
Return="asyncWait" />
that will allow your deferred custom action to run with admin privileges.
What's in the bat file? You might be asking us to debug the contents of a bat file we know nothing about. Anyway, WiX has a copyfile element that will do this without bat files, using the underlying MSI tables.
The actual error (without seeing inside the bat file) is probably the result of an assumption that the bat file is running in the same environment as if you ran it from your desktop as interactive user, but it isn't. It's being run from an msiexec.exe process that makes no assumptions about where files are located and is running with the local system account (because it's deferred).
Very few files should be copied to system32 these days. Perhaps driver files, Active X OCX files, Control Panel Applets, maybe Console Snapins, Screen Savers - all pretty unusual stuff to deploy, and I believe many of them not really necessary to deploy there at all. Are these the types of files you intend to install to system32?
Remember that there is a whole lot of protection going on in system32 via Windows File Protection on XP (replaced files reset by Windows itself) and Windows Resource Protection on Vista upwards (locked down and protected files using Windows NTFS rights). What you do there might be undone by Windows itself, so stay out of the folder if you can. And if you try to deploy runtime files from Microsoft, they should generally be deployed via Microsoft hotfixes and / or a few "still relevant" merge module runtime packages.
Deployment is not like it used to be. It is very complicated now, and many things that used to work will never work at all. It is especially important to not deploy files that are likely to be replaced by windows hotfixes. Instead, find the version of the file you depend on and set the setup to be dependent on that version or higher. See a good discussion of this here: How can I include KB2670838 in an installer with InstallShield 2013?

Per-user MSI non-elevated by default - how to show an elevation prompt for writing to a folder?

I have an MSI built via WiX, it's per-user and doesn't display the UAC prompt if the user has rights to the destination folder. However, if the destination folder is in Program Files, it errors with "Insuficient privileges".
How can I show a UAC prompt in the case the destination folder is not writable?
It doesn't really work that way. Per User installs should never require elevation and should never write to Program Files as that's a per machine location. Instead it should install to %LocalAppData%\Programs\Company\Product.
Read the following for a lot more background information. Parts apply and parts may be beyond scope ( dual per-user / per-machine requirements ):
Authoring a single package for Per-User or Per-Machine Installation context in Windows 7
I know this is an old thread, but I want to let others who come across it that it is indeed possible.
As was mentioned in the previous answer, conventional per-user installs should not require admin rights. However, I came across a perfectly valid situation where I needed to run a custom action which required admin rights. Requiring my users to launch the MSI with msiexec from a command line with elevated privileges did not seem like an acceptable solution.
It doesn't seem like this should be so difficult, but luckily I stumbled onto the answer in this post: The Package/#InstallScope attribute doesn’t support per-user, elevated packages! So just omit it in your Package definition:
<!-- NOTE: If you need to create a per-user installation (meaning it's not -->
<!-- visible in Add/Remove Programs from other logons) that prompts for -->
<!-- elevation, omit both the Package/#InstallPrivileges="elevated" and
<!-- Package/#InstallScope="perUser". -->
<Package InstallerVersion="200" Compressed="yes" />
Don't forget to leave ALLUSERS undefined, as well. I described this further on my blog:
How to Elevate a Per User Installer Using WiX

Can a .msi file install itself (presumably via a Custom Action)?

I wand to construct an MSI which, in its installation process, will deploy itself along with its contained Files/Components, to the TargetDir.
So MyApp.msi contains MyApp.exe and MyAppBootstrapperEmpty.exe (with no resources) in its File Table.
The user launches a MyAppBootstrapperPackaged.exe (containing MyApp.msi as a resource, obtained from the internet somewhere, or email or otherwise). MyAppBootStrapperPackaged.exe extracts MyApp.msi to a temp folder and executes it via msiexec.exe.
After the msiexec.exe process completes, I want MyApp.msi, MyBootstrapperEmpty.exe (AND MyApp.exe in %ProgramFiles%\MyApp folder so MyApp.exe can be assured access to MyApp.msi when it runs (for creating the below-mentioned packaged content).
MyAppBootstrapper*.exe could try and copy MyApp.msi to %ProgramFiles%\MyApp folder, but would need elevation to do so, and would not allow for its removal via Windows Installer uninstall process (from Add/Remove Programs or otherwise), which should be preserved.
Obviously (I think it's obvious - am I wrong?) I can't include the MSI as a file in my Media/CAB (chicken and egg scenario), so I believe it would have to be done via a Custom Action before the install process, adding the original MSI to the MSI DB's Media/CAB and the appropriate entry in the File table on the fly. Can this be done and if so how?
Think of a content distribution model where content files are only ever to be distributed together with the App. Content is produced by the end user via the App at run time, and packaged into a distributable EXE which includes both the App and the content.
MyApp's installer must remain an MSI, but may be executed by a Bootstrapper EXE. The installed MyApp.exe must have access to both MyApp.msi and EXE is to be "assembled" at runtime by the App from a base (empty) MyAppBootstrapper.exe, which is also installed by the MSI, and the content created by the end-user. The EXE's resource MSI must be the same as that used to install the App which is doing the runtime packaging.
WIX is not to be installed with MyApp.
There can be no network dependencies at run-/packaging- time (i.e. can't do the packaging via a Webservice - must be done locally).
I am familiar with (and using) Custom Actions (managed and unmanaged, via DTF and otherwise).
Add an uncompressed medium to your wxs like this:
<Media Id='2'/>
And then create a component with a File element like this:
<File Source='/path/to/myinstaller.msi' Compressed='no' DiskId='2' />
This will make the installer look for a file called "myinstaller.msi" on the installation medium, in the same folder as the msi that is being installed. The source path above should point to a dummy file, it is only there to appease wix.
Edit: The following sample test.wxs demonstrates that it works. It produces a test.msi file which installs itself to c:\program files\test. Note that you need to put a dummy test.msi file in the same folder as text.wxs to appease wix.
<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product
Name='ProductName'
Id='*'
Language='1033'
Version='0.0.1'
Manufacturer='ManufacturerName' >
<Package
Keywords='Installer'
Description='Installer which installs itself'
Manufacturer='ManufactererName'
InstallerVersion='100'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252'/>
<Media Id='1' Cabinet='test.cab' EmbedCab='yes'/>
<Media Id='2' />
<Directory Id='TARGETDIR' Name="SourceDir">
<Directory Id='ProgramFilesFolder'>
<Directory Id='TestFolder' Name='Test' >
<Component Id="InstallMyself">
<File Source="./test.msi" Compressed="no" DiskId="2" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature
Id='Complete'
Display='expand'
Level='1'
Title='Copy msi file to program files folder'
Description='Test'>
<ComponentRef Id="InstallMyself" />
</Feature>
</Product>
</Wix>
Having one .MSI package launch another .MSI package from "within" itself is called a nested install, and it's bad juju (see Rule 20). Windows Installer has some global data that it uses to manage the current install, and it doesn't handle well multiple installs at the same time. For the same reason, if you start one install and then try to start another while the first is still in progress, you'll usually see a pop-up to the effect of "another install in progress, please wait until it's done".
You can have a program, usually called a bootstrapper (I think that's what you're referring to) which is itself not an install package, but which contains an install package (such as an .MSI or an .EXE) as a resource, possibly compressed. The action of the bootstrapper program is to extract/expand the resource to a file, commonly in a %TEMP% directory, then either launch the extracted .EXE or run MSIEXEC on the extracted .MSI. The bootstrapper can contain multiple resources and extract+install them one by one, if you need to install prerequisites before the main package. Or you can ship multiple packages as separate files, and have the bootstrapper execute/install them directly from the distribution media one by one, or copy them down to the target machine and run the series of install from there, or...
WiX itself does not get installed, no. It's a tool with which .MSI packages can be built. The WiX project has on its wishlist a generic bootstrapper program, but it hasn't been implemented yet. There are other bootstrappers available, e.g. this one.
You won't need a custom action -- in fact, since the bootstrapper isn't itself a Windows Installer installation package, "custom action" has no meaning to it. And, if you're familiar enough with CAs to know about managed/unmanaged/DTF, then you know enough to avoid custom actions whenever you can. (grin)
I think it's much easier for your bootstrapper to extract MSI file to some predefined location rather than to the temp folder. For example, to C:\Documents and Settings\All Users\Application Data\My Company\My Product Install Cache. After installation finishes bootstrapper would leave MSI file sitting there. If at some stage user decides to reinstall your product Windows Installer will be able to locate source MSI file.
Also, add path to this file to RemoveFile table so that it gets deleted on uninstall. You can use RemoveFile element in WiX for that.
So if I understand, then I think I would have the app create a transform (MST) that has the content files and apply that to the base MSI. I'm still not convinced that I understand though. :)
I'd configure the MSI cache path to a known location.
Then at runtime if you need to "edit" the MSI use VBScript or similar.
But still, I ask WHY!?!
I am also working on a way to deploy multiple MSI files. I have a bootstrapper.exe program that bundles the MSI files and runs them one at a time. This solves my problem for most cases.
The case it does not solve is GPO (Global Policy Object) distribution of the install. GPO requires a dot-msi file to run an install.
To do this here's what I did which almost solved the problem (but not quite). I put the dot-msi files in the file table of an installer and put my bootstrapper in the binary table and run it from a custom action inserted after InstallFinalize in the InstallExecuteSequence. Of course the bootstrapper won't be able to run other MSI's because the top level MSI holds the _MSIExecute mutex.
It was pretty easy to get a little further. I made the bootstrapper return control to the top level installer and continute. And then I added a WaitForSingleObject call to wait for the top level install to finish, and the bootstrapper can then continue to finish the install.
My problem is that the GPO installation happens at boot time and the top level install completes before the sub installers are done and GPO reboots the machine.
The toplevel install also returns a success status when the install may actually fail later on.
I'm still looking for a way to block the top level install from completing until after the bootstrapper completes.