Support reinstallation of the same version

This commit is contained in:
Gerhard Tan
2025-07-16 14:30:06 +08:00
parent 3730c0660b
commit 4cdc9165be
7 changed files with 192 additions and 16 deletions

View File

@ -243,9 +243,10 @@ __declspec(dllexport) UINT __stdcall EvaluateFrpServices(MSIHANDLE installer)
record = MsiCreateRecord(5);
if (!record)
continue;
BOOL start = services[i].ServiceStatusProcess.dwWin32ExitCode == ERROR_FAIL_NOACTION_REBOOT;
MsiRecordSetStringW(record, 1, identifier);
MsiRecordSetStringW(record, 2, services[i].lpServiceName);
MsiRecordSetInteger(record, 3, msidbServiceControlEventStop | msidbServiceControlEventUninstallStop | (legacy == 0 ? msidbServiceControlEventDelete : 0) | msidbServiceControlEventUninstallDelete);
MsiRecordSetInteger(record, 3, (start ? msidbServiceControlEventStart : msidbServiceControlEventStop) | msidbServiceControlEventUninstallStop | (legacy == 0 ? msidbServiceControlEventDelete : 0) | msidbServiceControlEventUninstallDelete);
MsiRecordSetStringW(record, 4, L"frpmgr.exe");
MsiRecordSetInteger(record, 5, 1);
ret = MsiViewExecute(view, record);

View File

