Wix per user installer to detect the Visual C++ 2015 Redistributable
Asked Answered
S

2

7

I am creating an .msi installer which has to determine whether the Visual C++ 2015 Redistributable is present in the system and if not then interrupt the installation with custom message. The official Wix documentation refers to the actual installation of the VC++ which I do not wish to do as my installer is "per user" , There are couple of others stackoverflow questions which refer to the bundle rather than the .msi http://wixtoolset.org/documentation/manual/v3/howtos/redistributables_and_install_checks/install_vcredist.html.
Wix Burn vcredist, WIX check if VS2015 C++ redistributable is installed , https://gist.github.com/nathancorvussolis/6852ba282647aeb0c5c00e742e28eb48

So I guess the question is, how to efficiently detect the presense of Visual C++ 2015 Redistributable in the per user installer.

Solenoid answered 5/12, 2018 at 18:28 Comment(6)
I wonder if you can eliminate the dependency on the runtime altogether by static linking? Not sure if that works anymore. Static linking is not great (loss of shared fixes, especially security fixes), but sometimes it gets the job done. If you deliver regular updates for your application, it should be OK? To test, maybe run on a pristine virtual. And you can have a peek with dependency scanners too.Example
unfortunately this is not an option as the package which requires VC++ is from a third partySolenoid
The more I look at this, the more I wonder if the least worst approach is to check for the presence and version of the file vcruntime140.dll in the System32 folder (64-bit version) and SysWOW64 folder (32-bit version)? There are several other ways, I might get to take a further look tomorrow. See files list towards bottom here.Example
For testing purposes here is a quick VBScript: Set fso = CreateObject("Scripting.FileSystemObject") : MsgBox fso.GetFileVersion("C:\Windows\System32\vcruntime140.dll")Example
I added an answer I wrote. A bit over the top, but I have tried to summarize various approaches. Including also bad options - just to remember why they are bad. I would go for File Presence & Version Check.Example
The latest C++ Runtimes (since version 2015) are the same. Visual C++ 2015, 2017 and 2019 all share the same redistributable filesRothstein
C
9

The latest supported Visual C++ downloads


Runtime Detection Approaches

I can find a few ways to detect the presence of the Visual C++ Runtime.

  1. Registry

  2. File Presence & Version Check

    • Check for presence of core runtime files
    • See separate section below
  3. MSI API

    • You can detect whether a specific MSI is installed by looking up the product GUID
    • Reliable, but hard to keep track of all product GUIDs (different versions)
    • UPDATE: You can also use the upgrade code as described below. It should remain stable across releases and updates (for each major version and potentially between major versions as well).
  4. Fall-Over EXE?

    • Suggestions are seen to use an EXE depending on the runtime
    • Launching it and failing means the runtime is not there or broken

Good & Bad - Evaluation: Option 1 seems to be vulnerable since the merge module variant of deploying the runtime might not write these keys. Option 3 might work well, but it is hard to keep track of all GUIDs. Option 4 seems to already have failed based on the newer runtimes removing certain registry keys. Though fixed now, this could resurface.


File Version Presence / Version Check

The more I look at this, the more I start to think that you have to check for the actual files themselves, and potentially for the right file version. The file vcruntime140.dll in the System32 folder (64-bit version) and SysWOW64 folder (32-bit version)? See files list towards bottom here.

Just adding a link for safe-keeping.

A test VBScript - for test purposes only (scripts are sometimes blocked by anti-virus):

Set fso = CreateObject("Scripting.FileSystemObject")
MsgBox fso.GetFileVersion("C:\Windows\System32\vcruntime140.dll")

You can detect file presence and version using AppSearch in an MSI file.


Below are some other stuff I wrote up, just leaving it in.


VCRedist

It seems the Visual C++ Redistributable Packages (VCRedist_x86.exe, VCRedist_x64.exe) - which is the recommende way to deploy the runtime - checks the following registry key to determine what versions of the runtime is actually installed:

HKLM\SOFTWARE\Microsoft\VisualStudio\<version>\VC\Runtimes\

The sub-keys x86 and x64 seem to all contain an "Installed" value that is set to 1 when the runtime is installed. I would assume - without having had time to test it all - that you then can check:

  • HKLM\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64 Installed = 1
  • HKLM\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x86 Installed = 1

