I have an installer written in WIX, that has a WPF Bootstrapper. Recently we added a silent installation mode, and we need to use return codes in order to specify what kind of error occurred during silent installation, for instance: invalid username or password, incorrect server address, unsupported Windows version, etc.
We use the Engine.Quit() method from Bootstrapper class to exit the installer with an exit code. This exit code can be seen in the installer log:
[5490:4F84][2018-09-14T14:31:03]i007: Exit code: 0x101, restarting: No
However, when I check the %errorlevel% environment variable, it remains unchanged. Using Environment.Exit() did not help either.
I suspected, that MSI might be responsible for such behavior by overwriting what WIX tried to set, but even forcing ActionResult.Failure in one of the actions of installer does not help. The MSI exit code is in the MSI log, but %errorlevel% remains unchanged:
MSI (c) (AC:9C) [14:30:59:133]: MainEngineThread is returning 1603
=== Verbose logging stopped: 2018-09-14 14:30:59 ===
Is it possibe to make WIX set the %errorlevel% to a custom value, and if yes, how can it be done?
The %ERROPRLEVEL% value is a feature that you get in batch file environments (the Windows cmd BAT shell, and also PowerShell, I believe) so you're not going to see that value outside of a scripting batch environment. It's not clear from your post if your silent install is a batch script or not.
Having said that, an MSI install process returns standard Windows error results that are documented here:
https://learn.microsoft.com/en-us/windows/desktop/Msi/error-codes
so they can't be customized. The particular errors you mentioned (such as invalid user name or invalid server address) appear to be errors from your custom action code in the MSI. People generally deal with error diagnostics in custom actions by using the logging features of Windows Installer to add your messages to the standard log file. This uses MsiProcessMessage() or equivalent as here:
https://social.msdn.microsoft.com/Forums/windows/en-US/5698aaee-11e5-4a8c-b307-f96b9eb1884f/writing-custom-messages-to-log-file-of-msi-using-msiprocessmessage?forum=winformssetup
https://learn.microsoft.com/en-us/windows/desktop/msi/sending-messages-to-windows-installer-using-msiprocessmessage
So you're not going to get errors specific to your custom actions unless you arrange to record the details in the log, as above, or put them somewhere that your silent install can see them (registry?).
Related
I am building msi installer using wixsharp with silent installation using command line without any User Interface. I am having many custom action methods similar to the following for checking prerequisite conditions. I want to warn the users if prerequisite conditions are not met.
var project = new Project("ProductName",
new ManagedAction(new Id("OSVersion"), Check.CheckOSVersion, Return.check, When.Before, Step.InstallInitialize, Condition.NOT_Installed));
Custom action methods returns ActionResult.Failure if conditions are not met.
My batch script is below
start /wait msiexec /i Installer.msi /qn /l*v installerlog.log
if "%errorlevel%" == "1013" goto err
if "%errorlevel%" == "1603" goto err
:err
echo "Error: Msiexec failed with errorlevel = %errorlevel%"
pause
exit /b %errorlevel%
Is it possible to make the MSI installer return custom error code and custom error messages like "OS Version Invalid" and display the same in command line. ?
You cannot change the msiexec exitcode - it returns a Windows value, not one you can customize.
Custom error messages are typically done with a custom action that calls MsiProcessMessage with INSTALLMESSAGE_ERROR, and they'd go in the MSI log too.
I don't know exactly what displaying the error in the command line means, but a silent install really is a silent install and the install won't display anything. In what way do you want a silent install but also display a message, making it non-silent? Will the /qb options work so that you see progress and errors?
I launch msiexec in another process and wait for exit:
var p = new Process
{
StartInfo =
{
FileName = "msiexec",
Arguments = string.Format("/i \"{0}\" /qb", #"c:\install\setup.msi"),
Verb = "runas"
}
};
p.Start();
p.WaitForExit();
int exitCode = p.ExitCode;
If the setup.msi has not been previously installed it is installing to silent mode and returns 0. It normal.
But if setup.msi already installed (launch this code second time), installation not starting and return code 0 - success result! But in fact, the files have not been established, because product is already installed. How I can determine this situation?
You received an exit code of 0 because the product is already installed and you are not attempting to install a new version. In otherwords, your MSI does not have a new Product Code and version number, therefore the MSIExec installer considers it a reconfiguration, and exits. I tested this out by turning on the /log switch and reading the output after installing one of my MSI files twice.
MSI (c) (98:EC) [15:19:27:912]: Product: Product Name -- Configuration
completed successfully. MSI (c) (98:EC) [15:19:27:912]: Windows
Installer reconfigured the product. Product Name: Product Name.
Product Version: 4.8.22. Product Language: 1033. Manufacturer: Manufacturer. Reconfiguration success or error status: 0.
If you were trying to install a new version of your product and your MSI was not configured to remove previous versions, you would receive an error code of 1638. See list of error codes here: MSDN
If you want to check if the product is already installed with the existing MSI information (not an upgrade) you would need to check the registry at: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\YourProductCode
If it turns out it is installed (according to the system/registry -- maybe the files were deleted but it still is considered to be installed) you can try uninstalling it using the /x or /uninstall switch and then reinstalling. You could also use the /fa switch to do a repair and reinstall all files.
msiexec.exe /x ProductCode will uninstall it. Then you can run the install again after that.
msiexec.exe /fa ProductCode will do a repair of all files. The /f switch has a lot of different options for how it reinstalls files, so you'd do well to read the link to the msiexec switches article I posted above.
Some other notes about msiexec:
/qb displays a basic user interface. You probably want /qn.
When I was setting up my live-update software I ran into a bunch of problems, I had to make sure I called msiexec from system32 by using
p.StartInfo.FileName == Path.Combine(System.Environment.ExpandEnvironmentVariables("%windir%\\system32"), "MSIExec.exe");
First, the other remarks concerning 1638 return code are a bit misleading.
When you install the exact MSI file a second time, you get a return code 0 like you already (and correctly) observed. That's "correct" behaviour or in other words: That's how MSI is designed.
Moreover there are no changes made to your existing setup in this case. If you delete all files before the second install, you end up with nothing although MSI is returning zero.
So return code alone is not helping you for this scenario.
In short terms, you have the following possibilities:
Easy but seems unnormal: Just uninstall (maybe silently) the product before you install with:
msiexec /x {yourproductcode} /qn
(You will not get an error even if the product has been NOT installed before because of the silent parameter "/qn"
Advised if enough for you: Just use the repair mode if you want to install a second time:
Example:
msiexec /i ... REINSTALL=ALL REINSTALLMODE=vemus
Optimal: Use a launcher (boot-strapper or other names are the same thing) to test IF the product is already installed, etc. With this you can automate the previous options (pre-uninstall or add of repair parameters). This can be done also with scripts but in each case this is programming, so not the easiest way.
Now we come to the mentioned 1638 return code in some other answers:
IF (and only if) your build system (like InstallShield does by default) changes the socalled MSI PackageCode) in every build AND you are trying to update this slightly different built (MSI) to a previous installed one, you get the 1638 return code.
These things are often misunderstood.
Changing the PackageCode for every build is a very recommended practice.
On the other hand it will complicate things for you not only a bit, if you will release such MSIs to your customers. The name of this update type is "small update or minor upgrade" (their difference is not important here, because it has the same limitations for you.
If you really want to solve your problem with a return code, you can use this. But as said, the 1638 you will not get for the second install of the exactly SAME MSI!
To continue the recommendation about updates, there are more ways: the easiest to handle way (for beginners) are Major Upgrades. This is what is called "new version" in another answer which was not wrong, but not so exact.
For Major Upgrades you have to change MSI PackageCode and MSI ProductCode at minimum, recommended is changing the ProductVersion also.
(Another way would be to use MSI patches as delta updates, but this is not easy either).
MSI COM API: If you can use the MSI COM API you can use the ProductState property. In other words you can check for an installed product with two lines of code if you have the actual product code (How can I find the product GUID of an installed MSI setup?):
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
MsgBox installer.ProductState("{00000000-0000-0000-0000-000000000001}") ' <= PRODUCT CODE
Results: The normal states are 5 for installed or -1 for not installed:
INSTALLSTATE_UNKNOWN -1 The product is neither advertised or installed.
INSTALLSTATE_ADVERTISED 1 The product is advertised but not installed.
INSTALLSTATE_ABSENT 2 The product is installed for a different user.
INSTALLSTATE_DEFAULT 5 The product is installed for the current user.
Interactive VBScript: Here is a larger version of the VBScript with interactive input of product GUID in an InputBox - for use with any product GUID in an ad-hoc fashion: CheckProductState-Interactive.vbs
Links:
Check for installed VCRedist
How can I find the product GUID of an installed MSI setup?
WiX Installer wrong about newer version already installed (find product details based on product code)
I am trying to uninstall a program from my visual studio project but that seems to requiere me to run vs as an admin....so i tried doing this from the cmd to debug it .
I have managed to uninstall a msi setup project installation with this command from cmd :
msiexec /x {3A40307D-6DF2-4412-842F-B1D848043367} /quiet , but that only works when i start cmd as an admin, without admin rights it wont uninstall. What am i doing wrong and is there another approach to get the result I want?
I want to be able to silent uninstall an application without having to ask the user to login as an admin.
Edit:
This is the result from the log :
Error 1001. Error 1001. Unable to delete file C:\ProgramData\XXX.InstallState.
DEBUG: Error 2769: Custom Action _F6174138_B428_4AB6_9FEF_C4DD7A69BDC0.uninstall did not close 1 MSIHANDLEs.
The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2769. The arguments are: _F6174138_B428_4AB6_9FEF_C4DD7A69BDC0.uninstall, 1,
CustomAction _F6174138_B428_4AB6_9FEF_C4DD7A69BDC0.uninstall returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
Action ended 17:54:40: InstallExecute. Return value 3.
Action ended 17:54:40: INSTALL. Return value 3.
MSI (s) (F0:3C) [17:54:40:355]: Product: XXX -- Removal failed.
That error from the log file indicates that a custom action is crashing. You'll want to investigate the root cause of that issue. My guess is that the custom action requires elevation (admin privileges) to work correctly but is not marked deferred (i.e. runs during the part when the MSI is elevated).
If you launch the uninstall of the MSI from Add/Remove Programs (Programs and Features) then you should not be prompted for elevated credentials. Thus the root issue probably is this custom action.
It appears you have several options here. All of them require creating an msi that doesn't require Admin privileges from the start. If the msi requires them from the start (eg, you have no control over the creation of the msi), there is no way around it. It all depends on what files are being edited as to whether or not admin rights are really required. Check out this answer: How can I create a windows installer MSI that does not require admin access
I recently tried to change the display name of my windows service, but apparantly I messed up. An old version fails to uninstall completely and I cannot install a new version of the windows service. This is the error generated from running installutil /i myService:
An exception occurred during the uninstallation of the System.Diagnostics.EventLogInstaller installer.
System.InvalidOperationException: The event log source '7 Ac Service' cannot be deleted, because it's equal to the log name.
An exception occurred while uninstalling. This exception will be ignored and the uninstall will continue. However, the application might not be fully uninstalled after the uninstall is complete.
So the event log source is equal to the log name. What does that mean and how do I fix it?
I found out myself. This post helped. So there are basically 3 places in the registry in the same name as the windows service. So I tried first to write a console application to remove the eventlog:
System.Diagnostics.EventLog.Delete("7 Ac Service");
It took care of 2 of them. I removed the last one manually using regedit. Then my windows service installed without problems.
I'm still trying to run my easyhook exercize. right now, i get this error:
System.ApplicationException: Unable to install assembly in the GAC. This usually indicates either an invalid assembly path or you are not admin.
at EasyHook.NativeAPI.GacInstallAssembly(IntPtr InContext, String InAssemblyPath, String InDescription, String InUniqueID)
at EasyHook.Config.Register(String InDescription, String[] InUserAssemblies)
at HookTest.Program.Main()
and the problem seems to originate here:
Config.Register(
"Easy hook test",
"Hook Test.vshost.exe",
"TestInject.dll");
The solution I'm trying to build is composed by two projects, a library and an application. Once I build the solution, i copy testinject.dll to the hooktest debug folder, and then I run it in debug mode.
Maybe I should use an absolute path to indicate where testinject.dll is? or add the library somewhere?
UPDATE 1
"Easy hook test",
#"Hook Test.vshost.exe",
#"I:\Documents and Settings\foo\Desktop\Hook Test\TestInject\bin\Debug\TestInject.dll");
Even with this change, I get the same error
Try changing the target framework from 4.0 to 3.5, that should do the trick.
This usually indicates either an invalid assembly path or you are not admin.
That's as good an error message as you can expect. The path could be wrong because you don't specify the full path of the assembly (i.e. c:\mumble\foo.dll). You commonly don't have admin rights because of UAC. Use a manifest to get the privilege elevation or run Visual Studio in admin mode (change the desktop shortcut).
Even though you yourself are an Admin, it does not mean that apps that you run will be elevated to admin. In fact, VS 2010 will NOT be, nor will most others. You actually have to right-click "Run as Admin...". I actually set my VS start menu shortcut's properties to "Run as Administrator" so that I never forget, as I was burned on this too.