I have authored a COM component that is distributed through a WIX-generated MSI.
The COM component has rather complicated and non-static registration logic which means that embedding the registration information directly in the Windows Installer WXS file is not a feasible option - registration must be done using regsvr32 - and it's a 32-bit COM component, so it must use the 32-bit version of regsvr32.exe - that is %SystemRoot%\SysWow64\regsvr32.exe on 64-bit Windows or %SystemRoot%\System32\regsvr32.exe on x86 Windows.
I noticed two problems with WIX with this WXS XML:
<InstallExecuteSequence>
<Custom Action="COMRegister" After="InstallFinalize">NOT Installed</Custom>
<Custom Action="COMUnregister" After="InstallInitialize">Installed</Custom>
</InstallExecuteSequence>
<CustomAction Id="COMRegister" Directory="APPLICATIONROOTDIRECTORY" ExeCommand='regsvr32.exe /s "[APPLICATIONROOTDIRECTORY]Component.dll"' />
<CustomAction Id="COMUnregister" Directory="APPLICATIONROOTDIRECTORY" ExeCommand='regsvr32.exe /s /u "[APPLICATIONROOTDIRECTORY]Component.dll"' />
The wrong regsvr32.exe was being invoked. I noticed that the x64 version of resgvr32.exe was being run on 64-bit systems instead of the 32-bit version.
regsvr32.exe was being run without elevated permissions, so COM registration failed with E_ACCESSDENIED.
For 1. it works if I hardcoded the path to the regsvr32.exe executable using [WindowsFolder]\SysWOW64\regsvr32.exe, but this wouldn't work on a real 32-bit machine where SysWow64 doesn't exist.
For 2. I read online that changing After="InstallFinalize" toAfter="RemoveExistingProducts"would cause it to run with elevated permissions, however instead this just gives me errors aboutRemoveExistingProducts` being an unresolved symbol name.
How can I resolve these two issues?
Update
(After struggling with this problem for the past 2 hours, I'm convinced the authors of WIX are close relations of H.P. Lovecraft)
I've worked-around the first issue by writing my own intermediate-step program which is a 32-bit executable, so it will always run under a WOW context, so it will reliably invoke the 32-bit regsvr32.exe program.
I found out the issue with the second issue was these things: For a CustomAction to run with elevated permissions (well, in the same security context as the main installer job) these conditions must be true:
<Custom/>" must haveBefore="InstallFinalize", and **not**After=""any other values forBefore=""` won't work reliably as WIX or Windows Installer might rearrange the actions (wut).
<CustomAction /> have have these attributes explicitly set:
Execute="deferred"
Impersonate="off"
Even-so, I would like to not have to use my helper program to correctly resolve the 32-bit regsvr32.exe. What options are there?
I wouldn't advise using self registration at all it is not the really the right way to do it with Windows Installer, if you really must set File/#SelfRegCost to 1.
A much better way is to extract the registry values and write them with WiX - you can also use heat to generate the values.
This sounds like something that will cause you lots of problems later. Though it sounds like you are determined to use this self-registration, please read this whole post: Self-Registration considered harmful.
Software that is doing "odd things" during self-registration are really frowned upon when found in application packaging in larger companies. Sometimes it is reason enough to throw out the entire software.
If you have stuff you need done with admin rights before your application can run, you should do it as part of the installer, but not via self-registration (see a plethora of reasons in the linked post above). It is better almost any other way, including having your main application.exe run with a command line from a custom action to trigger the custom registration steps, and then provide a good log of what has actually been done. And you should revert to normal COM registration and extraction for the COM servers.
It would be interesting to hear what is unique about your COM servers? Is it the usual licensing issue?
Related
I'm finding that when I update the manifest for my bootstrapper to support Windows 10 compatability, the InstallUISequence of the MSI will correctly set VersionNT=1000, but the InstallExecuteSequence will set VersionNT=603.
How do I make the InstallExecuteSequence also set VersionNT=1000?
Here's my two cents....
I don't find the VersionNT property terribly useful. VersionNT64 is: VersionNT64 .... Not VersionNT64 to determine bitness.
This is a bit of a hack (they do this, we do that...) but desperate times call for desperate measures....
In all of the compatibility games MSFT is playing they only seem to be masking the Major and Minor but Build and revision. I've also worked out that on Win8 they mask it as 6.2 and on Win 10 they mask it as 6.3. So therefore I feel comfortable doing this:
<Property Id="WIN10FOUND">
<DirectorySearch Id="searchSystem" Path="[SystemFolder]" Depth="0">
<FileSearch Id="searchFile" Name="advapi32.dll" MinVersion="6.3.10000.0"/>
</DirectorySearch>
</Property>
What I tend to do ask myself is "WHY" do I need Windows (FOO)? I then look for some registry entry or DLL that indicates that particular feature, component, API is present and use that for my test.
Microsoft has adopted an evergreen approach of "you don't need to know what version it is, you'll always have the latest and it'll always be called Windows 10" and to me this reinforces the approach I prefer to take. I know that a day will come that they are wrong and I do need to know otherwise I'll install and my application will fail and my users will complain and not know what version they have. (Sigh...)
Microsoft official answer:
When you install an .msi installation package on Windows 10 or Windows Server 2016, the VersionNT value is 603.
Source
Since msiexec.exe does not have Windows 10 compatibility in its manifest, and VersionNT is a private property, there is no clean way I'm aware of to make the execute sequence see VersionNT=1000. I would recommend one of these approaches:
copy VersionNT to another property during the UI sequence (some public property like REALVERSIONNT; be sure to list it in SecureCustomProperties like any other property you want to pass to the execute sequence),
pass in the real value from your bootstrapper (ditto), or
possibly set a registry key in your bootstrapper if you need the same information during maintenance or uninstallation, then pull its value into the installation with a system search.
(I'm torn about the registry key option, as it can go stale if the OS is upgraded in the future. Note as well that all of these options are likely to be only as correct as the manifest on your bootstrap in a theoretical future version of Windows.)
I found that a very easy and robust way of detecting Windows 10 is by calling the built-in WMIC command and parsing the output from it:
wmic os get Name,Version,BuildNumber /VALUE
it'll return exactly the info you need to determine which OS you're on:
BuildNumber=14393
Version=10.0.14393
http://helpnet.flexerasoftware.com/installshield22helplib/helplibrary/whats_newIS2015.htm
On systems with Windows 10, the Windows Installer properties VersionNT
and VersionNT64 indicate 603, which was originally introduced as the
version number of Windows 8.1. Therefore, it is not possible to create
conditions in an .msi package that specifically target Windows 10.
Since Windows Installer 5.0 and Windows 7, DLL custom actions in .msi
packages are shimmed to block obtaining the operating system version;
the APIs GetVersion, GetVersionEx, and RtlGetVersion return a Windows
version of 6.0.6000, which was originally the version number of
Windows Vista. Therefore, it is also not possible to obtain the actual
version number of Windows from a DLL custom action or from an
InstallScript custom action (which is implemented as a DLL).
Because of the aforementioned behavior in Windows Installer, it is not
easily possible to detect what version of Windows on which an .msi
package is running
For installers that do not have a bootstrapper, I've found that creating an immediate custom action that calls GetVersionEx() and sets a property for the rest of the installer to use is also a good alternative.
I've sequenced my custom action to occur after AppSearch, and it was enough to condition components with it.
I have a WIX install program.
When I test my install program on a machine where the old version of the software is running, I get the prompt below.
The problem is that the installer can't manage to close the application. When the new program runs, it complains about the old one running.
Is there a way to forcefully kill the app?
If not, is there some entry in WIX that will require the user to shutdown the application before it will continue installation?
I found the answer here:
WiX <util:CloseApplication> element not working
I made one tweak to the solution in the post above. I kill the application earlier in the install sequence so the window above doesn't appear.
<!-- Code to force termination of running program...MSIExec couldn't do it -->
<Property Id="QtExecCmdLine" Value='"[WindowsFolder]\System32\taskkill.exe" /F /IM "$(var.ProductName).exe"'/>
<CustomAction Id="APP.TaskClose" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="immediate" Return="ignore"/>
<InstallExecuteSequence>
<Custom Action="APP.TaskClose" After="LaunchConditions"/>
</InstallExecuteSequence>
If you are wondering what "$(var.ProductName).exe" is, I pass the exe name on the commandline because I'm creating several branded versions of the same program. Just substitute your exe name.
And yes, it is safe in this particular intance to do this. There is no data held in memory that could be lost.
You should use the WiX util extension CloseApplication:
http://wixtoolset.org/documentation/manual/v3/xsd/util/closeapplication.html
That should work.
Long term you should get the app integrated with Restart Manager so it will shut down automatically.
I'm launching a custom action console application that can return 0 or -1.
On Windows 8 when it returns 0, the install continues.
On Windows 7 when it returns 0, the install ends prematurely.
<Property Id="QtExecCmdLine" Value=""$(var.SourceFiles)\MyProgram.exe""/>
<CustomAction Id="CheckForOld" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="immediate" Return="check"/>
<InstallExecuteSequence>
<Custom Action="CheckForOld" After="AppSearch" />
</InstallExecuteSequence>
Does anyone know what can be done to resolve this?
What are the UAC levels/settings on the Windows 8 and Window 7 machines? If you have UAC disabled on your dev machine but enabled (default setting) on the test Win 7 you could get the EXE failing to run as a custom action.
In this case I also recommend migrating your code to a DLL, you could write a C# DLL, using WixToolset and DTF.
The DLL can then set a property with the result of this search, and you can use that property to define a new launch condition to stop the installation, if required.
From Tao of the Windows Installer, Part 5
Rule 53: Test thoroughly It is crucial that you test your packages thoroughly before deployment. A classic mistake is for
developers and package authors to test only on their own systems where
they have full administrator rights and then discover that normal
users cannot use their applications.
Is that a QT exe file? What is the EXE file doing / checking? What happens if you turn off error checking - does it run and perform its task?
I wouldn't recommend to run a custom action with an exe file if you write the code yourself. Write it as a dll and connect the debugger to the code to step through it using a debug build dll - you will see exactly what happens as you step through the code:
Insert code to show a message box from within the function you want to debug
Compile a debug dll file and insert into the MSI file with the necessary plumbing to hook things up. This isn't entirely trivial, but not that complicated. See link below.
Run the MSI and wait for the message box to show
Attach the debugger to the correct msiexec.exe - either the user context one or the system context one depending on how your custom action is sequenced
Set breakpoints and step through the code to determine what is happening
How to create a basic MSI dll:
http://www.codeproject.com/Articles/1747/MSI-Custom-Action-DLL
What is this EXE supposed to do? There are several code smells. First is that it's scheduled in the UI sequence only. It won't get run during a silent install. The second is it's an EXE scheduled as immediate. EXE's can't access the MSI handle and set properties or do anything useful so I assume the EXE is changing the configuration of the machine. This is inappropriate outside of the execute sequence transaction. Finally, QtExecCmdLine needs an absolute path to the EXE on the destination machine. I don't see how $(var.SourceFiles) could provide that. That's a preprocessor variable that's only going to tell you where the file exists on the build server.
You aren't testing on your own dev machine? I'm guessing you build this on a Win 8 box and then tested on the same box. It happens to work cause it can find the file. When you run it on another machine (Win 7) it can't find the file and fails because the file can't possibly be there... you haven't installed anything yet.
I've got an MSI built with WiX. It performs the following custom action:
<CustomAction Id='StartTray'
Directory='INSTALLDIR'
ExeCommand='[INSTALLDIR]\myapptray.exe'
Return='asyncNoWait'
Impersonate='no'
Execute='deferred' />
It is scheduled like so:
<Custom Action='StartTray' After='StartServices'>NOT Installed OR (TRAYWASRUNNING AND NOT REMOVE~="ALL")</Custom>
myapptray.exe happens to use impersonation to relaunch itself from its starting context of Local System (running from MSI context) as the user currently active on the desktop. This is not in my control and Impersonate='yes' does not work because the MSI may be invoked for an upgrade from the context of the system service, meaning Impersonate='yes' would still end up running the app as Local System.
I recently moved from including the VC9 CRT as a MSM in this MSI, to including it in a bootstrapper exe.
Doing this prevents the myapptray.exe custom action from running successfully. The impersonation fails in WTSQueryUserToken which returns ERROR_PRIVILEGE_NOT_HELD. This seems to imply that removing the MSM actually changed the user context in which the MSI runs, but that seems ridiculous. The only lines I removed from the wxs file are the <Merge> and <MergeRef> tags for the MSM, nothing else has changed.
What am I doing wrong?
I'd look more at what versions of the CRT your EXE was built against and if there are any policy rules saying what it can run against. Moving from an MSM to an EXE run before your MSI should generally be a good thing.
BTW, I did something really hacking like this once upon a time. We had to push an MSI out under the SYSTEM context using MSI. If a user was logged on we had to relaunch the application using the users desktop login session. I had a DCOM server installed configured to impersonate the interactive user to accomplish this. Really wierd, but there was a valid reason.
This was all before Restart Manager though.
I figured it out.
The CRT MSM was setting ALLUSERS=1 and the installer's behavior changed because it was not present in our base installer. The MSM's setting of ALLUSERS was inherited into the base installer as a result.
Setting ALLUSERS=1 in our wxs file fixed the problem!
I'm writing a new major upgrade of our product.
In my installer I start by finding configuration settings of the previous version, then I'd like to uninstall the previous version.
I have found several guides telling me how one should make a MSI suitable for such upgrades.
However, the previous was not an MSI.
It was not according to best practices.
It does, however, in registry HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall{GUID} specify an UninstallString.
Using a RegistrySearch I can easy find the command below, which I store in UNINSTALL_CMD.
RunDll32 C:\PROGRA~1\COMMON~1\INSTAL~1\PROFES~1\RunTime\10\01\Intel32\Ctor.dll,LaunchSetup
"C:\Program Files\InstallShield Installation Information\{GUID}\setup.exe"
-l0x9 -removeonly 4:
I cannot get the hang of the CustomAction needed to perform the actual uninstall.
<CustomAction Id="ca.UninstPrev" Property="UNINSTALL_CMD" ExeCommand="" />
The MSI logs says:
Info 1721. There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: ca.UninstallPrevious, location: RunDll32 C:\PROGRA~1\COMMON~1\INSTAL~1\PROFES~1\RunTime\10\01\Intel32\Ctor.dll,LaunchSetup "C:\Program Files\InstallShield Installation Information{GUID}\setup.exe" -l0x9 -removeonly, command:
Anyone seeing what I am doing wrong here?
Regards
Leif
I did application repackaging for a couple years at Continental Airlines where I did SMS pushes to an 18,000 seat forest. I frequently had a legacy application in the wild that was not installed using MSI that I needed to get redeployed using MSI and once that was done I supported major upgrades going forward.
These previously deployed apps typically had very broken and misbehaved uninstallers. Instead of calling these, I would use SMS to query the forest to get all the deployed versions. I would then deploy those old packages to a integration lab and work out what it was each installer did to the machine and write my own aggregated "forced cleanup" custom action that was capable of wiping the various versions of the application off the machine.
I executed this custom action prior CostInitialize so that when the new MSI did it's costing it wouldn't be influenced by the crap that was no longer on the machine. This worked for me because I pushed packages as System and I didn't have to worry about elevation issues. If you want to be fully UAC compliant you would want to run this custom code from a prereq package and either have your users run it manually or wire it into a bootstrapper to run prior to your MSI.
After a good nights sleep I found my error.
If you really read http://wix.sourceforge.net/manual-wix3/wix_xsd_customaction.htm the answer was there.
I was trying to make a type 50 custom action, launch an executable already installed on system.
Property specifies the full path to an executable to launch
ExeCommand specifies the command line arguments for this executable above.
And my fault was that I did place the full exe+command line in the Property field.
/L