@ -43,8 +43,8 @@ if not defined TARGET_%ARCH% (
:build_actions
%WINDRES% -DVERSION_ARRAY=%VERSION:.=,% -DVERSION_STR=%VERSION% -o %PLAT_DIR%\actions.res.obj -i actions\version.rc -O coff -c 65001 || exit /b 1
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -municode -DUNICODE -D_UNICODE -DNDEBUG
set LDFLAGS=-shared -s -Wl,--kill-at -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -municode -DUNICODE -D_UNICODE -DNDEBUG
set LDFLAGS=-shared -s -Wl,--kill-at -Wl,--major-os-version=6 -Wl,--minor-os-version=2 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=2 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
set LDLIBS=-lmsi -lole32 -lshlwapi -lshell32 -ladvapi32
%CC% %CFLAGS% %LDFLAGS% -o %PLAT_DIR%\actions.dll actions\actions.c %PLAT_DIR%\actions.res.obj %LDLIBS% || exit /b 1
goto :eof
@ -83,9 +83,9 @@ if not defined TARGET_%ARCH% (
echo ERROR: UpgradeCode was not found.
exit /b 1
)
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -municode -DUNICODE -D_UNICODE -DNDEBUG -DUPGRADE_CODE=L\"{%UPGRADE_CODE%}\" -DVERSION=L\"%VERSION%\"
set LDFLAGS=-s -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -mwindows
set LDLIBS=-lmsi -lole32 -lshlwapi -ladvapi32 -luser32
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -municode -DUNICODE -D_UNICODE -DNDEBUG -DUPGRADE_CODE=L\"{%UPGRADE_CODE%}\" -DVERSION=L\"%VERSION%\"
set LDFLAGS=-s -Wl,--major-os-version=6 -Wl,--minor-os-version=2 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=2 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -mwindows
set LDLIBS=-lmsi -lole32 -lshlwapi -ladvapi32 -luser32 -lcomctl32
%CC% %CFLAGS% %LDFLAGS% -o %PLAT_DIR%\setup.exe setup\setup.c %PLAT_DIR%\setup.res.obj %LDLIBS% || exit /b 1
goto :eof

View File

@ -42,7 +42,7 @@
Detect previous install folder if it's a upgrade
-->
<SetProperty Id="INSTALLFOLDER" Value="[PREVINSTALLFOLDER]" After="AppSearch" Sequence="first">
WIX_UPGRADE_DETECTED
PREVINSTALLFOLDER
</SetProperty>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
@ -125,7 +125,7 @@
-->
<CustomAction Id="EvaluateFrpServices" BinaryKey="actions.dll" DllEntry="EvaluateFrpServices" />
<InstallExecuteSequence>
<Custom Action="EvaluateFrpServices" After="InstallInitialize">NOT (UPGRADINGPRODUCTCODE AND (REMOVE="ALL"))</Custom>
<Custom Action="EvaluateFrpServices" After="InstallInitialize">NOT ((UPGRADINGPRODUCTCODE OR SAVESTATE) AND (REMOVE="ALL"))</Custom>
</InstallExecuteSequence>
<!--
@ -143,7 +143,7 @@
<CustomAction Id="RemoveFrpFiles" BinaryKey="actions.dll" DllEntry="RemoveFrpFiles" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="RemoveFrpFiles.SetProperty" After="DeleteServices" />
<Custom Action="RemoveFrpFiles" After="RemoveFrpFiles.SetProperty">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
<Custom Action="RemoveFrpFiles" After="RemoveFrpFiles.SetProperty">(NOT UPGRADINGPRODUCTCODE) AND (NOT SAVESTATE) AND (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
<!--

View File

@ -7,4 +7,10 @@
#define IDC_LANG_COMBO 1000
#define IDC_STATIC -1
#define IDS_TITLE 200
#define IDS_MANAGEMENT 201
#define IDS_OPERATION 202
#define IDS_REINSTALL 203
#define IDS_UNINSTALL 204
#endif

View File

@ -133,3 +133,63 @@ LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
LANG_DIALOG_TEMPLATE(
TITLE_ES_ES, "Seleccione el idioma para la instalación entre las opciones siguientes.", "Aceptar", "Cancelar"
)
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
IDS_TITLE TITLE_EN_US
IDS_MANAGEMENT "Manage Current Product"
IDS_OPERATION "Select the action you want to perform."
IDS_REINSTALL "Reinstall with ""%1""%rThis operation requires suspending the running services.\0 "
IDS_UNINSTALL "Uninstall"
END
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
STRINGTABLE
BEGIN
IDS_TITLE TITLE_ZH_CN
IDS_MANAGEMENT "管理当前产品"
IDS_OPERATION "选择希望执行的操作。"
IDS_REINSTALL "以 “%1” 重新安装%r此操作需要暂停正在运行的服务。\0 "
IDS_UNINSTALL "卸载"
END
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
STRINGTABLE
BEGIN
IDS_TITLE TITLE_ZH_TW
IDS_MANAGEMENT "管理現行產品"
IDS_OPERATION "選取您要執行的作業。"
IDS_REINSTALL "以「%1」重新安裝%r此操作需要暫停正在運作的服務。\0 "
IDS_UNINSTALL "移除"
END
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
IDS_TITLE TITLE_JA_JP
IDS_MANAGEMENT "現在のインストールの管理"
IDS_OPERATION "実行するアクションを選択します。"
IDS_REINSTALL "「%1」で再インストールします%rこの操作では実行中のサービスを一時停止する必要があります。\0 "
IDS_UNINSTALL "アンインストール"
END
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
IDS_TITLE TITLE_KO_KR
IDS_MANAGEMENT "현재 제품 관리"
IDS_OPERATION "수행하고자 하는 작업을 선택하세요."
IDS_REINSTALL """%1""로 다시 설치%r이 작업을 수행하려면 실행 중인 서비스를 중단해야 합니다.\0 "
IDS_UNINSTALL "제거하다"
END
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
STRINGTABLE
BEGIN
IDS_TITLE TITLE_ES_ES
IDS_MANAGEMENT "Administrar el producto actual"
IDS_OPERATION "Seleccione la acción que desea realizar."
IDS_REINSTALL "Reinstalar con ""%1""%rEsta operación requiere suspender los servicios en ejecución.\0 "
IDS_UNINSTALL "Desinstalar"
END

View File

@ -2,6 +2,7 @@
#include <msi.h>
#include <shlwapi.h>
#include <sddl.h>
#include <commctrl.h>
#include <stdio.h>
#include "resource.h"
@ -42,6 +43,42 @@ static INT MatchLanguageCode(LPWSTR langCode)
return -1;
}
static LPWSTR FormatString(HINSTANCE hInstance, UINT uID, ...)
{
LPWSTR pBuffer = NULL, pFormat;
int n = LoadStringW(hInstance, uID, (LPWSTR)&pFormat, 0);
if (n < 2 || pFormat[n - 2] != L'\0')
return NULL;
va_list args = NULL;
va_start(args, uID);
FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, pFormat,
0, 0, (LPWSTR)&pBuffer, 0, &args);
va_end(args);
return pBuffer;
}
static HANDLE CreateReinstallEvent(LPWSTR path, DWORD pathLen)
{
if (!PathAppendW(path, L"frpmgr.exe"))
return NULL;
HANDLE hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
path[pathLen] = L'\0';
if (hFile == INVALID_HANDLE_VALUE)
return NULL;
FILE_ID_INFO fileId;
BOOL ret = GetFileInformationByHandleEx(hFile, FileIdInfo, &fileId, sizeof(fileId));
CloseHandle(hFile);
if (!ret)
return NULL;
CHAR name[_countof("Global\\") + sizeof(fileId) * 2];
int n = sprintf_s(name, _countof(name), "Global\\%llx", fileId.VolumeSerialNumber);
if (n < 0)
return NULL;
for (size_t i = 0; i < sizeof(fileId.FileId); i++)
n += sprintf_s(&name[n], _countof(name) - n, "%02x", fileId.FileId.Identifier[i]);
return CreateEventA(NULL, TRUE, FALSE, name);
}
static INT GetApplicationLanguage(LPWSTR path, DWORD pathLen)
{
if (!PathAppendW(path, L"lang.config"))
@ -144,7 +181,7 @@ INT_PTR CALLBACK LanguageDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM l
return (INT_PTR)FALSE;
}
static int cleanup(void)
static int Cleanup(void)
{
if (msiFile != INVALID_HANDLE_VALUE)
{
@ -159,13 +196,10 @@ static int cleanup(void)
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
INT langIndex = -1;
BOOL installed = FALSE, showDlg = TRUE;
BOOL installed = FALSE, reinstall = FALSE, showDlg = TRUE;
Product product = {
.path = { 0 },
.pathLen = _countof(product.path),
.lang = { 0 },
.langLen = _countof(product.lang),
.version = { 0 },
.versionLen = _countof(product.version)
};
@ -224,6 +258,40 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
if (lang == NULL)
return 0;
if (installed)
{
LPWSTR pszButtonText = FormatString(hInstance, IDS_REINSTALL, lang->name);
const TASKDIALOG_BUTTON buttons[] = {
{ IDYES, pszButtonText },
{ IDNO, MAKEINTRESOURCE(IDS_UNINSTALL) }
};
TASKDIALOGCONFIG config = {
.cbSize = sizeof(config),
.hInstance = hInstance,
.dwFlags = TDF_USE_COMMAND_LINKS,
.dwCommonButtons = TDCBF_CLOSE_BUTTON,
.pszWindowTitle = MAKEINTRESOURCE(IDS_TITLE),
.pszMainIcon = MAKEINTRESOURCE(IDI_ICON),
.pszMainInstruction = MAKEINTRESOURCE(IDS_MANAGEMENT),
.pszContent = MAKEINTRESOURCE(IDS_OPERATION),
.cButtons = ARRAYSIZE(buttons),
.pButtons = buttons,
.nDefaultButton = IDYES
};
LPWSTR newLine;
if (pszButtonText && (newLine = wcschr(pszButtonText, L'\r')))
*newLine = L'\n';
int nButtonPressed = 0;
HRESULT ret = TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL);
if (pszButtonText)
LocalFree((HLOCAL)pszButtonText);
if (ret != S_OK)
return 1;
if (nButtonPressed == IDCLOSE)
return 0;
reinstall = nButtonPressed == IDYES;
}
if (!GetWindowsDirectoryW(msiPath, _countof(msiPath)) || !PathAppendW(msiPath, L"Temp"))
return 1;
GUID guid;
@ -254,7 +322,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
LocalFree(sa.lpSecurityDescriptor);
if (msiFile == INVALID_HANDLE_VALUE)
return 1;
_onexit(cleanup);
_onexit(Cleanup);
DWORD bytesWritten;
BOOL ok = WriteFile(msiFile, pResData, resSize, &bytesWritten, NULL);
CloseHandle(msiFile);
@ -262,7 +330,21 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
if (!ok || bytesWritten != resSize)
return 1;
MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
if (installed)
{
if (!reinstall)
return MsiInstallProductW(msiPath, L"REMOVE=ALL");
HANDLE hEvent = CreateReinstallEvent(product.path, product.pathLen);
UINT ret = MsiInstallProductW(msiPath, L"REMOVE=ALL MSIDISABLERMRESTART=1 SAVESTATE=1");
if (hEvent)
CloseHandle(hEvent);
if (ret != ERROR_SUCCESS)
return 1;
MsiSetInternalUI(INSTALLUILEVEL_BASIC | INSTALLUILEVEL_ENDDIALOG, NULL);
}
else
MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
#define CMD_FORMAT L"ProductLanguage=%s PREVINSTALLFOLDER=\"%s\""
WCHAR cmd[_countof(CMD_FORMAT) + _countof(product.path)];
if (swprintf_s(cmd, _countof(cmd), CMD_FORMAT, lang->id, product.path) < 0)

View File

@ -5,9 +5,12 @@ import (
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"github.com/Microsoft/go-winio"
"github.com/fatedier/frp/pkg/util/log"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"github.com/koho/frpmgr/pkg/config"
@ -82,6 +85,9 @@ func (service *frpService) Execute(args []string, r <-chan svc.ChangeRequest, ch
switch c.Cmd {
case svc.Stop, svc.Shutdown:
svr.Stop(false)
if code := shutdownReason(path); code > 0 {
return false, code
}
return
case svc.ParamChange:
// Reload service
@ -125,3 +131,24 @@ func ReloadService(configPath string) error {
_, err = service.Control(svc.ParamChange)
return err
}
func shutdownReason(path string) uint32 {
f, err := os.Open(path)
if err != nil {
return 0
}
fileID, err := winio.GetFileID(f)
f.Close()
if err != nil {
return 0
}
name, err := syscall.UTF16PtrFromString(fmt.Sprintf("Global\\%x%x", fileID.VolumeSerialNumber, fileID.FileID))
if err != nil {
return 0
}
if h, err := windows.OpenEvent(windows.READ_CONTROL, false, name); err == nil {
windows.CloseHandle(h)
return uint32(windows.ERROR_FAIL_NOACTION_REBOOT)
}
return 0
}