Package the .Net redistributable in my burn bootstrapper - wix

I'm trying to package the .Net 4.5.2 redistributable into my burn application, by following the instructions on this page.
But it fails to find the file in the temp burn location.
According to the log burn tries to find the file here:
[0A14:09C4][2015-05-12T16:48:52]w343: Prompt for source of package: NetFx452Redist, payload: NetFx452Redist, path: `C:\Users\simon\Desktop\redist\NDP452-KB2901907-x86-x64-AllOS-ENU.exe`
But the file actually ends up in a temporary folder
eg.
C:\Users\simon\AppData\Local\Temp\{f5207472-d2a0-4b00-b9ee-c535385bde58}\redist\NDP452-KB2901907-x86-x64-AllOS-ENU.exe
The instructions say to do this:
<PayloadGroup Id="NetFx452RedistPayload">
<Payload Name="redist\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
SourceFile="..\..\Binaries\Microsoft\NetFramework\4.5.2\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"/>
</PayloadGroup>
How can I make Burn look in the correct location for the .net installer?

Rather than reference NetFxExtension you could gain more control by directly referencing the .NET install package:
<Fragment>
<!-- Value of the 'Release' registry value when .NET 4.5.2 is installed -->
<?define NetFx452MinRelease = 379893 ?>
<!-- Get the release of the .NET V4 framework currently installed -->
<util:RegistrySearch
Id="NETFRAMEWORK4"
Variable="NETFRAMEWORK4"
Root="HKLM"
Key="SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"
Value="Release"
Result="value" />
<!-- Install .NET 4.5.2 if not already installed -->
<PackageGroup Id="NetFx4FullRedist">
<ExePackage
Id="NetFx4FullRedist"
Cache="no"
Compressed="yes"
InstallCommand="/q /norestart /ChainingPackage "[WixBundleName]""
RepairCommand="/q /norestart /repair /ChainingPackage "[WixBundleName]""
UninstallCommand="/uninstall /q /norestart /ChainingPackage "[WixBundleName]""
PerMachine="yes"
DetectCondition="NETFRAMEWORK4 >= $(var.NetFx452MinRelease)"
Vital="yes"
Permanent="yes"
Protocol="netfx4"
SourceFile="..\..\Binaries\Microsoft\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
Name="Redist\NDP452-KB2901907-x86-x64-AllOS-ENU.exe">
</ExePackage>
</PackageGroup>
</Fragment>
Then reference NetFx4FullRedist in your <chain>..</chain>:
<!-- .NET runtime full profile -->
<PackageGroupRef Id="NetFx4FullRedist"/>
I've used a variation on this to include the .NET 4 Client redistributable into my burn generated installer application without any issues.

Adding an ExePackage element for the DotNet framework will work, but it is also possible to use the PayloadGroup:
<PayloadGroup Id="NetFx452RedistPayload">
<Payload Name="redist\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
SourceFile="X:\path\to\redists\in\repo\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"/>
<PayloadGroup/>
Reference this group so that it is included in your payload:
<BootstrapperApplicationRef Id="ManagedBootstrapperApplicationHost">
<PayloadGroupRef Id="NetFx452RedistPayload"/>
You also need to add a reference to NetFx452Redist in your chain:
<Chain>
<PackageGroupRef Id="NetFx452Redist" />
During the Apply phase, the source will fail to resolve (unless you have NDP452-KB2901907-x86-x64-AllOS-ENU.exe in a folder named "redist" relative to your bundle). To point to the NDP452-KB2901907-x86-x64-AllOS-ENU.exe included in your payload, the trick is to add an event handler for the ResolveSource event.
ResolveSource += ResolveSourceHandler;
private void ResolveSourceHandler(object sender, ResolveSourceEventArgs args)
{
try
{
//Everything from the "CurrentBundle" class is from my model.
//The model is built from parsing BootstrapperApplicationData.xml.
//Specifically, payload comes from WixPayloadProperties.
var payload = CurrentBundle.PayloadList.FirstOrDefault(x => string.Compare(x.Id, args.PayloadId, StringComparison.OrdinalIgnoreCase) == 0);
var newPayloadPath = (payload != null) ? Path.Combine(CurrentBundle.WorkingFolder, payload.Name) : string.Empty;
if (File.Exists(newPayloadPath))
{
Engine.SetLocalSource(args.PackageOrContainerId, args.PayloadId, newPayloadPath);
args.Result = Result.Retry;
}
else if (File.Exists(args.LocalSource) == false && string.IsNullOrEmpty(args.DownloadSource) == false)
{
args.Result = Result.Download;
}
}
catch (Exception ex)
{
args.Result = Result.Error;
}
}

