WIX Install .NET and VSTO if Admin, fail early if non-Admin - wix

I'm trying to deploy an Excel plug-in to the mass market. The plug-in requires VSTO runtime (vstor_redist.exe) and .NET 4.5 (for 64 bit OS) or .NET 4.0 (for 32 bit OS). I'm willing to assume that potential users already have at least .NET 2.0. Currently my setup consists of:
Separate 32 bit and 64 bit WIX .msi installers which install the plug-in in per-user mode.
Separate 32 bit and 64 bit bootstrapper bundles which wrap each .msi and install VSTO runtime and .NET as shown in the code below:
<util:RegistrySearch Id="VSTORuntimeTest" Root="HKLM" Key="SOFTWARE\Microsoft\VSTO Runtime Setup\v4R\" Value="VSTORFeature_CLR40" Variable="VSTORFeature"/>
<util:RegistrySearch Id="VSTORuntimeVersionV4R" Root="HKLM" Key="SOFTWARE\Microsoft\VSTO Runtime Setup\v4R\" Value="Version" Variable="VSTORVersionV4R"/>
<util:RegistrySearch Id="VSTORuntimeVersionV4" Root="HKLM" Key="SOFTWARE\Microsoft\VSTO Runtime Setup\v4\" Value="Version" Variable="VSTORVersionV4"/>
<Chain>
<!-- Install .Net 4.0 or 4.5, depending on build -->
<?ifdef x64?>
<PackageGroupRef Id="NetFx45Web" />
<?endif ?>
<?ifdef x86?>
<PackageGroupRef Id="NetFx40Web" />
<?endif ?>
<RollbackBoundary />
<!-- Install VSTO runtime -->
<ExePackage Id="VSTORuntime" SourceFile="..\resources\vstor_redist.exe" Permanent="yes" Vital="yes" Cache="no" Compressed="no"
DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=158917"
PerMachine="yes"
InstallCommand="/q /norestart"
DetectCondition="VSTORFeature"
InstallCondition="NOT VSTORFeature OR NOT (VSTORVersionV4R >=v10.0.40303) OR NOT (VSTORVersionV4 >=v10.0.21022)" />
<RollbackBoundary />
<?ifdef x64?>
<MsiPackage
Id="nx_msi_package_version"
SourceFile="..\My 64 bit Setup.msi"
Compressed="yes"
Vital="yes" />
<?endif ?>
<?ifdef x86?>
<MsiPackage
Id="nx_msi_package_version"
SourceFile="..\My 32 bit Setup.msi"
Compressed="yes"
Vital="yes" />
<?endif ?>
</Chain>
A .NET 2.0 wrapper which includes both the bootstrappers as embedded resources and deploys & runs the correct bootstrapper for the client OS. (Essentially Yochai Timmer's answer in Single MSI to install correct 32 or 64 bit c# application)
The whole thing feels like a turducken, but it works nicely for both silent upgrades and for fresh installs when the user has admin credentials. But if the user is not an admin and does not yet have both VSTO and the appropriate .NET installed, it fails ungracefully with the error: "0x8007051b - This security ID may not be assigned as the owner of the object" after a long download process.
What I would like to do is check in advance whether the user needs to ask an administrator to intervene and install VTSO and/or .NET for them, and display a message, ideally with website link(s), when this is the case. This check could be in the bootstrapper, or in my .NET 2.0 wrapper. Any recommendation on how best to do this?
Thanks, Eric

The best way for me was to do the work in my 32-bit C# wrapper. As the code in my question indicates, Burn doesn't appear to offer any built-in package with VSTO support -- instead I'm just reading the registry manually and basing my logic on that. Burn does offer pre-built packages for .NET 4.0 and .NET 4.5, but I didn't see an easy way to support Fail Early with these.
My C# 2.0 wrapper now includes the following logic:
private static bool NeedsMorePermissionToInstallPrerequisites(out string error)
{
error = string.Empty;
// Is user an admin? If so, we're OK
if (PrivilegeTester.CanBeAdmin())
{
return false;
}
// Is .NET already installed?
bool isDotNetInstalled;
string dotNetVersion;
if (Is64BitOperatingSystem())
{
isDotNetInstalled = PrereqSoftwareChecker.IsDotNet45Installed();
dotNetVersion = "Microsoft .NET 4.5";
}
else
{
isDotNetInstalled = PrereqSoftwareChecker.IsDotNet40Installed();
dotNetVersion = "Microsoft .NET 4.0";
}
// Is VTSO already installed?
bool isVtsoInstalled = PrereqSoftwareChecker.IsVstoRuntimeInstalled();
if (isVtsoInstalled && isDotNetInstalled)
{
return false;
}
// If we got this far, there's trouble. Build the error.
[...]
Here the CanBeAdmin() functionality is based on Calling IPrincipal.IsInRole on Windows 7, and the IsVstoRuntimeInstalled() looks at the exact same set of registry keys that I use in my VSTORuntimeTest, etc. logic in my original question. The .NET checks also look at the registry, using keys that are well documented in MSDN. For example:
internal static bool IsDotNet40Installed()
{
try
{
Version dotnet4Version = new Version(GetHKLMValue("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4.0\\Client", "Version").ToString());
return dotnet4Version >= new Version("4.0.0.0");
}
catch
{
return false;
}
}
private static object GetHKLMValue(string key, string valueName)
{
return Registry.GetValue("HKEY_LOCAL_MACHINE\\" + key, valueName, null);
}
It would be nice if Burn could handle something like this better, but it might be complicated, since simply providing a Fail Early .NET package and a separate Fail Early VSTO package would not be enough to build a full list of packages that would need to be installed independently by an administrator.
Using C# and .NET 2.0 in bootstrapper-wrapper code is obviously a risk here, but I'm guessing that there aren't too many old XP boxes out in the wild that have Office and don't have at least .NET 2.0.

Related

Wix Toolset prerequisites: Check for .Net Framework

I have a wix bundle project that I am editing (ver 3.10). I am trying to use the wixnetfxextensions to install .net framework 4.6 if it is not already installed. I created an exepackage that uses the WIX_IS_NETFRAMEWORK_46_OR_LATER_INSTALLED property. I'm guessing I am not using this correctly. Any help on how to use that? Currently the .net framework will not install not matter what.
<Chain>
<PackageGroupRef Id="redist_vc140" />
<PackageGroupRef Id="NetFx461Full" />
<MsiPackage Id="MSI_Installer" SourceFile="C:\Installer.msi"/>
</Chain>
<Fragment>
<PropertyRef Id="WIX_IS_NETFRAMEWORK_46_OR_LATER_INSTALLED"/>
<!-- Install .NET 4.6.1 -->
<PackageGroup Id="NetFx461Full">
<ExePackage Id="NetFx461"
DisplayName="Microsoft .NET Framework 4.6.1"
Compressed="no"
Cache="yes"
PerMachine="yes"
Permanent="yes"
Protocol="netfx4"
Vital="yes"
SourceFile="..\NDP461-KB3102436-x86-x64-AllOS-ENU.exe"
UninstallCommand="/q /norestart"
RepairCommand="/q /norestart"
DetectCondition="NOT WIX_IS_NETFRAMEWORK_46_OR_LATER_INSTALLED" />
</PackageGroup>
</Fragment>
You're doing a lot of extra work to install .net that you don't actually need to do.
To add .net 461 to your installer just include the netfxextension and add
<Bundle>
<PayloadGroup Id="NetFx461RedistPayload">
<Payload Name="redist\NDP461-KB3102436-x86-x64-AllOS-ENU.exe"
SourceFile="C:\path\to\redists\in\repo\NDP461-KB3102436-x86-x64-AllOS-ENU.exe"/>
<PayloadGroup/>
</Bundle>
so that the full installer is included in your bootstrapper. You can ignore this and then the bootstrapper will download the installer but if the customer doesn't have an internet connection they won't be able to install .net.
Then in your chain just add
<PackageGroupRef Id="NetFx461Redist"/>
I used this as a reference and checked the wix source to see what the
name of .net 461 being used is in netfxextension.
Sean Hall mentioned that bundles don't even use properties so what I had written here doesn't apply at all in this situation. (And it was also incorrect)
Did what Brian Sutherland suggested: Added the WxsVariable that compares the .netframework with the determined minimum release number. then make that a detectcondition in the exepackage

Using WiX, how can I download a dependency when the initial URL failed to download

I'd like to provide my WiX bundle an alternate download(s) URL for grabbing dependencies. I haven't been able to track down documentation on this scenario, so I don't even know if it's possible outside of a hackish Custom Action workaround. Right now, when any attempted download fails, the installation fails, as intended. I'd like it to simply try the next URL(s) with public or internal mirrors if the initial attempt fails until it either succeeds or hard-fails from none of them working. I already have normal dependency packages implemented. I'm just not sure where to go from here to achieve this design. I'd like to do it outside of custom actions, if possible.
Current code example:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Fragment>
<!-- Check registry keys for Microsoft Visual C++ 2005 Redistributable -->
<util:RegistrySearch Root="HKLM"
Key="SOFTWARE\Classes\Installer\Products\1af2a8da7e60d0b429d7e6453b3d0182"
Result="exists"
Variable="MVC2005Present_x64"
Win64="yes"/>
<!-- Package to deploy Microsoft Visual C++ 2005 Redistributable (x64) -->
<ExePackage Id="MVC2005Exe_x64"
Name="Microsoft Visual C++ 2005 Redistributable Package (x64)"
Cache="no"
Compressed="no"
PerMachine="yes"
DownloadUrl="https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE"
Permanent="yes"
InstallCommand="/q"
RepairCommand="/q"
DetectCondition="MVC2005Present_x64"
InstallCondition="DeployDependencies = 1">
<RemotePayload CertificatePublicKey="5C499B10F7EF186DC729991A262AB52066423909"
CertificateThumbprint="93859EBF98AFDEB488CCFA263899640E81BC49F1"
Description="Microsoft Visual C++ 2005 Redistributable (x64) Setup"
Hash="EE916012783024DAC67FC606457377932C826F05"
ProductName="Microsoft Visual C++ 2005 Redistributable (x64)"
Size="3175832"
Version="6.0.2900.2180" />
<ExitCode Behavior="scheduleReboot" Value="1641" />
<ExitCode Behavior="scheduleReboot" Value="3010" />
</ExePackage>
</Fragment>
</Wix>
Burn supports one download URL per payload. A custom bootstrapper application gets an OnResolveSource callback where it can supply an alternate download location when the one authored in the bundle fails.

WiX msxml 6 prerequisite

I am creating a WiX installer for our software which requires msxml 6. If I understand correctly msxml 6 is shipped with Windows starting from XP SP3 but since our software supports all versions of XP I guess there is a risk that the customer won't have msxml 6 installed; thus I would like msxml 6 to be installed by our installer if it is not already installed.
I found this page that shows how to detect if msxml 6 is installed on the computer but it doesn't say which version (SP1, SP2 etc.) is installed.
My question is how do I properly detect if msxml 6 is installed and install the latest version if it is not detected?
This is what I am using now to perform the detection (a combination of what I found in the link above and what I use for other prerequisites):
<!-- MSXML6 SP1 (x86) -->
<util:RegistrySearch Root="HKCR" Key="Msxml2.DOMDocument.6.0" Format="raw"
Variable="MsXml6x86Installed" />
<PackageGroup Id="MsXml6x86">
<MsiPackage Id="MsXml6x86" Cache="no" Compressed="yes" Permanent="yes" Vital="yes"
SourceFile="$(var.PrerequisitesPackagesRootPath)\msxml6_SP1_x86\msxml6_x86.msi"
InstallCondition="(VersionNT < v6.0) AND (NOT MsXml6x86Installed)" />
</PackageGroup>
<!-- MSXML6 SP1 (x64) -->
<util:RegistrySearch Root="HKCR" Key="Msxml2.DOMDocument.6.0" Format="raw"
Variable="MsXml6x64Installed" Win64="yes" />
<PackageGroup Id="MsXml6x64">
<MsiPackage Id="MsXml6x64" Cache="no" Compressed="yes" Permanent="yes" Vital="yes"
SourceFile="$(var.PrerequisitesPackagesRootPath)\msxml6_SP1_x64\msxml6_x64.msi"
InstallCondition="(VersionNT64 < v6.0) AND (NOT MsXml6x64Installed)" />
</PackageGroup>
And in my Bundle (x86):
<Bundle>
...
<Chain>
<PackageGroupRef Id="MsXml6x86"/>
<PackageGroupRef Id="Vc2010Sp1x86" />
<PackageGroupRef Id="Netfx35Sp1" />
<PackageGroupRef Id="Netfx4Full" />
...
</Chain>
</Bundle>
When I start my installer I get the following output in the log file:
[21E4:3F00][2015-02-11T09:57:31]i000: Setting string variable 'MsXml6x86Installed' to value 'XML DOM Document 6.0'
[21E4:3F00][2015-02-11T09:57:31]i101: Detected package: MsXml6x86, state: Absent, cached: None
So the registry key I search for is found which suggests msxml 6 exists but the actual package does not. I guess the reason is that when I search for the msxml6.dll my Win 7 computer appears to have SP3 installed while the msxml package I am using in the installer is for SP1? But this is where I don't know what to do; on our Win 7 test computer msxml 6 SP3 is installed by default and our Win XP SP3 test computer has msxml 6 SP2 installed by default. Which msxml package should I use in the installer in order to get a solution for all versions of Windows starting from XP? I am not able to find a download link to SP3 or SP2 om Microsoft's website.
I appreciate any help.
I don't believe the page you linked is very well thought out. What you need to do (in general) is observe the footprint of any given prereq and make the best choice on how to detect it. For MSXML6 I edited the MSI using ORCA and found the following in the registry table:
SOFTWARE\Microsoft\MSXML 6.0 Parser and SDK\CurrentVersion
PatchLevel = 6.00.3883.8
I would go find different sp levels of that MSI and see if you find a trend and then use that in your logic.
Otherwise it is very sound and reasonable approach to put a business/engineering requirement of XP SP3 on your application. If a customer is going to run a legacy OS they should atleast be up to the latest SP. Microsoft has cut off XP of Windows Update and you are on very solid ground to no longer support this OS. The result would be a simpler more reliable installer with less development and test costs.

WiX bootstrapper disable .NET Framework installation chaining

In my WiX installer, I do not want .NET installed automatically if it isn't installed. It should just give a warning or merely error out.
The reason to avoid it is explained here:
One HUGE word of
caution here: Because the .Net installer will technically be part of
your install chain, if the user installs .Net but then cancels your
install, your installer will still be listed in the Add/Remove
programs since one if it’s components (the .Net installer) completed.
Tread with caution.
He seems to be intentionally including it. But I'm not and I happened to set <supportedRuntime version="v4.5" /> which I guess isn't a real version (4.5 => 4.0 as far as this is concerned?). My WiX managed bootstrapper application exe automatically prompted me to download and install the "missing" .NET Framework.
For computers that ARE missing 4.0, I don't want this to happen. WiX also complains if I leave out:
<WixVariable Id="WixMbaPrereqLicenseUrl" Value="..." />
<WixVariable Id="WixMbaPrereqPackageId" Value="..." />
in which I literally leave in ... because I don't want it to work anyway.
In this thread, #Shruthi asks
Is there a way to replace the prerequisite .Net install UI with just an notification to the user that they need to install a particular version of .Net before they can install the bundle?
And Rob Mensching replies:
Yes, that is possible now.
... BUT HE DOESN'T ELABORATE. How is it possible now?
Thanks!
===============
In my Bundle's Bootstrap.Config:
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v6" /> <!-- Pretending it's the future! -->
</startup>
The Bundle itself does not reference any .NET stuff and does not reference WixNetFxExtension and uses the custom <BootstrapperApplicationRef Id="ManagedBootstrapperApplicationHost" />
To answer how to handle checking for the .Net 4.0 Framework include the WixNetfxExtension in your project
then under product add a reference to the property NETFRAMEWORK40FULL and put a condition on it.
<PropertyRef Id="NETFRAMEWORK40FULL"/>
<Condition Message="This application requires .NET Framework 4.0. Please install the .NET Framework then run this installer again.">
<![CDATA[Installed OR NETFRAMEWORK40FULL]]>
</Condition>
for the chained case again include wixnetfxextension and include NetFx40Redist package group
<PackageGroupRef Id="NetFx40Redist"/>

How to intelligently install .NET 4.x using WiX Burn

When installing an application that can use either .NET 4.0 or 4.5, what is the best practice when installing the prerequisites .NET framework? And how do you implement it using Burn in WiX?
These are the options and trade-offs that I am aware of:
Option 1: Install .NET 4.0 (just what you need)
Advantages: None known (except for Windows XP, where this is the only choice)
Option 2: Install .NET 4.5 if .NET 4.5 is not present
Advantages: User won't have to install .NET 4.5 later for future
apps. App won't experience a .NET version change when user later does
upgrade to .NET 4.5. App immediately gets performance improvements of
.NET 4.5.
Option 3: Install .NET 4.5 only if neither .NET 4.x is present
Advantages: Much faster deployment than option 2 if .NET 4.0 is
already installed. If it's not, then the advantages of option 2
apply.
As far as I can tell, the best practice would be option 2 if the performance improvements are important and option 3 if average deployment speed is important. Does this sound right? Am I missing any advantage to option 1? Most importantly, if option 3 does make sense, how do you implement it using Burn when installing .NET from the web?
Below is how I detect .NET in my bundle. Note the use of DetectConditions and InstallConditions. The DetectCondition will check whether or not the specific package is installed, whereas the InstallCondition can be used to override the DetectCondition to specify when the package should be installed. For example, on XP you can't install .NET 4.5 so my InstallCondition prevents installation in such a case.
<util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full" Value="Version" Variable="Netfx4FullVersion" />
<util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full" Value="Version" Variable="Netfx4x64FullVersion" Win64="yes" />
<!-- .NET 4.5 only installed if Vista or higher AND it's not already installed-->
<PackageGroup Id="Netfx45">
<ExePackage Id="Netfx45" Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" InstallCommand="/q"
SourceFile="C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\DotNetFX45Full\dotnetfx45_full_x86_x64.exe"
DetectCondition="(Netfx4FullVersion="4.5.50709") AND (NOT VersionNT64 OR (Netfx4x64FullVersion="4.5.50709"))"
InstallCondition="(VersionNT >= v6.0 OR VersionNT64 >= v6.0) AND (NOT (Netfx4FullVersion="4.5.50709" OR Netfx4x64FullVersion="4.5.50709"))"/>
</PackageGroup>
<!-- .NET 4.0 only installed if XP AND it's not already installed -->
<PackageGroup Id="Netfx4Full">
<ExePackage Id="Netfx4Full" Cache="no" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes" InstallCommand="/q"
SourceFile="C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\DotNetFX40\dotNetFx40_Full_x86_x64.exe"
DetectCondition="Netfx4FullVersion AND (NOT VersionNT64 OR Netfx4x64FullVersion)"
InstallCondition="(VersionNT < v6.0 OR VersionNT64 < v6.0) AND (NOT (Netfx4FullVersion OR Netfx4x64FullVersion))"/>
</PackageGroup>
Then if you want to install one of the packages, just reference it in your chain:
<Chain>
<PackageGroupRef Id='Netfx45'/>
</Chain>
With regards to your specific question, I would install whatever framework version the application was tested against. If tested against both .NET 4.0 and .NET 4.5 I suppose it is a judgement call, however I would try to simplify the setup experience as much as possible. So if .NET 4.0 were already installed and the application does not require .NET 4.5, I would not install it.
Also, there is a disadvantage to Option 2 if you are using a custom Managed Bootstrapper Application. Let's say you have .NET 4.0 installed and your managed bootstrapper requires .NET 4.0 (or greater). When you run the installer it will install .NET 4.5 which replaces .NET 4.0, forcing your installer to reboot halfway through because it was using .NET framework at the same time it was being updated. Again, this is only an issue if you are using your own custom managed bootstrapper.