From 75dd26682388debd6d9515254ae511481f3b7a53 Mon Sep 17 00:00:00 2001 From: Martin Prikryl Date: Thu, 10 Nov 2022 12:13:34 +0000 Subject: [PATCH] Bug 83: MSI installation package https://winscp.net/tracker/83 Source commit: 599e90f2406250f67542a77f1594218f68a7ac67 --- deployment/winscp.isl | 4 ++++ deployment/winscpsetup.iss | 7 ++++++ source/WinSCP.cbproj | 2 +- source/windows/Setup.cpp | 47 ++++++++++++++++++++++++++++++++++---- source/windows/Setup.h | 3 ++- source/windows/WinApi.h | 20 ++++++++++++++++ source/windows/WinMain.cpp | 1 + 7 files changed, 78 insertions(+), 6 deletions(-) diff --git a/deployment/winscp.isl b/deployment/winscp.isl index 5eaabc7bf..4b177282e 100644 --- a/deployment/winscp.isl +++ b/deployment/winscp.isl @@ -60,6 +60,10 @@ Currency=USD ImportSites=Looking for sites to import... AcceptButton=&Accept IncompleteTranslation=You are about to use an incomplete translation. It is completed by %1%% only.%n%nUntranslated parts of the user interface will be shown in their original English version.%n%nVisit WinSCP website at winscp.net to check, if newer version of the translation is available. +MsiInstallation=WinSCP is already installed on this system using an MSI installer. Please uninstall that before running this standalone executable installer. + +; MSI (sic) +InnoSetupInstallation=WinSCP is already installed on this system using a standalone executable installer. Please uninstall that before running this MSI installer. [CustomOptions] TranslationCompleteness=100 diff --git a/deployment/winscpsetup.iss b/deployment/winscpsetup.iss index 4f2f248f6..bf09e55f8 100644 --- a/deployment/winscpsetup.iss +++ b/deployment/winscpsetup.iss @@ -809,6 +809,13 @@ begin end; end; + if IsMsiProductInstalled('{029F9450-CFEF-4408-A2BB-B69ECE29EB18}', 0) and + (not CmdLineParamExists('/OverrideMsi')) then + begin + MsgBox(CustomMessage('MsiInstallation'), mbError, MB_OK); + Abort; + end; + Result := True; end; diff --git a/source/WinSCP.cbproj b/source/WinSCP.cbproj index acbd17b70..39a0785ad 100644 --- a/source/WinSCP.cbproj +++ b/source/WinSCP.cbproj @@ -49,7 +49,7 @@ <_TCHARMapping>wchar_t - vcl.lib;rtl.lib;vclx.lib;ws2_32.lib;secur32.lib;My.lib;DriveDir.lib;DragDropP.lib;tb2k.lib;tbxp.lib;bcbie.lib;Crypt32.lib;PngComponents.lib;xmlrtl.lib;vclactnband.lib;vclimg.lib;winhttp.lib;jcl.lib;vclie.lib;urlmon.lib;shlwapi.lib;powrprof.lib;soaprtl.lib;fmx.lib;dbrtl.lib;inet.lib;psapi.lib + vcl.lib;rtl.lib;vclx.lib;ws2_32.lib;secur32.lib;My.lib;DriveDir.lib;DragDropP.lib;tb2k.lib;tbxp.lib;bcbie.lib;Crypt32.lib;PngComponents.lib;xmlrtl.lib;vclactnband.lib;vclimg.lib;winhttp.lib;jcl.lib;vclie.lib;urlmon.lib;shlwapi.lib;powrprof.lib;soaprtl.lib;fmx.lib;dbrtl.lib;inet.lib;psapi.lib;msi.lib true true true diff --git a/source/windows/Setup.cpp b/source/windows/Setup.cpp index 576dd8dcd..fdc6b9005 100644 --- a/source/windows/Setup.cpp +++ b/source/windows/Setup.cpp @@ -34,6 +34,7 @@ #include #include #include +#include //--------------------------------------------------------------------------- #define KEY _T("SYSTEM\\CurrentControlSet\\Control\\") \ _T("Session Manager\\Environment") @@ -805,11 +806,12 @@ UnicodeString __fastcall ProgramUrl(UnicodeString URL) FORMAT(L"%d.%d.%d.%d", (HIWORD(FileInfo->dwFileVersionMS), LOWORD(FileInfo->dwFileVersionMS), HIWORD(FileInfo->dwFileVersionLS), LOWORD(FileInfo->dwFileVersionLS))); + int IsInstalledFlag = (IsInstalled() ? 1 : (IsInstalledMsi() ? 2 : 0)); UnicodeString Params = FORMAT(L"v=%s&lang=%s&isinstalled=%d", (CurrentVersionStr, GUIConfiguration->AppliedLocaleHex, - int(IsInstalled()))); + IsInstalledFlag)); if (Configuration->IsUnofficial) { @@ -1983,6 +1985,12 @@ bool __fastcall AnyOtherInstanceOfSelf() return Result; } //--------------------------------------------------------------------------- +static bool DoIsPathToExe(const UnicodeString & Path) +{ + UnicodeString ExePath = ExcludeTrailingBackslash(ExtractFilePath(Application->ExeName)); + return IsPathToSameFile(ExePath, Path); +} +//--------------------------------------------------------------------------- static bool __fastcall DoIsInstalled(HKEY RootKey) { std::unique_ptr Registry(new TRegistry(KEY_READ)); @@ -1992,21 +2000,52 @@ static bool __fastcall DoIsInstalled(HKEY RootKey) if (Result) { UnicodeString InstallPath = ExcludeTrailingBackslash(Registry->ReadString(L"Inno Setup: App Path")); - UnicodeString ExePath = ExcludeTrailingBackslash(ExtractFilePath(Application->ExeName)); Result = !InstallPath.IsEmpty() && - IsPathToSameFile(ExePath, InstallPath); + DoIsPathToExe(InstallPath); } return Result; } //--------------------------------------------------------------------------- -bool __fastcall IsInstalled() +bool IsInstalled() { return DoIsInstalled(HKEY_LOCAL_MACHINE) || DoIsInstalled(HKEY_CURRENT_USER); } //--------------------------------------------------------------------------- +static int GIsInstalledMsi = -1; +bool IsInstalledMsi() +{ + if (GIsInstalledMsi < 0) + { + GIsInstalledMsi = 0; + wchar_t ProductCode[MAX_GUID_CHARS + 1]; + if (MsiEnumRelatedProducts(L"{029F9450-CFEF-4408-A2BB-B69ECE29EB18}", 0, 0, ProductCode) == ERROR_SUCCESS) + { + UnicodeString InstallPath; + InstallPath.SetLength(MAX_PATH); + unsigned long Size = InstallPath.Length() + 1; + int ErrorCode = MsiGetProductInfo(ProductCode, INSTALLPROPERTY_INSTALLLOCATION, InstallPath.c_str(), &Size); + if (ErrorCode == ERROR_MORE_DATA) + { + InstallPath.SetLength(Size); + Size++; + ErrorCode = MsiGetProductInfo(ProductCode, INSTALLPROPERTY_INSTALLLOCATION, InstallPath.c_str(), &Size); + } + if (ErrorCode == ERROR_SUCCESS) + { + InstallPath.SetLength(Size); + if (DoIsPathToExe(InstallPath)) + { + GIsInstalledMsi = 1; + } + } + } + } + return (GIsInstalledMsi > 0); +} +//--------------------------------------------------------------------------- static TStringList * __fastcall TextToTipList(const UnicodeString & Text) { std::unique_ptr List(new TStringList()); diff --git a/source/windows/Setup.h b/source/windows/Setup.h index 351742db9..844aed115 100644 --- a/source/windows/Setup.h +++ b/source/windows/Setup.h @@ -25,7 +25,8 @@ void __fastcall StopUpdateThread(); UnicodeString __fastcall CampaignUrl(UnicodeString URL); void __fastcall UpdateJumpList(TStrings * SessionNames, TStrings * WorkspaceNames); bool __fastcall AnyOtherInstanceOfSelf(); -bool __fastcall IsInstalled(); +bool IsInstalled(); +bool IsInstalledMsi(); UnicodeString __fastcall ProgramUrl(UnicodeString URL); void __fastcall AutoShowNewTip(); bool __fastcall AnyTips(); diff --git a/source/windows/WinApi.h b/source/windows/WinApi.h index 408fbb366..e2bb9640a 100644 --- a/source/windows/WinApi.h +++ b/source/windows/WinApi.h @@ -192,4 +192,24 @@ public: #define SES_EX_HANDLEFRIENDLYURL (0x100) //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- +// msi.h (available, but does not compile) +extern "C" +{ +#define MAX_GUID_CHARS 38 +#define INSTALLPROPERTY_INSTALLLOCATION __TEXT("InstallLocation") +UINT WINAPI MsiEnumRelatedProductsW( + __in LPCWSTR lpUpgradeCode, // upgrade code of products to enumerate + __reserved DWORD dwReserved, // reserved, must be 0 + __in DWORD iProductIndex, // 0-based index into registered products + __out_ecount(MAX_GUID_CHARS+1) LPWSTR lpProductBuf); // buffer of char count: 39 (size of string GUID) +#define MsiEnumRelatedProducts MsiEnumRelatedProductsW +//--------------------------------------------------------------------------- +UINT WINAPI MsiGetProductInfoW( + __in LPCWSTR szProduct, // product code + __in LPCWSTR szAttribute, // attribute name, case-sensitive + __out_ecount_opt(*pcchValueBuf) LPWSTR lpValueBuf, // returned value, NULL if not desired + __inout_opt LPDWORD pcchValueBuf); // in/out buffer character count +#define MsiGetProductInfo MsiGetProductInfoW +} +//--------------------------------------------------------------------------- #endif // WinApiH diff --git a/source/windows/WinMain.cpp b/source/windows/WinMain.cpp index 366fe5697..1701f15c5 100644 --- a/source/windows/WinMain.cpp +++ b/source/windows/WinMain.cpp @@ -603,6 +603,7 @@ void __fastcall UpdateStaticUsage() bool InProgramFiles = AnsiSameText(ExeName.SubString(1, ProgramsFolder.Length()), ProgramsFolder); Configuration->Usage->Set(L"InProgramFiles", InProgramFiles); Configuration->Usage->Set(L"IsInstalled", IsInstalled()); + Configuration->Usage->Set(L"IsInstalledMsi", IsInstalledMsi()); Configuration->Usage->Set(L"Wine", IsWine()); Configuration->Usage->Set(L"NetFrameworkVersion", GetNetVersionStr()); Configuration->Usage->Set(L"NetCoreVersion", GetNetCoreVersionStr());