Related

How to Use CustomAction in Bootstrapper ExePackage DetectCondition to realize net core installed or not

I am trying to use customAction method in a Bootstrapper project to determined specific version of .net core is installed or not, but it not work
here is my wsx file code
<Fragment>
<WixVariable Id="TargetFileName" Value="[WixBundleSourceProcessPath]" Overridable="yes" />
<Binary Id="CustomAction.dll" SourceFile="$(var.CustomAction.TargetDir)CustomAction.CA.dll" />
<CustomAction Id="CheckASPNETCore" Return="check" Execute="immediate" Impersonate="no" BinaryKey=".CustomAction.dll" DllEntry="TestMethod" />
<WixVariable Id="ASPDOTNETCORE60" Value="[ASPDOTNETCORE60]" Overridable="yes" />
<PackageGroup Id="NetRuntime6Web">
<ExePackage
Name="dotnet-hosting-6.0.8-win.exe"
InstallCommand="/install /quiet /norestart /log "[AspNetCoreRuntime6Log]""
RepairCommand="/repair /quiet /norestart /log "[AspNetCoreRuntime6Log]""
UninstallCommand="/uninstall /quiet /norestart /log "[AspNetCoreRuntime6Log]""
PerMachine="yes"
DetectCondition="[ASPDOTNETCORE60] = 1"
Vital="yes"
Permanent="yes"
Protocol="burn"
DownloadUrl="https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-aspnetcore-6.0.8-windows-hosting-bundle-installer"
LogPathVariable="AspNetCoreRuntime6Log"
SourceFile="C:\Net\dotnet-hosting-6.0.8-win.exe"
Compressed="yes">
</ExePackage>
</PackageGroup>
</Fragment>
[CustomAction]
public static ActionResult TestMethod(Session session)
{
session.Log($"Test Method Run");
session["ASPDOTNETCORE60"] = "1";
return ActionResult.Success;
}
and here is my customAction
and this is the error I'm getting where run exe file
[26A0:E8C4][2022-12-01T12:17:46]e000: Error 0x8007000d: Failed to parse condition "[ASPDOTNETCORE60] = 1". Unexpected character at position 0.
how can I use that customAction ???
Each version of the .NET Runtime will create a directory in Program Files\dotnet\shared\Microsoft.NETCore.App\
The existance of the directory Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.5 confirms the existance of .NET Runtime version 6.0.5. Using this knowledge, you can do a simple DirectorySearch in Wix to check whether a specific version of the runtime is installed like so:
<!-- Check if NET 6.0 Runtime already installed -->
<util:DirectorySearch
Path="[ProgramFiles6432Folder]dotnet\shared\Microsoft.NETCore.App\6.0.5"
Result="exists"
Variable="Net6RuntimeExists"/>
Here's the (almost) full file for clarity:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle ...>
... code removed for brevity
<!-- Check if NET 6.0 Runtime already installed -->
<util:DirectorySearch
Path="[ProgramFiles6432Folder]dotnet\shared\Microsoft.NETCore.App\6.0.5"
Result="exists"
Variable="Net6RuntimeExists" />
<Chain>
<!--This will only be installed when the .NET 6 Runtime is not already installed-->
<ExePackage
Id="TheNet6Runtime"
DisplayName=".NET 6 Desktop Runtime"
Vital="no"
Cache="no"
Permanent="yes"
InstallCommand="/install /quiet /norestart"
SourceFile="windowsdesktop-runtime-6.0.5-win-x64.exe"
DetectCondition="Net6RuntimeExists" />
... code removed for brevity
</Chain>
</Bundle>
</Wix>

