3
votes

The WiX manual includes "How To: Install the .NET Framework Using Burn". However, these instructions don't seem to work for installing .NET Framework 3.5 on Windows 8, Windows Server 2012, and later operating systems. See this StackOverflow question and especially this wix-users mailing list discussion for details. The Microsoft .NET Framework 3.5 Service pack 1 (Full Package) installer won't run, because you need to use Deployment Image Servicing and Management (DISM.exe) or some other technique to enable the requested framework as a Windows feature. A suggested command line is as follows, assuming Windows is installed in the default location:

C:\Windows\system32\dism.exe /online /norestart /enable-feature /featurename:netfx3

Is there a clean way to ensure that the .NET Framework 3.5 will be installed on Windows 8 and Windows Server 2012 with WiX? Is there a good way to include such a step in the install chain?

2
Apparently, the 32-bit dism.exe is designed to fail immediately on 64-bit Windows (rather than forward on the command to the 64-bit version like, for example, procexp.exe does). So, to use dism, you have to find the 64-bit version on 64-bit Windows. %WINDIR%\sysnative\ works as the location on 64-bit Windows but not on 32-bit Windows.Tom Blodget
@TomBlodget, thanks for the warning about using this approach on 32-bit systems. I can confirm that this is an issue.Dan Jagnow

2 Answers

1
votes

Here is the best I have been able to come up with. I've added a fragment that that can install .NET Framework 3.5 for operating systems prior to Windows 8 and Windows Server 2012. Note that this requires a reference to NetFxExtension for the NETFRAMEWORK35_SP_LEVEL definition.

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
  <Fragment>
    <util:RegistrySearchRef Id="NETFRAMEWORK35_SP_LEVEL"/>    
    <PackageGroup Id="NetFx35Redist">
      <ExePackage
          SourceFile="{a path on my network}\Microsoft\DotNetFx\3.5\dotnetfx35.exe"
          DisplayName="Microsoft .NET Framework 3.5 Full"
          InstallCondition="VersionNT &lt; v6.1"
          InstallCommand="/q /norestart"
          RepairCommand="/q /norestart /f"
          UninstallCommand="/q /norestart /uninstall"
          PerMachine="yes"
          DetectCondition="NETFRAMEWORK35_SP_LEVEL &gt;= 1"
          Id="dotnetfx35.exe"
          Vital="yes"
          Permanent="yes"
          Protocol="none"
          Compressed="yes"
          Name="redist\dotnetfx35.exe">
        <!-- Exit codes
             0 = Successful installation.
          3010 = Successful installation; however, a system reboot is required.
        -->
        <ExitCode Value="0" Behavior="success" />
        <ExitCode Value="3010" Behavior="forceReboot" />
        <ExitCode Behavior="error"/>
      </ExePackage>
    </PackageGroup>
  </Fragment>
</Wix>

In my managed bootstrapper code, I'm handling the Windows 8/Windows Server 2012 at the beginning of the Apply phase:

model.Bootstrapper.ApplyBegin += this.ApplyBegin;

...

private void ApplyBegin(object sender, ApplyBeginEventArgs e)
{
    this.EnsureNetFramework35();
}

The method that calls dism.exe to enable .NET Framework 3.5 follows. Some of the code references classes like ProgressViewModel that won't be in every managed bootstrapper implementation, but I hope this provides a useful starting point for implementing your own version.

/// <summary>
/// Make sure we have the .NET Framework 3.5 when we're on Windows 8, Windows Server 2012, or later.
/// </summary>
private void EnsureNetFramework35()
{
    // Don't worry if we're on an older OS.  We don't need DISM.exe in that case.
    if (Environment.OSVersion.Version < new Version(6, 1) && this.root.Model.Engine.NumericVariables.Contains("NETFRAMEWORK35_SP_LEVEL"))
    {
        return;
    }

    // Don't worry if .NET Framework 3.5 is already installed.
    if (this.root.Model.Engine.NumericVariables.Contains("NETFRAMEWORK35_SP_LEVEL") &&
        this.root.Model.Engine.NumericVariables["NETFRAMEWORK35_SP_LEVEL"] >= 1)
    {
        return;
    }

    // Enable .NET Framework 3.5.
    this.root.Model.Engine.Log(LogLevel.Standard, "Enabling .NET Framework 3.5.");
    this.root.ProgressViewModel.Message = "Enabling .NET Framework 3.5.";

    // Get the path to DISM.exe.
    string windowsPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    string systemPath = Path.Combine(windowsPath, "System32");
    if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
    {
        // For 32-bit processes on 64-bit systems, %windir%\system32 folder
        // can only be accessed by specifying %windir%\sysnative folder.
        systemPath = Path.Combine(windowsPath, "SysNative");
    }

    string dismPath = Path.Combine(systemPath, @"dism.exe");
    string arguments = "/online /enable-feature:NetFx3 /quiet /norestart";

    if (!File.Exists(dismPath))
    {
        this.root.Model.Engine.Log(LogLevel.Error, "Could not find file: " + dismPath);
        return;
    }

    this.root.Model.Engine.Log(LogLevel.Standard, dismPath + " " + arguments);
    this.root.ProgressViewModel.DetailMessage = dismPath + " " + arguments;

    Process process = new Process();
    process.StartInfo.FileName = dismPath;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    process.Start();
    process.WaitForExit();

    // Check to see if we encountered any errors.
    if (process.ExitCode == 0)
    {
        this.root.Model.Engine.Log(LogLevel.Standard, ".NET Framework 3.5 enabled.");
        this.root.ProgressViewModel.Message = ".NET Framework 3.5 enabled.";
        this.root.ProgressViewModel.DetailMessage = string.Empty;
    }
    else
    {
        this.root.Model.Engine.Log(LogLevel.Error, ".NET Framework 3.5 could not be enabled.  Exit code: " + process.ExitCode);
        this.root.ProgressViewModel.Message = ".NET Framework 3.5 could not be enabled.";
        this.root.ProgressViewModel.DetailMessage = string.Empty;
    }
}