msi: detect and set previous installation path via a custom action (#2108)

This commit is contained in:
Andrey Nekrasov
2020-04-21 20:52:28 +03:00
committed by GitHub
parent 0354026292
commit b5dfe6320d
5 changed files with 106 additions and 20 deletions

View File

@@ -39,6 +39,8 @@
<ComponentGroupRef Id="CoreComponents" />
<ComponentGroupRef Id="ResourcesComponents" />
</Feature>
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<UI>
<UIRef Id="WixUI_PTInstallDir"/>
@@ -71,6 +73,9 @@
<RegistrySearch Id="ExistingImageResizerPath" Root="HKCU" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32" Type="raw"/>
</Property>
<InstallUISequence>
<Custom Action="DetectPrevInstallPath" After="CostFinalize" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="SetRegisterPowerToysSchTaskParam" Before="RegisterPowerToysSchTask" />
<Custom Action="RegisterPowerToysSchTask" After="InstallFiles">
@@ -163,6 +168,13 @@
DllEntry="TelemetryLogRepairFailCA"
/>
<CustomAction Id="DetectPrevInstallPath"
Return="check"
Impersonate="yes"
BinaryKey="PTCustomActions"
DllEntry="DetectPrevInstallPathCA"
/>
<!-- Close 'PowerToys.exe' before uninstall-->
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" />
<!-- Restart explorer.exe if we detect existing PowerRenameExt.dll or ImageResizerExt.dll installation -->
@@ -233,7 +245,7 @@
</Shortcut>
</File>
<RegistryKey Root="HKCR" Key="powertoys" Action="createAndRemoveOnUninstall">
<RegistryKey Root="HKCR" Key="powertoys">
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Value="URL:PowerToys custom internal URI protocol"/>
<RegistryKey Key="DefaultIcon">

View File

@@ -1,17 +1,5 @@
#include "stdafx.h"
#define SECURITY_WIN32
#include <Security.h>
#pragma comment(lib, "Secur32.lib")
#include <Lmcons.h>
#include <comdef.h>
#include <taskschd.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")
#include <iostream>
#include <strutil.h>
#include <ProjectTelemetry.h>
using namespace std;
@@ -26,6 +14,9 @@ TRACELOGGING_DEFINE_PROVIDER(
const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0'
const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0'
static const wchar_t* POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
static const wchar_t* POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
// Creates a Scheduled Task to run at logon for the current user.
// The path of the executable to run should be passed as the CustomActionData (Value).
// Based on the Task Scheduler Logon Trigger Example:
@@ -48,6 +39,8 @@ UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall)
ITriggerCollection* pTriggerCollection = NULL;
IRegisteredTask* pRegisteredTask = NULL;
LPWSTR wszExecutablePath = NULL;
hr = WcaInitialize(hInstall, "CreateScheduledTaskCA");
ExitOnFailure(hr, "Failed to initialize");
@@ -77,7 +70,6 @@ UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall)
wstrTaskName += username;
// Get the executable path passed to the custom action.
LPWSTR wszExecutablePath = NULL;
hr = WcaGetProperty(L"CustomActionData", &wszExecutablePath);
ExitOnFailure(hr, "Failed to get the executable path from CustomActionData.");
@@ -571,6 +563,73 @@ LExit:
return WcaFinalize(er);
}
std::optional<std::wstring> getMsiPackageInstalledPath(const wchar_t* product_upgrade_code, const wchar_t* file_component)
{
constexpr size_t guid_length = 39;
wchar_t product_ID[guid_length];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(product_upgrade_code, 0, 0, product_ID); !found)
{
return std::nullopt;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(product_ID); !installed)
{
return std::nullopt;
}
DWORD buf_size = MAX_PATH;
wchar_t buf[MAX_PATH];
if (ERROR_SUCCESS == MsiGetProductInfoW(product_ID, INSTALLPROPERTY_INSTALLLOCATION, buf, &buf_size) && buf_size)
{
return buf;
}
DWORD package_path_size = 0;
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size))
{
return std::nullopt;
}
std::wstring package_path(++package_path_size, L'\0');
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size))
{
return std::nullopt;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
wchar_t path[256];
DWORD path_size = 256;
MsiGetComponentPathW(product_ID, file_component, path, &path_size);
if (!path_size)
{
return std::nullopt;
}
PathCchRemoveFileSpec(path, path_size);
return path;
}
UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "DetectPrevInstallPathCA");
try
{
if (auto install_path = getMsiPackageInstalledPath(POWERTOYS_UPGRADE_CODE, POWERTOYS_EXE_COMPONENT))
{
MsiSetPropertyW(hInstall, L"INSTALLFOLDER", install_path->data());
}
}
catch(...)
{
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// DllMain - Initialize and cleanup WiX custom action utils.
extern "C" BOOL WINAPI DllMain(__in HINSTANCE hInst, __in ULONG ulReason, __in LPVOID)
{

View File

@@ -2,6 +2,7 @@ LIBRARY "PowerToysSetupCustomActions"
EXPORTS
CreateScheduledTaskCA
DetectPrevInstallPathCA
RemoveScheduledTasksCA
TelemetryLogInstallSuccessCA
TelemetryLogInstallCancelCA

View File

@@ -55,11 +55,13 @@
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -76,11 +78,13 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>

View File

@@ -1,13 +1,23 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
#include <strsafe.h>
#include <msiquery.h>
#include <Msi.h>
// WiX Header Files:
#include <wcautil.h>
#define SECURITY_WIN32
#include <Security.h>
#include <Lmcons.h>
// TODO: reference additional headers your program requires here
#include <comdef.h>
#include <taskschd.h>
#include <iostream>
#include <strutil.h>
#include <string>
#include <optional>
#include <pathcch.h>