Upgrade to a newer version using WIX Installer doesn't copy the new files

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.

WIX does not remove the web applications after uninstall in wix

We are using WIX 3.10 to create web applications under default website, this implementation works fine but the issue is with uninstall of the product that it does not remove the web application under the default website.
The Website port and name are entered by user as part of custom dialog which are stored in respective properties.further during uninstall these properties are restored using wix toolset remember property http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/ which sets the web application property during uninstall, but still the application is not removed on uninstall.
Uninstall log that sets the website properties after Appsearch:
Action start 15:17:04: AppSearch.
MSI (s) (24:64) [15:17:04:657]: Note: 1: 2262 2: Signature 3: -2147287038
MSI (s) (24:64) [15:17:04:657]: PROPERTY CHANGE: Modifying WEBSITEPORTPROPERTY property. Its current value is 'WEBSITEPORT'. Its new value: '80'.
MSI (s) (24:64) [15:17:04:657]: Note: 1: 2262 2: Signature 3: -2147287038
MSI (s) (24:64) [15:17:04:657]: PROPERTY CHANGE: Modifying WEBSITEPROPERTY property. Its current value is 'WEBSITE'. Its new value: 'Default Web Site'.
<Fragment>
<iis:WebSite Id="SITE" Description="[WEBSITE]">
<iis:WebAddress Id="AllUnassigned" Port="[WEBSITEPORT]"/>
</iis:WebSite>
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="TestAppPool" Guid="GUID" KeyPath="yes" Permanent="yes">
<iis:WebAppPool Id="TestAppPool"
Name="Test Net 4.0"
Identity="applicationPoolIdentity"
ManagedPipelineMode="Integrated"
ManagedRuntimeVersion="v4.0" />
</Component>
<Component Id="IIS.Component" Guid="GUID" KeyPath="yes" Permanent="no" Win64="yes">
<iis:WebVirtualDir Id="VirtualDir" Alias="[APPLICATION_NAME]" Directory="Dir" WebSite="SITE" >
<iis:WebApplication Id="Application" Name="[APPLICATION_NAME]" WebAppPool="TestAppPool"/>
</iis:WebVirtualDir>
<RegistryValue Root='HKLM' Key='SOFTWARE\Test\Prod' Name='Website' Value='[WEBSITE]' Type='string' Action='write' />
<RegistryValue Root='HKLM' Key='SOFTWARE\Test\Prod' Name='WebsitePort' Value='[WEBSITEPORT]' Type='string' Action='write' />
<RegistryValue Root='HKLM' Key='SOFTWARE\Test\Prod' Name='Application' Value='[APPLICATION_NAME]' Type='string' Action='write' />
</Component>
</DirectoryRef>
</Fragment>
There is another issue related, repair operation through Add Remove Programs throws a Fatal error with the following error:
WriteIIS7ConfigChanges: Entering WriteIIS7ConfigChanges in C:\Windows\Installer\MSICDDF.tmp, version 3.10.3007.0
WriteIIS7ConfigChanges: Custom action data hash: 41034C345A4E6B9B4DDB8490C1BC5266637BC0E8
WriteIIS7ConfigChanges: CustomActionData WriteIIS7ConfigChanges length: 225
WriteIIS7ConfigChanges: Error 0x80070002: Site not found for create application
WriteIIS7ConfigChanges: Error 0x80070002: Failed to configure IIS application.
WriteIIS7ConfigChanges: Error 0x80070002: WriteIIS7ConfigChanges Failed.
Any other alternative solution that would solve the issue?

How to keep a config file when major upgrade in wix v3.8?

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

How can I set recovery-options of a service with WiX?

