I'm trying to install 3 msi drivers one after another.
There are all in the same folder and I'm using the same attrubutes for all of them/
I already did it before and it worked fine, but now I don't know what possibly can go wrong.
string attributesToDownload = "REINSTALL=ALL /qb- /norestart";
Process proc = Process.Start("msiexec.exe", string.Format("/i \"{0}\" {1}", driverPath, attributesToDownload));
proc.WaitForExit();
return proc.ExitCode;
The curious thing is:
For the first driver my ExitCode is 1603.
For the second sriver the ExitCode is 0 but the driver is not really installed.
For the third one it all works fine.
When I tried to install the first and the second drivers directly from cmd with the same attributes it installed succesfully.
"msiexec /i driverPath REINSTALL=ALL /qb- /norestart"
Waiting for some ideas.....
Got it, I think.... without the REINSTALL=ALL, the only way to do it (that I found) is uninstalling the current version and than installing the new one
If you're doing REINSTALL=ALL to get this to work, then you are updating an existing product installed on the system. That's ok, maybe you want to patch the existing product, but you don't seem to be aware that you cannot install the same product twice - the newer one needs to a patch or and update like your REINSTALL=ALL or a major upgrade. If you want to uninstall the older one then you need a major upgrade!!! There is no need to uninstall the older version because a major upgrade does that automatically.
Related
I have a stable production version of software which always has version number "1.X.0". We don't have any testing environment so I created a development version with version number "1.X.Y" for testing purposes. I test this version on production computer before releasing new production version (1.X+1.0)
This works fine, there are safety measures in place so that they cannot get mixed up. I also created automatic update for both versions using
startInfo.Arguments = string.Format("/passive /norestart /i \"{0}\" TARGETDIR=\"{1}\"", msiPath, installDirectory);
installDirectory is different for production and development version.
The problem is this:
The upgrade code is the same for both versions. I don't want to switch between upgrade codes.
I also set RemovePreviousVersion to false, because I don't want to have version 1.X.0 removed, when installing version 1.X.Y.
Is it possible to have versions 1.X.0 and 1.X.Y on the same computer at the same time and when version 1.X.0 gets upgraded to version 1.X+1.0, only 1.X.0 is uninstalled? The same should apply for 1.X.Y version.
Or is there a better way to handle this?
Thank you
I would strongly recommend using a virtual machine to test installation and upgrades of software. If you have pro or enterprise versions of windows you already have access to HyperV. This has several advantages:
You have a known environment, either a clean windows install, or in combination with the software of your choice.
You can easily revert the VM to a previous known state.
You can keep multiple VMs or snapshots of different versions.
You can test different operating systems in a easy way.
You can limit memory and processor capacity without affecting your main workstation.
You can install network limiting tools without affecting your main workstation.
You can, and should, still run and test development builds of the software on your own workstation since this simplify debugging. While VMs are more appropriate when doing "release" tests.
I found the solution for having 2 or more different versions of the same software installed at the same time and updating them automatically.
First I find the version I am currently using with
var version = Assembly.GetExecutingAssembly().GetName().Version;
I then compare that to the version of .msi file which is in a update folder on server (there are production and development folders). If the .msi version is higher than installed version, the upgrade begins.
First I install the new software in the same folder using
startInfo.Arguments = string.Format("/passive /norestart /i \"{0}\" TARGETDIR=\"{1}\"", path, installDirectory);
var process = System.Diagnostics.Process.Start(startInfo);
process.WaitForExit();
Then I uninstall only the version I opened.
I find the product code of that specific version using below code (I found the code in Windows GUID or Application List answer and modified it a bit)
private string GetProductCode(string programName, string version)
{
StringBuilder sbProductCode = new StringBuilder(39);
int iIdx = 0;
while (
0 == MsiEnumProducts(iIdx++, sbProductCode))
{
Int32 productNameLen = 512;
StringBuilder sbProductName = new StringBuilder(productNameLen);
MsiGetProductInfo(sbProductCode.ToString(),
"ProductName", sbProductName, ref productNameLen);
if (sbProductName.ToString().Contains(programName))
{
Int32 installDirLen = 1024;
StringBuilder sbVersionString = new StringBuilder(installDirLen);
MsiGetProductInfo(sbProductCode.ToString(),
"VersionString", sbVersionString, ref installDirLen);
if (version.Contains(sbVersionString.ToString()))
{
return sbProductCode.ToString();
}
}
}
return null;
}
and run
startInfo.Arguments = string.Format("/passive /norestart /x \"{0}\"", productCode);
System.Diagnostics.Process.Start(startInfo);
At the end there are still 2 installed version of the softare and only one was updated.
Initially I created an application that I completely rewrite in a second version. It is a complete different Visual studio solution.
Now I would like that its Setup installer uninstall the previous version but because it was not created using the same solution, the automatic uninstallation of previous version does not work.
Is there any way to force the installer to uninstall certain application based on product name or product code?
I found a WMIC command that works when run from command line
wmic product where name="XXXX" call uninstall /nointeractive
So I created a VBS script which execute a bat file containing the WMIC code and I added it to the Setup project
dim shell
set shell=createobject("wscript.shell")
shell.run "uninstallAll.bat",0,true
set shell=nothing
but when I run the result MSI, it fires an error 1001, meaning that a service already exists. , in other words the uninstallation didn't work.
The old program is still present and they create a service with the same name. :/
any suggestion?
There are 2 options:
You can increase the version of MSI project so it will treat as upgrade and it will not throw any error while installing.
another way out is the write some in the installer project as follows:
protected override void OnBeforeInstall(IDictionary savedState)
{
//Write uninstall powershell script
//installutil /u <yourproject>.exe
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript("");
PowerShellInstance.AddParameter("");
}
PowerShellInstance.Invoke();
}
Note: This InstallUtil is available with the .NET Framework, and its path is %WINDIR%\Microsoft.NET\Framework[64]\<framework_version>.
For example, for the 32-bit version of the .NET Framework 4 or 4.5.*, if your Windows installation directory is C:\Windows, the path is C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe.
For the 64-bit version of the .NET Framework 4 or 4.5.*, the default path is C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
I decided to go for the option of introducing c# code in the project installer. Firstly I added the reference for System.Management.Automation via nuget
https://www.nuget.org/packages/System.Management.Automation
After this, I just created a string variable containing the PS code I need to uninstall several programs with a similar name.
string unInstallKiosk = #"$app = get-WMIObject win32_Product -Filter ""name like 'KIOSK'""
foreach ($program in $app){
$app2 = Get-WmiObject -Class Win32_Product | Where -Object { $_.IdentifyingNumber -match ""$($program.identifyingNumber)""
}
$app2.Uninstall()}";
and passed this variable to the method PowerShellInstance.AddScript()
PowerShellInstance.AddScript(unInstallKiosk);
The installation ends but the uninstallation simply dont happens.
anyone has an idea how to solve this?
I want to restore previous version of C# application published using ClickOnce, if database migration fails because database wont be latest and it will not support latest version of application.
Details
I am developing an app which will be used locally in remote areas where internet is not available. A person will update his/her app every once in a while by getting internet somehow and then will deploy the app on local network. From there every one will be able to get the updated version of app. What I want now is to use database migration using this app and if the app fails It should restore to previous version. I have already used FluentMigrator for database migration and have used ClickOnce to deploy the app. I have also gone through almost every link over here to see how can I do it. I now know that its not possible using ClickOnce. Can anybody tell me some other way or may be some kind of hack?. I am using ClickOnce because of its auto update feature so don't really want to lose that functionality now. Any help will be appreciated.
FluentMigrator keeps track of current version in the database. It also keeps track of latest version in the current app version. Run Migrator function and check if the latest version of Migration files in the current version is equal to the latest version stored in a database. If both are equal then Migration was successful. If they are not equal then you can run the cmd command to directly open (remove or backup) window of the control panel and go to the previous version. This is the best you can do to revert to the previous version using ClickOnce.
try {
new MigrationsWrapper(AppManager.ConnectionString).MigrateToLatestVersion();
}
catch (Exception ex)
{
}
LatestVersionNumber = new MigrationsWrapper(AppManager.ConnectionStringADO).LatestVersionNumber;
CurrentVersionNumber = new MigrationsWrapper(AppManager.ConnectionStringADO).CurrentVersionNumber;
if (LatestVersionNumber > CurrentVersionNumber) {
string applicationName = ConfigurationManager.AppSettings["ApplicationName"].ToString();
string uninstallString = GetUninstallRegistryKeyByProductName(applicationName);
if (uninstallString != string.Empty) {
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/c " + uninstallString;
process.StartInfo = startInfo;
process.Start();
}
} else {
// Successfull
}
So, you want to run previous version of the app if some problem happen during execution.
I don`t know solution for ClickOnce, but there is analogue for it - Squirrel.
Bad news that Squirrel has no straight solution too, but it phisically stores previous version of app and you can run it and it works (I just checked it on my app).
So, there is a strategy:
Migrate to the squirrel (they have a tool for it)
in case of emergency - calc path to the stored previous version and run it. Relative path should be like "../app-1.1.1/myApp.exe"
But there is one thing to keep in mind. Squirrel stores previous version only if it upgraded app from it. There is no prev version after first install.
Good luck.
From the description the different versions of the application are sharing a single database. As a new version of the application goes live on the machine it needs to patch/migrate a shared database. If that fails the application doesn't install. Let's hope it fails without corrupting the database also.
There are clickonce events that can be hoocked into. Create a Custom Installer maybe.
https://msdn.microsoft.com/en-us/library/system.deployment.application.applicationdeployment_events.aspx
https://msdn.microsoft.com/en-us/library/system.deployment.application.applicationdeployment.aspx
Publishing ClickOnce Applications
https://msdn.microsoft.com/en-us/library/748fh114.aspx
Walkthrough: Creating a Custom Installer for a ClickOnce Application
https://msdn.microsoft.com/en-us/library/dd997001.aspx
Hope that's helpful.
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 have a Custom Action that should execute during the Install portion of an .msi setup. I have a previous version that was installed using InstallShield (which was overkill) and wanted to move to the simpler VS Setup Proj because I do not require all the control that an .isproj provides. However, doing a straight install with my new .msi seems to install side by side with the previous version. Here's what I have found out so far:
I have my product ID
I have written code that will uninstall the previous version through creating a process that uses MsiExec.exe (code will follow)
Tried implementing the custom action to uninstall during setup but it seems you can only have one instance of MsiExec.exe running at a time.
Have been to this post (http://stackoverflow.com/questions/197365/vs-setup-project-uninstall-other-component-on-install), which didnt help.
Custom Action code:
//Exe used to uninstall
string fileName = "MsiExec.exe";
//Product ID of versions installed using InstallShield
string productID = "{DC625BCF-5E7B-4FEF-96DD-3CDBA7FC02C1}";
//Use /x for uninstall and use /qn to supress interface
ProcessStartInfo startInfo = new ProcessStartInfo(fileName, string.Format("/x{0}", productID));
startInfo.WindowStyle = ProcessWindowStyle.Normal;
startInfo.UseShellExecute = false;
//Start process
Process uninstallProcess = Process.Start(startInfo);
//Wait until uninstall is complete
uninstallProcess.WaitForExit();
My hope is to eventually deploy my .msi via ClickOnce, so I am hoping for an option that will fit into deployment option. Currently everything is written in .NET 2.0 and VS 2005, but I do have .NET 4.0 and VS 2010 available to me if there is a new option that works.
Any help is appreciated.
I was able to install over the top of the previous install by making the product code of my setup the same as the code of the older version. Didn't dawn on me to try that because when you create a new version of the setup package, VS always prompts you to change your product code.