diff --git a/.gitignore b/.gitignore
index 301974b66a..c2300810f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,7 @@ xcuserdata
/packages/
/win32/distrib-*
+/win32/uninst-helper.dll
/*.snap
/poedit_source.tar.bz2
diff --git a/Poedit.vcxproj b/Poedit.vcxproj
index c15fedbc08..f48fe55b71 100644
--- a/Poedit.vcxproj
+++ b/Poedit.vcxproj
@@ -1,5 +1,6 @@
+
@@ -357,5 +358,6 @@
+
\ No newline at end of file
diff --git a/packages.config b/packages.config
index f51d96f1d4..684d7059c7 100644
--- a/packages.config
+++ b/packages.config
@@ -3,5 +3,6 @@
+
\ No newline at end of file
diff --git a/win32/distrib.proj b/win32/distrib.proj
new file mode 100644
index 0000000000..4d6581949e
--- /dev/null
+++ b/win32/distrib.proj
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+ $(MSBuildProjectDirectory)\..\packages\MSBuildTasks.1.5.0.235\tools
+
+
+
+
+ ../x64/$(Configuration)
+ "$(VCToolsInstallDir)\bin\HostX64\x86"
+ "$(VCToolsInstallDir)\lib\x86"
+ ..\packages\Tools.InnoSetup.6.2.2\tools\ISCC.exe
+ /dCONFIG=$(Configuration)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ call "$(VCINSTALLDIR)\Auxiliary\Build\vcvarsall.bat" x64_x86 || exit /b 666
+ cl /LD /O1 /MT /W3 uninst-helper.c || exit /b 666
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/win32/poedit.iss b/win32/poedit.iss
index 104bfce643..f9c326df4e 100644
--- a/win32/poedit.iss
+++ b/win32/poedit.iss
@@ -35,6 +35,8 @@
#define VERSION "3.4.2"
#define VERSION_WIN VERSION + "." + Str(POEDIT_GIT_BUILD_NUMBER)
+#define APP_ID "{68EB2C37-083A-4303-B5D8-41FA67E50B8F}"
+
#ifndef CRT_REDIST
#define CRT_REDIST GetEnv("VCToolsRedistDir") + "\x64\Microsoft.VC143.CRT"
#endif
@@ -70,7 +72,7 @@ ShowLanguageDialog=no
DisableWelcomePage=true
AllowUNCPath=true
InternalCompressLevel=ultra
-AppID={{68EB2C37-083A-4303-B5D8-41FA67E50B8F}
+AppID={{#APP_ID}
VersionInfoVersion={#VERSION_WIN}
VersionInfoTextVersion={#VERSION}
AppCopyright=Copyright © 1999-2023 Vaclav Slavik
@@ -99,6 +101,7 @@ VersionInfoProductTextVersion={#VERSION}
DisableDirPage=auto
[Files]
+Source: win32\uninst-helper.dll; Flags: dontcopy
Source: {#BINDIR}\Poedit.exe; DestDir: {app}; Flags: ignoreversion
Source: {#BINDIR}\*.dll; DestDir: {app}; Flags: ignoreversion
Source: {#BINDIR}\icudt*.dat; DestDir: {app}
@@ -209,6 +212,7 @@ Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl"
[CustomMessages]
OpenAfterInstall=Open Poedit after installation
+Uninstall32bit=Uninstalling 32-bit version of Poedit...
brazilianportuguese.OpenAfterInstall=Abrir o Poedit após a instalação
catalan.OpenAfterInstall=Obre el Poedit després de la instal·lació
corsican.OpenAfterInstall=Apre Poedit dopu à l'installazione
@@ -229,3 +233,48 @@ slovenian.OpenAfterInstall=Odpri Poedit po namestitvi
spanish.OpenAfterInstall=Abrir Poedit tras la instalación
turkish.OpenAfterInstall=Kurulumdan sonra Poedit'i aç
ukrainian.OpenAfterInstall=Відкрити Poedit після встановлення
+
+[Code]
+
+const UninstallKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{#APP_ID}_is1';
+
+function SafelyUninstall32BitVersion(uninstallerExe: String): Integer;
+external 'SafelyUninstall32BitVersion@files:uninst-helper.dll cdecl setuponly';
+
+function Find32bitUninstallerPath(): String;
+var
+ uninst: String;
+begin
+ if (not RegKeyExists(HKCU64, UninstallKey)) and (not RegKeyExists(HKLM64, UninstallKey)) and RegKeyExists(HKLM32, UninstallKey) then
+ begin
+ RegQueryStringValue(HKLM32, UninstallKey, 'UninstallString', uninst);
+ Result := RemoveQuotes(uninst);
+ end
+ else
+ Result := '';
+end;
+
+
+procedure Uninstall32bitVersionIfPresent;
+var
+ uninstaller: String;
+begin
+ uninstaller := Find32bitUninstallerPath();
+ if (uninstaller <> '') then
+ begin
+ Log('Migrating from 32bit install, uninstalling via ' + uninstaller);
+ WizardForm.StatusLabel.Caption := CustomMessage('Uninstall32bit');
+ SafelyUninstall32BitVersion(uninstaller);
+ end;
+end;
+
+
+procedure CurStepChanged(CurStep: TSetupStep);
+begin
+ if (CurStep = ssInstall) then
+ begin
+ Uninstall32bitVersionIfPresent();
+ end;
+end;
+
+
diff --git a/win32/uninst-helper.c b/win32/uninst-helper.c
new file mode 100644
index 0000000000..668c9b1207
--- /dev/null
+++ b/win32/uninst-helper.c
@@ -0,0 +1,93 @@
+/*
+ * This file is part of Poedit (https://poedit.net)
+ *
+ * Copyright (C) 2024 Vaclav Slavik
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef _M_IX86
+#error This file must be compiled for 32bit x86 target
+#endif
+
+// This DLL is used by the uninstaller to remove Poedit's 32bit version during upgrades.
+//
+// Inno Setup treats 64bit and 32bit versions as separate applications, so it can't
+// upgrade existing install seamlessly; we need to remove the old version first.
+//
+// Unfortunately, the installer for older versions of Poedit uninstalled HKCU registry
+// keys, i.e. deleted user settings; that wouldn't be good during a simple upgrade.
+// Hence this DLL: it is loaded by the uninstaller and it removes the old version,
+// while taking care to preserve user settings.
+
+#define UNICODE
+#include
+#pragma comment(lib, "advapi32.lib")
+
+
+__declspec(dllexport) int SafelyUninstall32BitVersion(LPCWSTR uninstallerExe)
+{
+ // check if the uninstaller file exists:
+ if (GetFileAttributesW(uninstallerExe) == INVALID_FILE_ATTRIBUTES)
+ return 0;
+
+ // rename the HKCU\Software\Vaclav Slavik\Poedit key to *.backup:
+ HKEY key = 0;
+ if (SUCCEEDED(RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Vaclav Slavik\\Poedit", 0, KEY_ALL_ACCESS, &key)))
+ {
+ if (FAILED(RegRenameKey(key, NULL, L"Poedit.backup")))
+ {
+ RegCloseKey(key);
+ key = 0;
+ }
+ }
+
+ // run the uninstaller:
+ wchar_t cmdline[10240];
+ wcscpy_s(cmdline, 10240, uninstallerExe);
+ wcscat_s(cmdline, 10240, L" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART");
+
+ STARTUPINFOW si = { sizeof(si) };
+ PROCESS_INFORMATION pi;
+ if (CreateProcessW(uninstallerExe, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
+ {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+
+ // Inno Setup's uninstaller spawns a subprocess in order to be able to delete itself, so wait until
+ // the uninstaller is removed (or 60 seconds pass, whichever comes first):
+ for (int i = 0; i < 600; i++)
+ {
+ if (GetFileAttributesW(uninstallerExe) == INVALID_FILE_ATTRIBUTES)
+ break;
+ Sleep(100);
+ }
+
+ // rename the backup key back:
+ if (key)
+ {
+ RegRenameKey(key, NULL, L"Poedit");
+ RegCloseKey(key);
+ }
+
+ return 1;
+}