I have following .wxs-file:
<?xml version="1.0" encoding="UTF-8"?>
<?define ProductVersion="x.x.x.x" ?>
<?define UpgradeCode="{**MYGUID**}" ?>
<?define Manufacturer="My Company" ?>
<?define ProductName="My Product" ?>
<?define SkuName="MyProduct" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*"
Name="$(var.ProductName)"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="$(var.Manufacturer)"
UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="301"
Compressed="yes"
InstallPrivileges="elevated"
InstallScope="perMachine"
Platform="x86" />
<Media Id="1"
Cabinet="$(var.SkuName).cab"
EmbedCab="yes" />
<Directory Id="TARGETDIR"
Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="ManufacturereDirectory"
Name="$(var.Manufacturer)">
<Directory Id="ProductDirectory"
Name="$(var.ProductName)" />
</Directory>
</Directory>
</Directory>
<ComponentGroup Id="MainComponentGroup">
<Component Directory="ProductDirectory">
<File Name="$(var.MyProject.TargetFileName)"
Source="$(var.MyProject.TargetPath)"
KeyPath="yes"
Vital="yes" />
<ServiceInstall Id="SeviceInstall"
Name="$(var.ProductName)"
DisplayName="$(var.ProductName)"
Type="ownProcess"
Interactive="no"
Start="auto"
Vital="yes"
ErrorControl="normal"
Account="LOCALSYSTEM">
</ServiceInstall>
<ServiceControl Id="ServiceControl_Start"
Name="$(var.ProductName)"
Start="install"
Wait="no" />
<ServiceControl Id="ServiceControl_Stop"
Name="$(var.ProductName)"
Stop="both"
Remove="uninstall"
Wait="yes" />
</Component>
</ComponentGroup>
<Feature Id="MainFeature"
Level="1">
<ComponentGroupRef Id="MainComponentGroup" />
</Feature>
<Upgrade Id="$(var.UpgradeCode)">
<UpgradeVersion Property="UPGRADEFOUND"
Minimum="0.0.0.1" IncludeMinimum="yes"
Maximum="$(var.ProductVersion)" IncludeMaximum="yes"
OnlyDetect="no"
IgnoreRemoveFailure="yes"
MigrateFeatures="yes"/>
</Upgrade>
<CustomAction Id="ServiceRestarter"
Directory="ProductDirectory"
ExeCommand=""[SystemFolder]sc.exe" failure "$(var.ProductName)" reset= 60 actions= restart/0"
Impersonate="no" />
<InstallExecuteSequence>
<InstallExecute Before="RemoveExistingProducts" />
<RemoveExistingProducts Before="InstallFinalize" />
<Custom Action="ServiceRestarter" After="InstallFinalize"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
</Product>
</Wix>
Before that, I've tried:
<CustomAction Id="ServiceRestarter"
Property="QtExecCmdLine"
Value='"[SystemFolder]sc.exe" failure "$(var.ProductName)" reset= 60 actions= restart/0' />
which apparently called sc.exe - but changed nothing ...
Before that I've tried:
<ServiceInstall Id="SeviceInstall"
Name="$(var.ProductName)"
DisplayName="$(var.ProductName)"
Type="ownProcess"
Interactive="no"
Start="auto"
Vital="yes"
ErrorControl="normal"
Account="LOCALSYSTEM">
<ServiceConfig Id="ServiceConfig"
DelayedAutoStart="yes"
OnInstall="yes"
OnReinstall="yes"
OnUninstall="no"
FailureActionsWhen="failedToStopOrReturnedError" />
<ServiceConfigFailureActions Id="ServiceRestarter"
OnInstall="yes"
OnReinstall="yes"
OnUninstall="no"
ResetPeriod="0">
<Failure Action="restartService" Delay="0" />
<Failure Action="restartService" Delay="0" />
<Failure Action="restartService" Delay="0" />
</ServiceConfigFailureActions>
</ServiceInstall>
which did not work, as the MsiServiceConfigFailureActions table does not work if using an installer < 5.0, and even if using InstallerVersion="500" the only thing I get is an error:
Serivce 'My Product' (My Product) could not be configured. This could
be a problem with the package or your permissions. Verify that you
have sufficient privileges to configure system services.
(and yes, ... I've tried InstallPrivilges="elevated" also - but ... the real issue is Action="restartService" according to this)
So ... using a CustomAction is the way to go (or not?).
I have following output of the log
MSI (s) (34:28) [13:56:46:914]: Note: 1: 1722 2: ServiceRestarter 3: C:\Program Files (x86)\My Company\My Product\ 4: "C:\Windows\SysWOW64\sc.exe" failure "My Product" reset= 60 actions= restart/0
MSI (s) (34:28) [13:56:46:914]: Note: 1: 2205 2: 3: Error
MSI (s) (34:28) [13:56:46:914]: Note: 1: 2228 2: 3: Error 4: SELECT Message FROM Error WHERE Error = 1722
MSI (c) (2C:0C) [13:56:46:914]: Font created. Charset: Req=0, Ret=0, Font: Req=MS Shell Dlg, Ret=MS Shell Dlg
Error 1722. There is a problem with this Windows Installer package. A program run as part of the setup did not finish as expected. Contact your support personnel or package vendor. Action ServiceRestarter, location: C:\Program Files (x86)\My Company\My Product\, command: "C:\Windows\SysWOW64\sc.exe" failure "My Product" reset= 60 actions= restart/0
MSI (s) (34:28) [13:56:48:849]: Note: 1: 2205 2: 3: Error
MSI (s) (34:28) [13:56:48:849]: Note: 1: 2228 2: 3: Error 4: SELECT Message FROM Error WHERE Error = 1709
MSI (s) (34:28) [13:56:48:849]: Product: My Product -- Error 1722. There is a problem with this Windows Installer package. A program run as part of the setup did not finish as expected. Contact your support personnel or package vendor. Action ServiceRestarter, location: C:\Program Files (x86)\My Company\My Product\, command: "C:\Windows\SysWOW64\sc.exe" failure "My Product" reset= 60 actions= restart/0
Action ended 13:56:48: ServiceRestarter. Return value 3.
Action ended 13:56:48: INSTALL. Return value 3.
Can anybody help me out?
edit
I've used the old ServiceConfig-extension:
<util:ServiceConfig xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
FirstFailureActionType="restart"
SecondFailureActionType="restart"
ThirdFailureActionType="restart"
ResetPeriodInDays="1"
RestartServiceDelayInSeconds="20" />
which gives me a following build-error:
error CNDL0200: The ServiceInstall element contains an unhandled
extension element 'util:ServiceConfig'. Please ensure that the
extension for elements in the
'http://schemas.microsoft.com/wix/UtilExtension' namespace has been
provided.
I know that I can resolve this error by using -ext WixUtilExtension via commandline - but I want to use Visual Studio for building ... So how can I adapt the build-command?
Only chance is to add a reference to WixUtilExtension.dll to my project.
I can see that you've only tried the ServiceConfig element, which came with MSI 5.0. However, there's another ServiceConfig element in UtilExtension, which has been there for a long time and it seems that the thread you mention in your question confirms that it works.
The util:ServiceConfig element contains 3 parameters you'd like to use: FirstFailureActionType, SecondFailureActionType and ThirdFailureActionType, all accepting the same enumeration of values - none, reboot, restart and runCommand.
Try it out and if it works, it is far better choice than a custom action.
For WIX V 4.0, building with VS2015, the following works:
1: Ensure that WixUtilExtension.dll assembly is referenced by WIX project.
2: Add http://wixtoolset.org/schemas/v4/wxs/util ns to root Wix element. Note that this is the correct NS for WIX 4.0 (NOT http://schemas.microsoft.com/wix/UtilExtension as for previous versions).
<Wix
xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"
>
3: Ensure that ServiceConfig element is prefixed with correct namespace reference.
<ServiceInstall
Id="MyService"
Type="ownProcess"
Name="MyService"
DisplayName="MyService"
Description="My Service"
Start="auto"
Account="[SERVICEACCOUNT]"
Password="[SERVICEPASSWORD]"
ErrorControl="normal"
>
<util:ServiceConfig
FirstFailureActionType='restart'
SecondFailureActionType='restart'
ThirdFailureActionType='restart'
RestartServiceDelayInSeconds='30'
ResetPeriodInDays='1'/>
</ServiceInstall>
In Visual Studio, to avoid using -ext in CLI you may do the following:
Of course, you add a resource: <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
then, in Solution Explorer -> References -> Add..
WixUtilExtension.dll
After that everything works like a charm.
(wix 3.10)
Of course, if you do use the second ServiceConfig from utils. Like <util:ServiceConfig blablabla