Merge Module: After a brief check, it looks like these values are not written by the merge modules that can also be used to distribute this runtime. I do not have the time or means to check this properly now.

Astonishingly both version 2015 and version 2017 of the runtime write to the 14.0 key - since they are binary compatible. If the 2017 version is installed, then the VCRedist executable will return an error since no install is needed. Weird indeed. But for your purpose that should be besides the point. Source.


MSI API - Retrieve Product Codes


UPDATE: installer.ProductState - normal installed state is 5:

I forgot about the ProductState property when writing the below. You can check for an installed product with two lines of code if you have the actual product code:

Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
MsgBox installer.ProductState("{00000000-0000-0000-0000-000000000001}")

Here is even one more way to do it: MSDN: How to programmatically check for the presence of a Windows Installer-based product by using its product code.

Tip: I wouldn't use this approach seeing as the product code changes frequently when products are updated. Hence I like better to check for file versions of core-runtime files. This seems more reliable for the future (provided version parsing is done correctly and reliably - don't roll your own).


Mockup:

Public installer
Set installer = CreateObject("WindowsInstaller.Installer")

' Don't have the 2015 GUID
VC2015 = CheckForProductCode("{00000000-0000-0000-0000-000000000000}")
VC2017 = CheckForProductCode("{C77195A4-CEB8-38EE-BDD6-C46CB459EF6E}")
   
MsgBox "VC2015: " & CStr(VC2015) & vbCrLf & "VC2017: " &  CStr(VC2017)

Function CheckForProductCode(productcode)

   CheckForProductCode = False

    For Each product In installer.ProductsEx("", "", 7)   
        If(LCase(productcode) = LCase(product.ProductCode)) Then
            CheckForProductCode = True
            Exit For
        End If
    Next

End Function

Update based on Zett42's suggestion to enumerate products sharing the same upgrade code:

Set installer = CreateObject("WindowsInstaller.Installer")

' Enumerate all products related to "Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.4148"

' {AA783A14-A7A3-3D33-95F0-9A351D530011} is the upgrade code
Set upgrades = installer.RelatedProducts("{AA783A14-A7A3-3D33-95F0-9A351D530011}")
For Each u In upgrades
   MsgBox u, vbOKOnly, "Product Code: "
Next

Deploying The Visual Studio C++ Runtime

Beyond detection, there are several approaches for distributing the Visual Studio C++ Runtime:

  1. Static Linking
  2. Visual C++ Redistributable Packages
    • VCRedist_x86.exe, VCRedist_x64.exe, or VCRedist_arm.exe
    • Program Files(x86)\Microsoft Visual Studio\2017\edition\VC\Redist\MSVC\lib-version
  3. Redistributable Merge Modules (.msm files)
  4. Local Application Folder
    • Copy DLLs to the local application folder
    • Not recommended for servicing reasons (updates, security fixes)

Links For Safe Keeping:


Old Answer

There is this old post. I am not too fond of direct registry reads, let me see if I can find a more reliable way, but maybe have a look in the mean time: Detect if Visual C++ Redistributable for Visual Studio 2012 is installed

Just one more link, how to find the Windows Installer product code of products that are installed: How can I find the product GUID of an installed MSI setup?

