I have wix project. With Installation all is ok. But when I do the same (passing parameter during unistall) its not working((
Wix file
<Product Id="$(var.ProductCode)" Name="$(var.ProductName)" Language="1033" Version="$(var.ProductVersion)" Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine"/>
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes" />
<Feature Id="ProductFeature" Title="VMP.Passport.Installers.Server" Level="1">
<ComponentGroupRef Id="ProductComponents" />
<ComponentGroupRef Id="ServerInstallerFiles" />
<ComponentGroupRef Id="DataInitInstallerFiles" />
</Feature>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" ></Property>
<UIRef Id="WixUI_MinimalCustom"/>
<InstallExecuteSequence>
<Custom Action="DoAfterInstallJobParams" Before="DoAfterInstallJob">Not Installed</Custom>
<Custom Action="DoAfterInstallJob" After="InstallFiles">Not Installed</Custom>
<Custom Action="DoBeforeUnstallJobParams" Before="DoBeforeUnstallJob">REMOVE="ALL"</Custom>
<Custom Action="DoBeforeUnstallJob" After="InstallInitialize">REMOVE="ALL"</Custom>
</InstallExecuteSequence>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="VMP" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<Property Id="DoBeforeUninstallJob" Value="[INSTALLFOLDER]" />
<Binary Id="CustomActionBinary" SourceFile="$(var.SolutionDir)Output\Installers\Actions\VMP.Passport.Installers.Server.Actions.CA.dll" />
<CustomAction Id="DoAfterInstallJob" BinaryKey="CustomActionBinary" DllEntry="AfterIntall" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="DoAfterInstallJobParams" Property="DoAfterInstallJob" Value="HOSTING_URL=[HOSTING_URL];HOSTING_PORT=[HOSTING_PORT];DB_CONNECTION=[DB_CONNECTION];INSTALLPATH=[INSTALLFOLDER]" />
<CustomAction Id="DoBeforeUnstallJob" BinaryKey="CustomActionBinary" DllEntry="BeforeUnistall" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="DoBeforeUnstallJobParams" Property="DoBeforeUninstallJob" Value="INSTALLPATH=[INSTALLFOLDER]" />
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<ComponentRef Id="cmpDataInit"/>
<ComponentRef Id="cmpServerHost"/>
</ComponentGroup>
</Fragment>
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<Directory Id="DataInit" Name="DataInit">
<Component Id="cmpDataInit">
<File Id="DataInitLog4netConfig" Source="$(var.SolutionDir)..\Logging\log4net.config" />
</Component>
</Directory>
</DirectoryRef>
<DirectoryRef Id="INSTALLFOLDER">
<Directory Id="ServerHost" Name="ServerHost">
<Component Id="cmpServerHost">
<File Id="ServerLog4netConfig" Source="$(var.SolutionDir)..\Logging\log4net.config" />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
My Custom Action
public class CustomActions
{
protected static string sourceLogFilesPath = "\"Logs\\";
protected static string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);
[CustomAction]
public static ActionResult AfterIntall(Session session)
{
try
{
string rootPath = session.CustomActionData["INSTALLPATH"];
string installPath = Path.Combine(session.CustomActionData["INSTALLPATH"], "ServerHost");
#region Coping Log4net config file and change config path
string passportConfigFile = Path.Combine(installPath, "VMP.Core.ServerHost.exe.config");
string sourceLogPath = #"loggerConfigFile=""..\..\..\Logging\log4net.config""";
string destLogPath = #"loggerConfigFile=""log4net.config""";
//passport server
string passportConfigText = File.ReadAllText(passportConfigFile);
passportConfigText = passportConfigText.Replace(sourceLogPath, destLogPath);
File.WriteAllText(passportConfigFile, passportConfigText);
//datainit
string dataInitConfigFile = Path.Combine(rootPath, "DataInit", "VMP.Passport.DataInit.exe.config");
string dataConfigText = File.ReadAllText(dataInitConfigFile);
dataConfigText = dataConfigText.Replace(sourceLogPath, destLogPath);
File.WriteAllText(dataInitConfigFile, dataConfigText);
#endregion
#region Changing passport server app.config
string passportServerExeFile = Path.Combine(installPath, "VMP.Core.ServerHost.exe");
var configFile = ConfigurationManager.OpenExeConfiguration(passportServerExeFile);
//Changing hosting params
configFile.AppSettings.Settings["serverUrl"].Value = session.CustomActionData["HOSTING_URL"];
configFile.AppSettings.Settings["startingPort"].Value = session.CustomActionData["HOSTING_PORT"];
//Changing db server name
string dbconnection = session.CustomActionData["DB_CONNECTION"];
var connectionStringsSection = (ConnectionStringsSection)configFile.GetSection("connectionStrings");
connectionStringsSection.ConnectionStrings["RootDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.RootDB;Integrated Security=True";
connectionStringsSection.ConnectionStrings["PassportDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.PassportDB;Integrated Security=True";
connectionStringsSection.ConnectionStrings["ContentDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.ContentDB;Integrated Security=True";
connectionStringsSection.ConnectionStrings["ShamirDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.ShamirDB;Integrated Security=True";
connectionStringsSection.ConnectionStrings["ReplicationDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.ReplicationDB;Integrated Security=True";
configFile.Save();
string dataInitConfigurationFileName = Path.Combine(rootPath, "DataInit", "VMP.Passport.DataInit.exe");
var dataInitConfigurationFile = ConfigurationManager.OpenExeConfiguration(dataInitConfigurationFileName);
var dataInitconnectionStringsSection = (ConnectionStringsSection)dataInitConfigurationFile.GetSection("connectionStrings");
dataInitconnectionStringsSection.ConnectionStrings["RootDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.RootDB;Integrated Security=True";
dataInitconnectionStringsSection.ConnectionStrings["PassportDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.PassportDB;Integrated Security=True";
dataInitconnectionStringsSection.ConnectionStrings["ContentDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.ContentDB;Integrated Security=True";
dataInitconnectionStringsSection.ConnectionStrings["ReplicationDB"].ConnectionString = $"Data Source={dbconnection};Initial Catalog=VMP.ReplicationDB;Integrated Security=True";
dataInitConfigurationFile.Save();
#endregion
#region Start dataInit
string dataInitExeFile = Path.Combine(rootPath, "DataInit", "VMP.Passport.DataInit.exe");
Process.Start(dataInitExeFile);
#endregion
#region Installing win service
string batFile = Path.Combine(installPath, "InstallService.bat");
string command = $#"
{installPath.Substring(0, 2)}
cd {installPath}
VMP.Core.ServerHost.exe install
VMP.Core.ServerHost.exe start";
File.WriteAllText(batFile, command);
string batUnistallFile = Path.Combine(installPath, "UninstallService.bat");
string commandUnistall = $#"
{installPath.Substring(0, 2)}
cd {installPath}
VMP.Core.ServerHost.exe uninstall
pause";
File.WriteAllText(batUnistallFile, commandUnistall);
//Process.Start(batFile);
#endregion
return ActionResult.Success;
}
catch (Exception exc)
{
File.AppendAllText(Path.Combine(documentsPath, "install_log.txt"), exc.ToString());
return ActionResult.Failure;
}
}
[CustomAction]
public static ActionResult BeforeUnistall(Session session)
{
try
{
string installPath = Path.Combine(session.CustomActionData["INSTALLPATH"], "ServerHost");
string batUnistallFile = Path.Combine(installPath, "UninstallService.bat");
string commandUnistall = $#"
{installPath.Substring(0, 2)}
cd {installPath}
VMP.Core.ServerHost.exe uninstall";
File.WriteAllText(batUnistallFile, commandUnistall);
Process.Start(batUnistallFile).WaitForExit(10000);
return ActionResult.Success;
}
catch (Exception exc)
{
File.AppendAllText(Path.Combine(documentsPath, "install_log.txt"), exc.ToString());
return ActionResult.Failure;
}
}
}
In BeforeUnistall method session.CustomActionData is empty when unistalling. But in AfterIntall method all parameters exists.
What is my problem? What is wrong?
I resolved my problem. I store path in registry and get it on uninstall.
Windows Installer doesn't persist properties after an installation completes. You need to do it yourself such as this example.
http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/
IsWiX generated projects include boilerplate code as an example here.
(Lines 41-61)
https://github.com/iswix-llc/iswix/blob/master/Application/IsWiXNewAddIn/MSISolutionTemplate/SetupProjectTemplate/UI-CustomDialog.wxs
Related
I am trying to upgrade an already installed MSI package from V1 to V2.
The MSI package is part of a bundle (we have 10 different MSI packages in the bundle, but only this one is causing upgrade issues).
The problem I have is that after upgrade the new version appears correct in Control Panel but when I go to the disk the new files are not copied and I'm left with the old files on the disk (V1 files).
If I do a Repair from Control Panel the new files get copied with the correct version.
I am using WixSharp version 1.4.4.1.
Does anyone know why the upgrade is not done properly?
An example of how the Bundle.wxs looks like can be found below:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="Test.Installer"
Version="!(bind.packageVersion.Test_Package_msi)"
Manufacturer="Test"
UpgradeCode="bdbd5b31-e749-455d-9fea-cd81f3297ca1" >
<BootstrapperApplicationRef Id ="ManagedBootstrapperApplicationHost" >
<PayloadGroupRef Id ="InstallerPayload" />
</BootstrapperApplicationRef>
<Chain>
<PackageGroupRef Id ="NetFx472Redist" />
<PackageGroupRef Id ="InstallerPackages" />
</Chain>
</Bundle>
<?define NetFx472MinVersion = 4703062 ?>
<?define NetFx472EulaLink = https://referencesource.microsoft.com/license.html ?>
<?define NetFx472RedistLink = https://go.microsoft.com/fwlink/?linkid=863265 ?>
<Fragment>
<WixVariable Id="WixMbaPrereqPackageId" Value="NetFx472Redist" />
<WixVariable Id="WixMbaPrereqLicenseUrl" Value="$(var.NetFx472EulaLink)" Overridable="yes" />
<WixVariable Id="NetFx472RedistCondition" Value="Netfx4FullVersion >= $(var.NetFx472MinVersion)" Overridable="yes" />
<WixVariable Id="NetFx472RedistPackageDirectory" Value="Redist\NETFX472\" Overridable="yes" />
<util:RegistrySearch
Root="HKLM"
Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full"
Value="Version"
Variable="Netfx4FullVersion" />
<PackageGroup Id="NetFx472Redist">
<ExePackage
DisplayName="Microsoft .NET Framework 4.7.2"
InstallCommand="/q /norestart /ChainingPackage "[WixBundleName]" /log "[NetFx472FullLog].html""
RepairCommand="/q /norestart /repair /ChainingPackage "[WixBundleName]" /log "[NetFx472FullLog].html""
UninstallCommand="/uninstall /q /norestart /ChainingPackage "[WixBundleName]" /log "[NetFx472FullLog].html""
PerMachine="yes"
DetectCondition="!(wix.NetFx472RedistCondition)"
InstallCondition="NOT !(wix.NetFx472RedistCondition)"
DownloadUrl="$(var.NetFx472RedistLink)"
Id="NetFx472Redist"
Vital="yes"
Permanent="yes"
Protocol="netfx4"
Compressed="yes"
SourceFile="..\Redist\NETFX472\NDP472-KB4054530-x86-x64-AllOS-ENU.exe"
Name="!(wix.NetFx472RedistPackageDirectory)NDP472-KB4054530-x86-x64-AllOS-ENU.exe"
>
</ExePackage>
</PackageGroup>
</Fragment>
<Fragment>
<PayloadGroup Id ="InstallerPayload">
<Payload SourceFile = "$(var.SolutionDir)\Libraries\Microsoft.Deployment.WindowsInstaller.dll" />
<Payload SourceFile = "$(var.SolutionDir)\Libraries\GalaSoft.MvvmLight.dll"/>
<Payload SourceFile = "$(var.SolutionDir)\Libraries\GalaSoft.MvvmLight.Extras.dll"/>
<Payload SourceFile = "$(var.SolutionDir)\Libraries\GalaSoft.MvvmLight.Platform.dll"/>
<Payload SourceFile = "$(var.SolutionDir)\Libraries\log4net.dll"/>
</PayloadGroup>
</Fragment>
<Fragment>
<PackageGroup Id ="InstallerPackages">
<MsiPackage SourceFile="$(var.SolutionDir)\Libraries\MSI\Package1.msi" ForcePerMachine="yes"
Id="Package1_msi" DisplayName="Package 1" Visible="yes" Cache="yes" DisplayInternalUI="no" Description="23A29233-37FD-4079-92AF-8337998DF4D3">
</MsiPackage>
<!-- 10 other packages -->
</PackageGroup>
</Fragment>
</Wix>
The MSI project is created using the following lines:
var project = new ManagedProject();
project.Name = "Test";
project.Description = "Test Package";
project.GUID = Guid.Parse("{94682162-F133-40A3-92BB-0AF08142474C}");
project.ProductId = Guid.Parse("{94682162-F133-40A3-92BB-0AF08142474C}");
project.Dirs = GetDirectoriesAndFilesToInstall();
project.Version = GetInstallerVersion();
project.BannerImage = "Banner.bmp";
project.Actions = new WixSharp.Action[]
{
new ElevatedManagedAction(CustomActions.UninstallService, Return.check, When.After, Step.InstallInitialize, Condition.Always),
new ElevatedManagedAction(CustomActions.InstallService, Return.check, When.After, Step.InstallExecute, Condition.Always),
};
project.BuildMsi(msiPath);
I found the problem: the upgrade only works if you add files during the upgrade, not if you remove files.
I compared the old installation with the new one and 6 files were missing from the new one. Because of that the upgrade didn't update any files at all.
The article that inspired me can be found here:
MSI Installer Rules
I realized that I broke Rule 2: Only add, never remove resources from a component
This is somehow weird, what if I want to remove files during an upgrade? What solutions do I have?
UPDATE: I found out that the MajorUpgrade can be forced in code like below:
var project = new ManagedProject
{
Name = "Your name",
Description = "Your description",
GUID = Guid.Parse("{94682162-F133-40A3-92BB-0AF08142474C}"),
MajorUpgrade = new MajorUpgrade
{
DowngradeErrorMessage = $"A later version of {projectName} is already installed. Setup will now exit."
},
Actions = new WixSharp.Action[]
{
new ElevatedManagedAction(CustomActions.UninstallService, Return.check, When.After, Step.InstallInitialize, Condition.Always),
new ElevatedManagedAction(CustomActions.InstallService, Return.check, When.After, Step.InstallExecute, Condition.Always),
}
};
and you don't need to change the major part in the assembly version.
For instance, if the existing installer had version 1.1.0.0 installed, then you can force a Major Upgrade in code but change the version to 1.2.0.0 instead of changing to 2.0.0.0.
Also, in the installer bundle project I had to add this property: EnableFeatureSelection="yes" for each of the MSIPackage element. This property enables you to catch the event named: DetectRelatedMsiPackage. This will help you to differentiate between versions and identify which package is installed or not and also which one can be upgraded.
I want to keep a config file when the msi installer does a major upgrade. For the config file, I make a change when installing. The code is as follows:
<Component Id="MODIFYCONFIG" Guid="6A1D7762-B707-4084-A01F-6F936CC159CE" Win64="yes">
<File Id="Application.config" Name="Application.config" Vital="yes" KeyPath="yes" Source="Resource\Application.config"></File>
<util:XmlFile Id="SetValueIP" Action="setValue" Permanent="yes" File="[#Application.config]"
ElementPath="/configuration/applicationSettings/Application.Properties.Settings/setting[\[]#name='IpAddress'[\]]/value" Value="[IPADDRESS]" Sequence="1"/>
<util:XmlFile Id="SetValuePort" Action="setValue" Permanent="yes" File="[#Application.config]"
ElementPath="/configuration/applicationSettings/Application.Properties.Settings/setting[\[]#name='IpPort'[\]]/value" Value="[PORT]" Sequence="2"/>
<Condition>Not Installed</Condition>
</Component>
<Component Id="KEEPCONFIG" Guid="F7F173AA-C2FD-4017-BFBC-B81852A671E7" Win64="yes">
<RemoveFile Id="ApplicationConfig" Name="Application.config" On="uninstall"/>
<Condition>(REMOVE=ALL) AND (NOT UPGRADINGPRODUCTCODE)</Condition>
</Component>
But when a major upgrade occurs the file is not preserved. How can I preserve the modified file?
This solved it for me... config file is preserved with minor/major upgrade, and completely removed on uninstall.
Ref: Aaron Stebner: How to retain user-customized files during a Windows Installer major upgrade
EDIT: Summarized info from the linked page...
Each config file shall have it's own component, where the config file is marked as the keypath of the component. Unversioned file replacement logic will be used by the Windows Installer.
Add "RemoveExistingProducts" action after the "InstallFiles" action. New versions of all components are installed before removing the old MSI. When it's done in this sequence, the components will have their reference count incremented to 2, but the config files will not be replaced unless they are unmodified (because of unversioned file replacement logic). When the old MSI is removed, the reference count will be decremented back to 1, but the files will not be removed because the reference count are not 0.
You have 3 options when upgrading:
Make the config file component permanent. This will not un-install it, and you will be able to upgrade it, but removing it will be very difficult.
Use the Remember property pattern to store the config settings for the IP and PORT in the registry.
As part of the install, write the config file to a temporary filename and then use a CopyFile command to create the destination file. On upgrade check for the file using a FileSearch, and if it exists then don't copy. Only issue here is if the config file has changed you won't get the updated sections.
The best option is the remember me property as this has the least problems.
It took me a while, but here is how I solved it myself. It's probably a variation of caveman_dick's third option.
1) Add new action into UISequence to back up your current config file. You can do it with the magic of custom action and ComponentSearch to actually locate the file.
2) Restore the file later in ExecuteSequence.
<Binary Id="CustomActions.CA.dll" SourceFile="..\CustomActions\bin\$(var.Configuration)\CustomActions.CA.dll" />
<CustomAction Id="BackupConfigFile"
Return="check"
BinaryKey="CustomActions.CA.dll"
DllEntry="BackupFile" />
<CustomAction Id="RestoreConfigFile"
Return="check"
Execute="deferred"
Impersonate="no"
BinaryKey="CustomActions.CA.dll"
DllEntry="RestoreFile" />
<CustomAction Id="PropertyDelegator"
Property="RestoreConfigFile"
Value="MYTARGET=[MYTARGET];FILENAME_TO_BACKUP=[FILENAME_TO_BACKUP]" />
<Property Id="FILENAME_TO_BACKUP" Value="test.exe.config" />
<Property Id="PREVIOUS_PATH">
<ComponentSearch Id="evSearch" Guid="{010447A6-3330-41BB-8A7A-70D08ADB35E4}" />
</Property>
and here is quick CustomAction.cs I wrote:
[CustomAction]
public static ActionResult BackupFile(Session session)
{
try
{
// check out if the previous installation has our file included
// and if it does,
// then make copy of it.
var previousInstallationPath = session["PREVIOUS_PATH"];
var fileToBackup = session["FILENAME_TO_BACKUP"];
if (!string.IsNullOrEmpty(previousInstallationPath) && !string.IsNullOrEmpty(fileToBackup))
{
var absolutePath = Path.Combine(previousInstallationPath, fileToBackup);
if (File.Exists(absolutePath))
{
var destinationPath = Path.Combine(Path.GetTempPath(),
string.Concat(fileToBackup, _MODIFIER));
File.Copy(absolutePath, destinationPath);
}
}
}
catch (Exception e)
{
session.Log("Couldn't backup previous file: {0}", e);
}
return ActionResult.Success;
}
[CustomAction]
public static ActionResult RestoreFile(Session session)
{
try
{
// check if our CustomAction made backup of file,
// and if it indeed exists in temp path, then
// we basically copy it back.
var currentInstallationPath = session.CustomActionData["MYTARGET"];
var fileToRestore = session.CustomActionData["FILENAME_TO_BACKUP"];
var fileOriginalContentPath = Path.Combine(Path.GetTempPath(),
string.Concat(fileToRestore, _MODIFIER));
if (File.Exists(fileOriginalContentPath))
{
var destinationFile = Path.Combine(currentInstallationPath, fileToRestore);
if (File.Exists(destinationFile))
File.Delete(destinationFile);
File.Move(fileOriginalContentPath, destinationFile);
}
}
catch (Exception e)
{
session.Log("Couldn't restore previous file: {0}", e);
}
return ActionResult.Success;
}
to actually define sequences:
<InstallUISequence>
<Custom Action="BackupConfigFile" After="AppSearch"></Custom>
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="PropertyDelegator" Before="RestoreConfigFile" />
<Custom Action="RestoreConfigFile" After="InstallFiles"></Custom>
</InstallExecuteSequence>
haven't tested it thoroughly, but seems to do the job for now. Caveat: Temp folder might change?!
Alternatively there is this one that I found from Internet, but haven't tested it.
<!-- Support Upgrading the Product -->
<Upgrade Id="{B0FB80ED-249E-4946-87A2-08A5BCA36E7E}">
<UpgradeVersion Minimum="$(var.Version)"
OnlyDetect="yes" Property="NEWERVERSIONDETECTED" />
<UpgradeVersion Minimum="0.0.0"
Maximum="$(var.Version)" IncludeMinimum="yes"
IncludeMaximum="no"
Property="OLDERVERSIONBEINGUPGRADED" />
</Upgrade>
<Property Id="OLDERVERSIONBEINGUPGRADED" Secure="yes" />
<!-- Action to save and Restore the Config-File on reinstall
-->
<!-- We're using CAQuietExec to prevent DOS-Boxes from
popping up -->
<CustomAction Id="SetQtCmdLineCopy" Property="QtExecCmdLine"
Value=""[SystemFolder]cmd.exe" /c copy
"[INSTALLDIR]MyApp.exe.config"
"[INSTALLDIR]config.bak"" />
<CustomAction Id="QtCmdCopy" BinaryKey="WixCA"
DllEntry="CAQuietExec" Execute="immediate" />
<CustomAction Id="SetQtCmdLineRestore"
Property="QtCmdRestore" Value=""[SystemFolder]cmd.exe" /c move
/Y "[INSTALLDIR]config.bak"
"[INSTALLDIR]MyApp.exe.config"" />
<CustomAction Id="QtCmdRestore" Execute="commit"
BinaryKey="WixCA" DllEntry="CAQuietExec" />
<!-- These actions will run only for a major upgrade -->
<InstallExecuteSequence>
<Custom Action="SetQtCmdLineCopy"
After="InstallInitialize"> NOT (OLDERVERSIONBEINGUPGRADED = "")</Custom>
<Custom Action="QtCmdCopy"
After="SetQtCmdLineCopy">NOT (OLDERVERSIONBEINGUPGRADED = "")</Custom>
<Custom Action="SetQtCmdLineRestore"
Before="InstallFinalize">NOT (OLDERVERSIONBEINGUPGRADED = "")</Custom>
<Custom Action="QtCmdRestore"
After="SetQtCmdLineRestore">NOT (OLDERVERSIONBEINGUPGRADED =
"")</Custom>
</InstallExecuteSequence>
There is another option, but it may not be applicable to your scenario - it all depends on who is initially running your installer...
If your app is downloaded over the web for example, then we usually go with caveman_dick's remember property pattern.
However, we have a couple of suites of products that are always installed by our own installation staff who visit a clients site. In this instance, simply do not include the config file in the installer at all!
Put simply - if the installer doesn't know about a file, then it won't uninstall it!
In this case you have the option of your installation team creating and configuring the config file, or your app creating it when it doesn't exist, and asking the user for the values.
As stated this won't be an option in some scenarios, but it works fine for ours.
Add Schedule="afterInstallExecuteAgain" in the MajorUpgrade
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallExecuteAgain" />
It work for me
I have a scenario to install two MSI packages with single MSI package.
For example we have two products to install viz. Sample1.MSI and Sample2.MSI.
We need to embed Sample2.MSI package into Sample1.MSI.
If we install Sample1.MSI it should install both Sample1.MSI and Sample2.MSI and this should create two entries in Add or Remove programs (appwiz.cpl).
After a search I found a sample app which is using 'EmbeddedChainer' tag which is working properly with installation. But it is not uninstalling properly.
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="SampleSetup">
<Component Id="InstallMSIComponent" Guid="{7091DE57-7BE3-4b0d-95D5-07EEF6463B62}">
<File Id="ChainRunner.exe" Name="ChainRunner.exe"
Source="C:\ChainRunner.exe"
DiskId="1" KeyPath="yes"/>
<File Id="TestFile.txt" Name="TestFile.txt" Source="TestFile.txt" />
<File Id="Microsoft.Deployment.WindowsInstaller.dll" Name="Microsoft.Deployment.WindowsInstaller.dll" Source="C:\Microsoft.Deployment.WindowsInstaller.dll"/>
<File Id="Microsoft.Deployment.WindowsInstaller.xml" Name="Microsoft.Deployment.WindowsInstaller.xml" Source="C:\Microsoft.Deployment.WindowsInstaller.xml"/>
<RemoveFolder Id='INSTALLLOCATION' On='uninstall' />
</Component>
<Component Id="SampleSetup2Component" Guid="{CB568AA4-9790-4efd-91BB-82682F063321}">
<File Id="SampleSetup2.msi" Name="SampleSetup2.msi"
Source="SampleSetup2.msi"
DiskId="1" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
</Directory>
<EmbeddedChainer Id="Chainer" FileSource="ChainRunner.exe"/>
<Feature Id="ProductFeature" Title="SampleSetup" Level="1">
<ComponentRef Id="InstallMSIComponent"/>
<ComponentRef Id="SampleSetup2Component"/>
<ComponentGroupRef Id="Product.Generated" />
</Feature>
ChainRunner code
public class CustomActions
{
static void Main(string[] args)
{
try
{
IntPtr ptr = new IntPtr(Convert.ToInt32(args[0], 16));
ptr = System.Runtime.InteropServices.Marshal.StringToCoTaskMemAuto(args[0]);
Transaction transaction = Transaction.FromHandle(ptr, true);
transaction.Join(TransactionAttributes.JoinExistingEmbeddedUI);
Installer.InstallProduct(#"C:\SampleSetup2.msi", "");
transaction.Commit();
transaction.Close();
}
catch (Exception e)
{
Console.WriteLine("Exception in Installation:"+e.Message+"\n"+e.StackTrace.ToString());
Console.ReadKey();
throw e;
}
}
[CustomAction]
public static ActionResult CustomAction1(Session session)
{
session.Log("Begin CustomAction1");
return ActionResult.Success;
}
}
Where the this has been messed up?
Please advice if there is any other best way other than this approach?
You can use Wix Burn to create a setup package contained multiple application installers:
Wix Toolset: Building Installation Package Bundles;
Neil Sleightholm's Blog: WiX Burn – tips/tricks.
I am trying to install multiple .msi(s) using a Windows Installer Embedded chain. I researched some information from a website and I tried it. But it doesn't work (WiX + C#).
When I checked the debug, it works successfully until transaction.Commit() and transaction.close(). But SampleApp.msi didn't install.
'Multiple MISs Installer' installed successfully even if SampleApp.msi didn't work, and I can't uninstall 'Multiple MISs Installer'. The entry in the error log is "fatal error during installation".
How do I fix it?
Multiple MSIs Installer\Product.wxs
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="Multiple Installer">
<Component Id="InstallMSIComponent" Guid="{7091DE57-7BE3-4b0d-95D5-07EEF6463B62}">
<File Id="FILE_MprjChainer.exe" Name="MprjChainer.exe"
Source="C:\A_VS2008\WiX\MprjChainer\MprjChainer\bin\Debug\MprjChainer.exe"
DiskId="1" KeyPath="yes"/>
<File Id="Microsoft.Deployment.WindowsInstaller.dll"
Name="Microsoft.Deployment.WindowsInstaller.dll"
Source="C:\Program Files\Windows Installer XML v3.5\SDK\Microsoft.Deployment.WindowsInstaller.dll" />
<File Id="System.dll"
Name="System.dll"
Source="C:\windows\Microsoft.NET\Framework\v2.0.50727\System.dll" />
<File Id="System.Core.dll" Name="System.Core.dll"
Source="C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll" />
<File Id="System.Windows.Forms.dll" Name="System.Windows.Forms.dll"
Source="C:\windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll" />
<File Id="System.Xml.dll"
Name="System.Xml.dll"
Source="C:\windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll" />
<File Id="System.Xml.Linq.dll"
Name="System.Xml.Linq.dll"
Source="c:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll" />
<RemoveFolder Id="INSTALLLOCATION" On="uninstall"/>
</Component>
<Component Id="CMPG_SimpleApp" Guid="{CB568AA4-9790-4efd-91BB-82682F063321}">
<File Id="SimpleApp.msi"
Name="SimpleApp.msi"
Source="C:\A_VS2008\WiX\MSIs\SimpleApp.msi"
DiskId="1" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
</Directory>
<EmbeddedChainer Id="Chainer" FileSource="FILE_MprjChainer.exe"/>
<Feature Id="ProductFeature" Title="MPrjInstaller" Level="1">
<ComponentRef Id="InstallMSIComponent"/>
<ComponentRef Id="CMPG_SimpleApp"/>
<ComponentGroupRef Id="Product.Generated" />
</Feature>
Chainer\CustonAction.cs
namespace MprjChainer
{
public class CustomActions
{
static void Main(string[] args)
{
System.Diagnostics.Debugger.Launch();
try
{
IntPtr ptr = new IntPtr(Convert.ToInt32(args[0], 16));
Transaction transaction = Transaction.FromHandle(ptr, true);
transaction.Join(TransactionAttributes.JoinExistingEmbeddedUI);
transaction.Commit();
transaction.Close();
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show("e.Message; " + e.Message);
throw e;
}
}
[CustomAction]
public static ActionResult CustomAction1(Session session)
{
System.Diagnostics.Debugger.Launch();
session.Log("Begin CustomAction1");
return ActionResult.Success;
}
}
}
Try calling InstallProduct():
transaction.Join(TransactionAttributes.JoinExistingEmbeddedUI);
// start another installation in the same transaction
Installer.InstallProduct(#"path_To_Your_MSI", arguments);
// it's good to include /l*v install.log in the arguments so that you'll get a verbose log of the installation
transaction.Commit();
An example of Embedded Chainer can be seen at: WiX EmbeddedChainer Examples?
You could also look at Burn (the bootstrapper/chainer from Wix) for chaining multiple msi packages: http://robmensching.com/blog/posts/2009/7/14/Lets-talk-about-Burn
There is a bug in the c# code:
In the line "IntPtr ptr = new IntPtr(Convert.ToInt32(args[0], 16));" the "16" must be a "10"!
Otherwise you will get "bad handle" errors when there are more than 10 transactions (e.g. when there are five or more sub msi's called from the embedded chainer).
when I get my achitecture type like this:
<Property Id="PLATTFORM">
<RegistrySearch Id="myRegSearchPalttform"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
Name="PROCESSOR_ARCHITECTURE"
Type="raw">
</RegistrySearch>
</Property>
and want to check if it is "AMD64" like this:
<?define myPlattform = [PLATTFORM] ?>
<?if $(var.myPlattform) = AMD64 ?>
some stuff
<?else ?>
some stuff
<?endif ?>
it fails.
When I set the value static:
<?define stest = AMD64 ?>
<?if $(var.stest) = AMD64 ?>
it goes in true scope. So why is the value from the registry(there is the value AMD64) not identical with my proof string????
Tanx in advance
<?define myPlattform = [PLATTFORM] ?>
Probably because myPlattform is a preprocessor variable and gets assigned before the PLATTFORM property ever has a value. If you want to conditionally install different components, you can try this way: How to use conditions in features in WiX?
This questions is possibly a duplicate of Is there a way to set a preprocessor variable to the value of a property?.
Update: If your goal is to set an installation location based on architecture, and your architecture is determined by the "PLATTFORM" property using the registry search you specified, then you could try the following:
<Property Id="PLATTFORM">
<RegistrySearch Id="myRegSearchPalttform"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
Name="PROCESSOR_ARCHITECTURE"
Type="raw">
</RegistrySearch>
</Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="SomeValue" />
</Directory>
</Directory>
<ComponentGroup Id="ProductComponentGroup">
<Component Id="ProductComponent" Guid="INSERT-GUID-HERE" Directory="INSTALLFOLDER">
<File Id="TestTextFile.txt" Source=".\TestTextFile.txt" KeyPath="yes"/>
</Component>
</ComponentGroup>
<Feature Id="ProductFeature" Level="1">
<ComponentGroupRef Id="ProductComponentGroup"/>
</Feature>
<SetDirectory Id="INSTALLFOLDER" Value="[ProgramFilesFolder]\SomeOtherValue">
PLATTFORM="AMD"
</SetDirectory>
Note: See that I used the SetDirectory element. I generally download the WiX weekly releases and never used that element until testing the above sample. Therefore I'm not sure what version SetDirectory was first introduced.