diff --git a/tools/msvs/msi/custom_actions.cc b/tools/msvs/msi/custom_actions.cc index 9a23d557476004..8a8417ea0b2929 100644 --- a/tools/msvs/msi/custom_actions.cc +++ b/tools/msvs/msi/custom_actions.cc @@ -1,10 +1,80 @@ - #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <msiquery.h> #include <wcautil.h> +#define GUID_BUFFER_SIZE 39 // {8-4-4-4-12}\0 + + +extern "C" UINT WINAPI SetInstallScope(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + PMSIHANDLE hDB; + PMSIHANDLE hView; + PMSIHANDLE hRecord; + + hr = WcaInitialize(hInstall, "SetInstallScope"); + ExitOnFailure(hr, "Failed to initialize"); + + hDB = MsiGetActiveDatabase(hInstall); + ExitOnNull(hDB, hr, S_FALSE, "Failed to get active database"); + + LPCTSTR query = TEXT("SELECT DISTINCT UpgradeCode FROM Upgrade"); + er = MsiDatabaseOpenView(hDB, query, &hView); + ExitOnWin32Error(er, hr, "Failed MsiDatabaseOpenView"); + + er = MsiViewExecute(hView, 0); + ExitOnWin32Error(er, hr, "Failed MsiViewExecute"); + + for (;;) { + er = MsiViewFetch(hView, &hRecord); + if (er == ERROR_NO_MORE_ITEMS) break; + ExitOnWin32Error(er, hr, "Failed MsiViewFetch"); + + TCHAR upgrade_code[GUID_BUFFER_SIZE]; + DWORD upgrade_code_len = GUID_BUFFER_SIZE; + er = MsiRecordGetString(hRecord, 1, upgrade_code, &upgrade_code_len); + ExitOnWin32Error(er, hr, "Failed to read UpgradeCode"); + + DWORD iProductIndex; + for (iProductIndex = 0;; iProductIndex++) { + TCHAR product_code[GUID_BUFFER_SIZE]; + er = MsiEnumRelatedProducts(upgrade_code, 0, iProductIndex, + product_code); + if (er == ERROR_NO_MORE_ITEMS) break; + ExitOnWin32Error(er, hr, "Failed to get related product code"); + + TCHAR assignment_type[2]; + DWORD assignment_type_len = 2; + er = MsiGetProductInfo(product_code, INSTALLPROPERTY_ASSIGNMENTTYPE, + assignment_type, &assignment_type_len); + ExitOnWin32Error(er, hr, "Failed to get the assignment type property " + "from related product"); + + // '0' = per-user; '1' = per-machine + if (assignment_type[0] == '0') { + /* When old versions which were installed as per-user are detected, + * the installation scope has to be set to per-user to be able to do + * an upgrade. If not, two versions will be installed side-by-side: + * one as per-user and the other as per-machine. + * + * If we wanted to disable backward compatibility, the installer + * should abort here, and request the previous version to be manually + * uninstalled before installing this one. + */ + er = MsiSetProperty(hInstall, TEXT("ALLUSERS"), TEXT("")); + ExitOnWin32Error(er, hr, "Failed to set the install scope to per-user"); + goto LExit; + } + } + } + +LExit: + // Always succeed. This should not block the installation. + return WcaFinalize(ERROR_SUCCESS); +} + extern "C" UINT WINAPI BroadcastEnvironmentUpdate(MSIHANDLE hInstall) { HRESULT hr = S_OK; diff --git a/tools/msvs/msi/custom_actions.def b/tools/msvs/msi/custom_actions.def index 29e0933e379a78..5f6b25fc423492 100644 --- a/tools/msvs/msi/custom_actions.def +++ b/tools/msvs/msi/custom_actions.def @@ -1,4 +1,5 @@ LIBRARY "custom_actions" EXPORTS -BroadcastEnvironmentUpdate \ No newline at end of file +SetInstallScope +BroadcastEnvironmentUpdate diff --git a/tools/msvs/msi/product.wxs b/tools/msvs/msi/product.wxs index 21246fed38e2d3..172399ca5c8703 100755 --- a/tools/msvs/msi/product.wxs +++ b/tools/msvs/msi/product.wxs @@ -18,7 +18,10 @@ Manufacturer="$(var.ProductAuthor)" UpgradeCode="47c07a3a-42ef-4213-a85d-8f5a59077c28"> - <Package Languages="!(loc.LocaleId)" InstallerVersion="200" Compressed="yes"/> + <Package Languages="!(loc.LocaleId)" + InstallerVersion="200" + Compressed="yes" + InstallScope="perMachine"/> <Media Id="1" Cabinet="media1.cab" EmbedCab="yes"/> @@ -41,6 +44,14 @@ <Property Id="INSTALLDIR"> <RegistrySearch Id="InstallPathRegistry" + Type="raw" + Root="HKLM" + Key="$(var.RegistryKeyPath)" + Name="InstallPath"/> + <!-- Also need to search under HKCU to support upgrading from old + versions. If we wanted to disable backward compatibility, this + second search could be deleted. --> + <RegistrySearch Id="InstallPathRegistryCU" Type="raw" Root="HKCU" Key="$(var.RegistryKeyPath)" @@ -53,8 +64,9 @@ Description="!(loc.NodeRuntime_Description)" Absent="disallow"> <ComponentRef Id="NodeExecutable"/> + <ComponentRef Id="NodeRegistryEntries"/> <ComponentRef Id="NodeVarsScript"/> - <ComponentRef Id="NodeStartMenuAndRegistryEntries"/> + <ComponentRef Id="NodeStartMenu"/> <ComponentRef Id="AppData" /> <ComponentGroupRef Id="Product.Generated"/> @@ -126,6 +138,20 @@ <File Id="node.exe" KeyPath="yes" Source="$(var.SourceDir)\node.exe"/> </Component> + <Component Id="NodeRegistryEntries"> + <RegistryValue Root="HKLM" + Key="$(var.RegistryKeyPath)" + Name="InstallPath" + Type="string" + Value="[INSTALLDIR]" + KeyPath="yes"/> + <RegistryValue Root="HKLM" + Key="$(var.RegistryKeyPath)" + Name="Version" + Type="string" + Value="$(var.ProductVersion)"/> + </Component> + <Component Id="NodeVarsScript"> <File Id="nodevars.bat" KeyPath="yes" Source="$(var.RepoDir)\tools\msvs\nodevars.bat"/> </Component> @@ -148,18 +174,15 @@ </DirectoryRef> <DirectoryRef Id="ApplicationProgramsFolder"> - <Component Id="NodeStartMenuAndRegistryEntries"> + <Component Id="NodeStartMenu"> + <!-- RegistryValue needed because every Component must have a KeyPath. + Because of ICE43, the Root must be HKCU. --> <RegistryValue Root="HKCU" - Key="$(var.RegistryKeyPath)" - Name="InstallPath" - Type="string" - Value="[INSTALLDIR]" + Key="$(var.RegistryKeyPath)\Components" + Name="NodeStartMenuShortcuts" + Type="integer" + Value="1" KeyPath="yes"/> - <RegistryValue Root="HKCU" - Key="$(var.RegistryKeyPath)" - Name="Version" - Type="string" - Value="$(var.ProductVersion)"/> <Shortcut Id="NodeVarsScriptShortcut" Name="Node.js command prompt" Target="[%ComSpec]" @@ -258,16 +281,27 @@ </Component> </DirectoryRef> - <Binary Id='BroadcastEnvironmentUpdate' + <Binary Id='CustomActionsDLL' SourceFile='$(var.custom_actions.TargetDir)$(var.custom_actions.TargetName).dll' /> + <CustomAction Id="SetInstallScope" + BinaryKey="CustomActionsDLL" + DllEntry="SetInstallScope" + Execute="immediate" + Return="check" /> + <CustomAction Id="BroadcastEnvironmentUpdate" - BinaryKey="BroadcastEnvironmentUpdate" + BinaryKey="CustomActionsDLL" DllEntry="BroadcastEnvironmentUpdate" Execute="immediate" Return="check" /> + <InstallUISequence> + <Custom Action='SetInstallScope' Before='FindRelatedProducts'/> + </InstallUISequence> + <InstallExecuteSequence> + <Custom Action='SetInstallScope' Before='FindRelatedProducts'/> <Custom Action='BroadcastEnvironmentUpdate' After='InstallFinalize'/> </InstallExecuteSequence>