Calcaneus answered 5/12, 2018 at 19:5 Comment(8)
Checking for ProductCode is not a good way to detect VC++ runtimes, because the ProductCode changes with every little update (not just between major versions!). You can check for all ProductCodes up to date, but you can't know future ones. A better way is to start from the UpgradeCode (which is stable between updates) then call MsiEnumRelatedProducts to find all products with this UpgradeCode.Osman
Make sure to call MsiQueryProductState() on the products returned by MsiEnumRelatedProducts, because the latter sometimes reports already uninstalled products. Only if MsiQueryProductState() returns INSTALLSTATE_DEFAULT, you can be sure that the given VC-Redist is installed. You should compare the installed version of VC-Redist with the one your installer includes, because your product may depend on a newer version. You can call MsiGetProductInfo(productCode, INSTALLPROPERTY_VERSIONSTRING, ...) to get the installed version.Osman
Yes, I agree. I was wondering whether there are localized versions of the runtime as well - they could also have their own product GUIDs. It is a mess this runtime? I hope it becomes an OS part over time. My choice would be file checking to be really sure.Example
There are localized packages, but they contain the same MSI packages. Only the installer UI differs.Osman
I like the UpgradeCode / RelatedProducts idea. I'll just link to an existing sample of installer.RelatedProducts(UpgradeCode). I'll inline the code sample too.Example
Here are the UpgradeCodes for VC-Redist 2015/2017 (they are the same because of the binary compatibility). 32-bit: {65E5BD06-6392-3027-8C26-853107D3CF1A}, 64-bit: {36F68A90-239C-34DF-B58C-64B30153CE35}. Actually, the VC-Redist.exe installers consist of two MSI packages (a "base" and an "additional" one) per architecture (32/64-bit). These are the UpgradeCodes of the base packages. IMO it is sufficient to just check for the base packages, because base and additional packages are always installed together.Osman
If the redistributable is installed would it always be in System32 or SysWOW64 regardless of the version? I'm wondering if I could just check those directories for a file matching vcruntime*.dll. That would allow the check to be forward compatible if the "140" changes with new versions.Cristionna
I am not up to date on all the details here I am afraid. If you ask me there should be an API call to determine the current runtime version - perhaps there is? The runtime should also be a Windows component that you can add via Add / Remove Programs? Like IIS and Media Player? That rant doesn't help much though. Perhaps one could write a detection script for runtime versions that can be open sourced?Example
R
1

You can use LaunchConditions from WiX Toolset. The detection can be done by RegistrySearch.

Until version 2015 it was just a registry key, GUID value. Since 2017 and still in 2019 the key is concat (merged), so it is not that easy anymore. This is way I used a loop from 21 to 40 to find all places. C++ Runtime Documentation.

Add the following lines to your product.wxs inside the Product elemnent:

    ...

    <!-- Visual C++ Redistributable 2015, 2017 and 2019 (x86) -->
    <Property Id="CPPRUNTIME2015X86" Secure="yes">
      <!-- C++ 2015 -->
      <RegistrySearch Id="mfc140x86_23026" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{74d0e5db-b326-4dae-a6b2-445b9de1836e}" Type="raw" />
      <RegistrySearch Id="mfc140x86_24215" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{e2803110-78b3-4664-a479-3611a381656a}" Type="raw" />

      <!-- C++ 2017 -->
      <RegistrySearch Id="mfc1416x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.16,bundle" Type="raw" />

      <!-- C++ 2019 -->
      <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40?>
        <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
      <?endforeach ?>
    </Property>
    <Condition Message="Microsoft Visual C++ 2015-2019 (x86) Redistributable missing">
      <![CDATA[((REMOVE="ALL")) OR Installed]]>
    </Condition>

    <!-- Visual C++ Redistributable 2015, 2017 and 2019 (x64) -->
    <?if $(var.Platform) = x64 ?>
      <Property Id="CPPRUNTIME2015X64" Secure="yes">
        <!-- C++ 2015 -->
        <RegistrySearch Id="mfc140x64_23026" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{e46eca4f-393b-40df-9f49-076faf788d83}" Type="raw" />
        <RegistrySearch Id="mfc140x64_24215" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{d992c12e-cab2-426f-bde3-fb8c53950b0d}" Type="raw" />      

        <!-- C++ 2017 -->
        <RegistrySearch Id="mfc1416x64" Root="HKCR" Key="Installer\Dependencies\VC,redist.x64,amd64,14.16,bundle" Type="raw" />

        <!-- C++ 2019 -->
        <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40?>
          <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x64" Root="HKCR" Key="Installer\Dependencies\VC,redist.x64,amd64,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
        <?endforeach ?>
      </Property>
      <Condition Message="Microsoft Visual C++ 2015-2019 (x64) Redistributable missing">
        <![CDATA[((REMOVE="ALL")) OR Installed]]>
      </Condition>
    <?endif ?>

    ...
Rothstein answered 14/4, 2020 at 14:56 Comment(1)
I found also an very good answer on Stackoverflow, the title covers only 2012, but inside the answer there are many C++ Runtime versionsRothstein

© 2022 - 2024 — McMap. All rights reserved.