mirror of
https://github.com/koho/frpmgr.git
synced 2025-10-20 07:33:47 +08:00
Rewrite custom actions in C (#253)
* Rewrite custom actions in C * Add language setting file * Improve setup * Improve build script * Skip directory selection on upgrade * Fix dll symbols error on x86 * Validate install path * Hide language selection dialog on upgrade * Improve macros * Add multi-language support to the setup * Remove old files
This commit is contained in:
@ -72,7 +72,7 @@ VERSIONINFO_TEMPLATE(
|
||||
"FRP 관리자"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH
|
||||
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"0C0A04B0", 0x0C0A, 1200,
|
||||
"Administrador de FRP"
|
||||
|
14
i18n/text.go
14
i18n/text.go
@ -42,7 +42,14 @@ func GetLanguage() string {
|
||||
|
||||
// langInConfig returns the UI language code in config file
|
||||
func langInConfig() string {
|
||||
b, err := os.ReadFile(config.DefaultAppFile)
|
||||
b, err := os.ReadFile(config.LangFile)
|
||||
if err == nil {
|
||||
id := string(b)
|
||||
if _, ok := IDToName[id]; ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
b, err = os.ReadFile(config.DefaultAppFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@ -52,7 +59,10 @@ func langInConfig() string {
|
||||
if err = json.Unmarshal(b, &s); err != nil {
|
||||
return ""
|
||||
}
|
||||
return s.Lang
|
||||
if _, ok := IDToName[s.Lang]; ok {
|
||||
return s.Lang
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// lang returns the user preferred UI language.
|
||||
|
3
installer/actions/.gitignore
vendored
3
installer/actions/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
.vs
|
||||
bin
|
||||
obj
|
397
installer/actions/actions.c
Normal file
397
installer/actions/actions.c
Normal file
@ -0,0 +1,397 @@
|
||||
#include <windows.h>
|
||||
#include <msi.h>
|
||||
#include <msidefs.h>
|
||||
#include <msiquery.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
#define LEGACY_SERVICE_PREFIX L"FRPC$"
|
||||
#define SERVICE_PREFIX L"frpmgr_"
|
||||
|
||||
static void Log(MSIHANDLE installer, INSTALLMESSAGE messageType, const WCHAR* format, ...)
|
||||
{
|
||||
MSIHANDLE record = MsiCreateRecord(0);
|
||||
if (!record)
|
||||
return;
|
||||
LPWSTR pBuffer = NULL;
|
||||
va_list args = NULL;
|
||||
va_start(args, format);
|
||||
FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, format,
|
||||
0, 0, (LPWSTR)&pBuffer, 0, &args);
|
||||
va_end(args);
|
||||
if (pBuffer)
|
||||
{
|
||||
MsiRecordSetStringW(record, 0, pBuffer);
|
||||
MsiProcessMessage(installer, messageType, record);
|
||||
LocalFree(pBuffer);
|
||||
}
|
||||
MsiCloseHandle(record);
|
||||
}
|
||||
|
||||
static BOOL GetFileInformation(const LPWSTR path, BY_HANDLE_FILE_INFORMATION* fileInfo)
|
||||
{
|
||||
HANDLE file = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
return FALSE;
|
||||
BOOL ret = GetFileInformationByHandle(file, fileInfo);
|
||||
CloseHandle(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void KillProcessesEx(LPWSTR path, BOOL uiOnly)
|
||||
{
|
||||
HANDLE snapshot, process;
|
||||
BY_HANDLE_FILE_INFORMATION fileInfo = { 0 }, procFileInfo = { 0 };
|
||||
WCHAR procPath[MAX_PATH];
|
||||
PROCESSENTRY32W entry;
|
||||
entry.dwSize = sizeof(PROCESSENTRY32W);
|
||||
|
||||
LPWSTR filename = PathFindFileNameW(path);
|
||||
if (!GetFileInformation(path, &fileInfo))
|
||||
return;
|
||||
|
||||
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
for (BOOL ret = Process32FirstW(snapshot, &entry); ret; ret = Process32NextW(snapshot, &entry))
|
||||
{
|
||||
if (_wcsicmp(entry.szExeFile, filename))
|
||||
continue;
|
||||
process = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
|
||||
if (!process)
|
||||
continue;
|
||||
DWORD procPathLen = _countof(procPath);
|
||||
DWORD sessionId = 0;
|
||||
if (!QueryFullProcessImageNameW(process, 0, procPath, &procPathLen))
|
||||
goto next;
|
||||
if (!GetFileInformation(procPath, &procFileInfo))
|
||||
goto next;
|
||||
if (fileInfo.dwVolumeSerialNumber != procFileInfo.dwVolumeSerialNumber ||
|
||||
fileInfo.nFileIndexHigh != procFileInfo.nFileIndexHigh ||
|
||||
fileInfo.nFileIndexLow != procFileInfo.nFileIndexLow)
|
||||
goto next;
|
||||
if (!ProcessIdToSessionId(entry.th32ProcessID, &sessionId))
|
||||
goto next;
|
||||
if (uiOnly && sessionId == 0)
|
||||
goto next;
|
||||
if (TerminateProcess(process, 1))
|
||||
WaitForSingleObject(process, INFINITE);
|
||||
next:
|
||||
CloseHandle(process);
|
||||
}
|
||||
CloseHandle(snapshot);
|
||||
return;
|
||||
}
|
||||
|
||||
__declspec(dllexport) UINT __stdcall KillFrpProcesses(MSIHANDLE installer)
|
||||
{
|
||||
WCHAR path[MAX_PATH];
|
||||
DWORD pathLen = _countof(path);
|
||||
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
if (path[0])
|
||||
KillProcessesEx(path, FALSE);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(dllexport) UINT __stdcall KillFrpGUIProcesses(MSIHANDLE installer)
|
||||
{
|
||||
WCHAR path[MAX_PATH];
|
||||
DWORD pathLen = _countof(path);
|
||||
MSIHANDLE record = MsiCreateRecord(0);
|
||||
if (!record)
|
||||
return ERROR_SUCCESS;
|
||||
MsiRecordSetStringW(record, 0, L"[#frpmgr.exe]");
|
||||
UINT ret = MsiFormatRecordW(installer, record, path, &pathLen);
|
||||
MsiCloseHandle(record);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load application path");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
if (path[0])
|
||||
KillProcessesEx(path, TRUE);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(dllexport) UINT __stdcall EvaluateFrpServices(MSIHANDLE installer)
|
||||
{
|
||||
SC_HANDLE scm = NULL;
|
||||
LPENUM_SERVICE_STATUS_PROCESSW services = NULL;
|
||||
DWORD SERVICE_STATUS_PROCESS_SIZE = 0x10000;
|
||||
DWORD resume = 0;
|
||||
LPQUERY_SERVICE_CONFIGW cfg = NULL;
|
||||
DWORD cfgSize = 0;
|
||||
|
||||
MSIHANDLE db = 0, view = 0;
|
||||
WCHAR path[MAX_PATH];
|
||||
DWORD pathLen = _countof(path);
|
||||
BY_HANDLE_FILE_INFORMATION fileInfo = { 0 }, svcFileInfo = { 0 };
|
||||
BOOL fileInfoExists = FALSE;
|
||||
MSIHANDLE record = MsiCreateRecord(0);
|
||||
if (!record)
|
||||
goto out;
|
||||
MsiRecordSetStringW(record, 0, L"[#frpmgr.exe]");
|
||||
UINT ret = MsiFormatRecordW(installer, record, path, &pathLen);
|
||||
MsiCloseHandle(record);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load application path");
|
||||
goto out;
|
||||
}
|
||||
if (!path[0])
|
||||
goto out;
|
||||
fileInfoExists = GetFileInformation(path, &fileInfo);
|
||||
|
||||
db = MsiGetActiveDatabase(installer);
|
||||
if (!db)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"MsiGetActiveDatabase failed");
|
||||
goto out;
|
||||
}
|
||||
ret = MsiDatabaseOpenViewW(db,
|
||||
L"INSERT INTO `ServiceControl` (`ServiceControl`, `Name`, `Event`, `Component_`, `Wait`) VALUES(?, ?, ?, ?, ?) TEMPORARY",
|
||||
&view);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"MsiDatabaseOpenView failed (%1)", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
scm = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASEW, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
|
||||
if (!scm)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"OpenSCManager failed (%1)", GetLastError());
|
||||
goto out;
|
||||
}
|
||||
|
||||
services = (LPENUM_SERVICE_STATUS_PROCESSW)LocalAlloc(LMEM_FIXED, SERVICE_STATUS_PROCESS_SIZE);
|
||||
if (!services)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"LocalAlloc failed (%1)", GetLastError());
|
||||
goto out;
|
||||
}
|
||||
for (BOOL more = TRUE; more;)
|
||||
{
|
||||
DWORD bytesNeeded = 0, count = 0;
|
||||
if (EnumServicesStatusExW(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, (LPBYTE)services,
|
||||
SERVICE_STATUS_PROCESS_SIZE, &bytesNeeded, &count, &resume, NULL))
|
||||
more = FALSE;
|
||||
else
|
||||
{
|
||||
ret = GetLastError();
|
||||
if (ret != ERROR_MORE_DATA)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"EnumServicesStatusEx failed (%1)", ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < count; ++i)
|
||||
{
|
||||
INT legacy;
|
||||
if ((legacy = _wcsnicmp(services[i].lpServiceName, LEGACY_SERVICE_PREFIX, _countof(LEGACY_SERVICE_PREFIX) - 1)) &&
|
||||
_wcsnicmp(services[i].lpServiceName, SERVICE_PREFIX, _countof(SERVICE_PREFIX) - 1))
|
||||
continue;
|
||||
|
||||
SC_HANDLE service = OpenServiceW(scm, services[i].lpServiceName, SERVICE_QUERY_CONFIG);
|
||||
if (!service)
|
||||
continue;
|
||||
BOOL ok = FALSE;
|
||||
while (!(ok = QueryServiceConfigW(service, cfg, cfgSize, &bytesNeeded)) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
if (cfg)
|
||||
LocalFree(cfg);
|
||||
else
|
||||
bytesNeeded += sizeof(path) + 256 * sizeof(WCHAR); // Additional size for path and display name.
|
||||
cfgSize = bytesNeeded;
|
||||
cfg = (LPQUERY_SERVICE_CONFIGW)LocalAlloc(LMEM_FIXED, cfgSize);
|
||||
if (!cfg)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"LocalAlloc failed (%1)", GetLastError());
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseServiceHandle(service);
|
||||
|
||||
if (!ok || cfg == NULL)
|
||||
continue;
|
||||
INT nArgs = 0;
|
||||
LPWSTR* args = CommandLineToArgvW(cfg->lpBinaryPathName, &nArgs);
|
||||
if (!args)
|
||||
continue;
|
||||
ok = nArgs >= 1 && (fileInfoExists ?
|
||||
(GetFileInformation(args[0], &svcFileInfo) &&
|
||||
fileInfo.dwVolumeSerialNumber == svcFileInfo.dwVolumeSerialNumber &&
|
||||
fileInfo.nFileIndexHigh == svcFileInfo.nFileIndexHigh &&
|
||||
fileInfo.nFileIndexLow == svcFileInfo.nFileIndexLow) : _wcsicmp(args[0], path) == 0);
|
||||
LocalFree(args);
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
Log(installer, INSTALLMESSAGE_INFO, L"Scheduling stop on upgrade or removal on uninstall of service %1", services[i].lpServiceName);
|
||||
GUID guid;
|
||||
if (FAILED(CoCreateGuid(&guid)))
|
||||
continue;
|
||||
WCHAR identifier[40];
|
||||
if (StringFromGUID2(&guid, identifier, _countof(identifier)) == 0)
|
||||
continue;
|
||||
record = MsiCreateRecord(5);
|
||||
if (!record)
|
||||
continue;
|
||||
MsiRecordSetStringW(record, 1, identifier);
|
||||
MsiRecordSetStringW(record, 2, services[i].lpServiceName);
|
||||
MsiRecordSetInteger(record, 3, msidbServiceControlEventStop | msidbServiceControlEventUninstallStop | (legacy == 0 ? msidbServiceControlEventDelete : 0) | msidbServiceControlEventUninstallDelete);
|
||||
MsiRecordSetStringW(record, 4, L"frpmgr.exe");
|
||||
MsiRecordSetInteger(record, 5, 1);
|
||||
ret = MsiViewExecute(view, record);
|
||||
MsiCloseHandle(record);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"MsiViewExecute failed for service %1 (%2)", services[i].lpServiceName, ret);
|
||||
}
|
||||
}
|
||||
LocalFree(services);
|
||||
if (cfg)
|
||||
LocalFree(cfg);
|
||||
|
||||
out:
|
||||
if (scm)
|
||||
CloseServiceHandle(scm);
|
||||
if (view)
|
||||
MsiCloseHandle(view);
|
||||
if (db)
|
||||
MsiCloseHandle(db);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(dllexport) UINT __stdcall SetLangConfig(MSIHANDLE installer)
|
||||
{
|
||||
WCHAR path[MAX_PATH];
|
||||
DWORD pathLen = _countof(path);
|
||||
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
|
||||
goto out;
|
||||
}
|
||||
if (!path[0] || !PathAppendW(path, L"lang.config"))
|
||||
goto out;
|
||||
|
||||
WCHAR localeName[LOCALE_NAME_MAX_LENGTH];
|
||||
if (LCIDToLocaleName(MsiGetLanguage(installer), localeName, _countof(localeName), 0) == 0)
|
||||
goto out;
|
||||
|
||||
HANDLE langFile = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (langFile == INVALID_HANDLE_VALUE)
|
||||
goto out;
|
||||
CHAR buf[LOCALE_NAME_MAX_LENGTH];
|
||||
DWORD bytesWritten = WideCharToMultiByte(CP_UTF8, 0, localeName, -1, buf, sizeof(buf), NULL, NULL);
|
||||
if (bytesWritten > 0)
|
||||
WriteFile(langFile, buf, bytesWritten - 1, &bytesWritten, NULL);
|
||||
CloseHandle(langFile);
|
||||
|
||||
out:
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(dllexport) UINT __stdcall MoveFrpProfiles(MSIHANDLE installer)
|
||||
{
|
||||
WIN32_FIND_DATAW findData;
|
||||
const WCHAR* dirs[] = { NULL, L"profiles" };
|
||||
|
||||
WCHAR path[MAX_PATH], newPath[MAX_PATH];
|
||||
DWORD pathLen = _countof(path), newPathLen = _countof(newPath);
|
||||
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
|
||||
goto out;
|
||||
}
|
||||
if (!path[0] || !PathCombineW(newPath, path, L"profiles"))
|
||||
goto out;
|
||||
if (CreateDirectoryW(newPath, NULL) == 0 && GetLastError() != ERROR_ALREADY_EXISTS)
|
||||
goto out;
|
||||
newPathLen = wcsnlen_s(newPath, _countof(newPath) - 1);
|
||||
for (size_t i = 0; i < _countof(dirs); i++)
|
||||
{
|
||||
path[pathLen] = L'\0';
|
||||
if (dirs[i] && !PathAppendW(path, dirs[i]))
|
||||
continue;
|
||||
pathLen = wcsnlen_s(path, _countof(path) - 1);
|
||||
if (!PathAppendW(path, L"*.ini"))
|
||||
continue;
|
||||
HANDLE hFind = FindFirstFileExW(path, FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, 0);
|
||||
if (hFind != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY || !PathMatchSpecW(findData.cFileName, L"*.ini"))
|
||||
continue;
|
||||
path[pathLen] = L'\0';
|
||||
newPath[newPathLen] = L'\0';
|
||||
if (!PathAppendW(path, findData.cFileName) ||
|
||||
!PathRenameExtensionW(findData.cFileName, L".conf") ||
|
||||
!PathAppendW(newPath, findData.cFileName))
|
||||
continue;
|
||||
MoveFileW(path, newPath);
|
||||
} while (FindNextFileW(hFind, &findData));
|
||||
FindClose(hFind);
|
||||
}
|
||||
}
|
||||
out:
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
__declspec(dllexport) UINT __stdcall RemoveFrpFiles(MSIHANDLE installer)
|
||||
{
|
||||
WCHAR path[MAX_PATH];
|
||||
DWORD pathLen = _countof(path);
|
||||
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
const WCHAR* appFiles[] = { L"app.json", L"lang.config" };
|
||||
for (size_t i = 0; i < _countof(appFiles); i++)
|
||||
{
|
||||
path[pathLen] = L'\0';
|
||||
if (!PathAppendW(path, appFiles[i]))
|
||||
return ERROR_SUCCESS;
|
||||
DeleteFileW(path);
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW findData;
|
||||
const WCHAR* files[][2] = { {L"profiles", L"*.conf"}, {L"logs", L"*.log"} };
|
||||
for (size_t i = 0; i < _countof(files); i++)
|
||||
{
|
||||
path[pathLen] = L'\0';
|
||||
if (!PathAppendW(path, files[i][0]))
|
||||
continue;
|
||||
SIZE_T dirLen = wcsnlen_s(path, _countof(path) - 1);
|
||||
if (!PathAppendW(path, files[i][1]))
|
||||
continue;
|
||||
HANDLE hFind = FindFirstFileExW(path, FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, 0);
|
||||
if (hFind != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY || !PathMatchSpecW(findData.cFileName, files[i][1]))
|
||||
continue;
|
||||
path[dirLen] = L'\0';
|
||||
if (!PathAppendW(path, findData.cFileName))
|
||||
continue;
|
||||
DeleteFileW(path);
|
||||
} while (FindNextFileW(hFind, &findData));
|
||||
FindClose(hFind);
|
||||
}
|
||||
path[dirLen] = L'\0';
|
||||
RemoveDirectoryW(path);
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
8
installer/actions/actions.def
Normal file
8
installer/actions/actions.def
Normal file
@ -0,0 +1,8 @@
|
||||
LIBRARY actions
|
||||
EXPORTS
|
||||
EvaluateFrpServices
|
||||
KillFrpGUIProcesses
|
||||
KillFrpProcesses
|
||||
MoveFrpProfiles
|
||||
RemoveFrpFiles
|
||||
SetLangConfig
|
@ -1,31 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30717.126
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "actions", "actions\actions.csproj", "{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x64.Build.0 = Debug|x64
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x86.Build.0 = Debug|x86
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x64.ActiveCfg = Release|x64
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x64.Build.0 = Release|x64
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x86.ActiveCfg = Release|x86
|
||||
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9B84C910-F22D-4D3F-9BD6-6C2134E26EE8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
|
||||
<!--
|
||||
Use supportedRuntime tags to explicitly specify the version(s) of the .NET Framework runtime that
|
||||
the custom action should run on. If no versions are specified, the chosen version of the runtime
|
||||
will be the "best" match to what Microsoft.Deployment.WindowsInstaller.dll was built against.
|
||||
|
||||
WARNING: leaving the version unspecified is dangerous as it introduces a risk of compatibility
|
||||
problems with future versions of the .NET Framework runtime. It is highly recommended that you specify
|
||||
only the version(s) of the .NET Framework runtime that you have tested against.
|
||||
|
||||
Note for .NET Framework v3.0 and v3.5, the runtime version is still v2.0.
|
||||
|
||||
In order to enable .NET Framework version 2.0 runtime activation policy, which is to load all assemblies
|
||||
by using the latest supported runtime, @useLegacyV2RuntimeActivationPolicy="true".
|
||||
|
||||
For more information, see http://msdn.microsoft.com/en-us/library/bbx34a2h.aspx
|
||||
-->
|
||||
|
||||
<supportedRuntime version="v4.0" />
|
||||
<supportedRuntime version="v2.0.50727"/>
|
||||
|
||||
</startup>
|
||||
|
||||
<!--
|
||||
Add additional configuration settings here. For more information on application config files,
|
||||
see http://msdn.microsoft.com/en-us/library/kza1yk3a.aspx
|
||||
-->
|
||||
|
||||
</configuration>
|
@ -1,290 +0,0 @@
|
||||
using Microsoft.Deployment.WindowsInstaller;
|
||||
using System;
|
||||
using System.Configuration.Install;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
|
||||
namespace actions
|
||||
{
|
||||
public class CustomActions
|
||||
{
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern int MessageBox(int hWnd, String text, String caption, uint type);
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSECURITY_ATTRIBUTES, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool GetFileInformationByHandle(IntPtr handle, ref BY_HANDLE_FILE_INFORMATION hfi);
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern int LCIDToLocaleName(uint Locale, StringBuilder lpName, int cchName, int dwFlags);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct BY_HANDLE_FILE_INFORMATION
|
||||
{
|
||||
public uint dwFileAttributes;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
|
||||
public uint dwVolumeSerialNumber;
|
||||
public uint nFileSizeHigh;
|
||||
public uint nFileSizeLow;
|
||||
public uint nNumberOfLinks;
|
||||
public uint nFileIndexHigh;
|
||||
public uint nFileIndexLow;
|
||||
}
|
||||
|
||||
public const int OPEN_EXISTING = 3;
|
||||
public const int INVALID_HANDLE_VALUE = -1;
|
||||
public const int FILE_ATTRIBUTE_NORMAL = 0x80;
|
||||
public const int MB_OK = 0;
|
||||
public const int MB_YESNO = 0x4;
|
||||
public const int MB_RETRYCANCEL = 0x5;
|
||||
public const int MB_ICONQUESTION = 0x20;
|
||||
public const int MB_ICONWARNING = 0x30;
|
||||
public const int IDYES = 6;
|
||||
public const int IDRETRY = 4;
|
||||
|
||||
public static bool CalculateFileId(string path, out BY_HANDLE_FILE_INFORMATION hfi)
|
||||
{
|
||||
hfi = new BY_HANDLE_FILE_INFORMATION { };
|
||||
IntPtr file = CreateFile(path, 0, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
|
||||
if (file.ToInt32() == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
bool ret = GetFileInformationByHandle(file, ref hfi);
|
||||
CloseHandle(file);
|
||||
if (!ret)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool CompareFile(BY_HANDLE_FILE_INFORMATION f1, BY_HANDLE_FILE_INFORMATION f2)
|
||||
{
|
||||
return f1.dwVolumeSerialNumber == f2.dwVolumeSerialNumber && f1.nFileIndexHigh == f2.nFileIndexHigh && f1.nFileIndexLow == f2.nFileIndexLow;
|
||||
}
|
||||
|
||||
public static int ForceDeleteDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
using (ManagementObject dirObject = new ManagementObject("Win32_Directory.Name='" + path + "'"))
|
||||
{
|
||||
dirObject.Get();
|
||||
ManagementBaseObject outParams = dirObject.InvokeMethod("Delete", null, null);
|
||||
return Convert.ToInt32(outParams.Properties["ReturnValue"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveServices(Session session, string prefix, string binPath)
|
||||
{
|
||||
ServiceController[] services = ServiceController.GetServices();
|
||||
foreach (ServiceController controller in services)
|
||||
{
|
||||
ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + controller.ServiceName + "'");
|
||||
wmiService.Get();
|
||||
string pathName = wmiService.GetPropertyValue("PathName").ToString();
|
||||
string path1 = pathName.Substring(0, Math.Min(binPath.Length, pathName.Length));
|
||||
string path2 = pathName.Substring(1, Math.Min(binPath.Length, pathName.Length - 1));
|
||||
if ((binPath.ToLower().Equals(path1.ToLower()) || binPath.ToLower().Equals(path2.ToLower())) && controller.ServiceName.StartsWith(prefix))
|
||||
{
|
||||
try
|
||||
{
|
||||
controller.Stop();
|
||||
controller.WaitForStatus(ServiceControllerStatus.Stopped);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
session.Log("Failed to stop " + controller.ServiceName);
|
||||
}
|
||||
|
||||
ServiceInstaller installer = new ServiceInstaller
|
||||
{
|
||||
Context = new InstallContext(),
|
||||
ServiceName = controller.ServiceName
|
||||
};
|
||||
try
|
||||
{
|
||||
installer.Uninstall(null);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
session.Log("Failed to uninstall " + controller.ServiceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult KillProcesses(Session session)
|
||||
{
|
||||
session.Log("Killing FRP processes");
|
||||
string binPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(binPath) || !CalculateFileId(binPath, out BY_HANDLE_FILE_INFORMATION binInfo))
|
||||
{
|
||||
return ActionResult.Success;
|
||||
}
|
||||
Process[] processes = Process.GetProcesses();
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CalculateFileId(p.MainModule.FileName, out BY_HANDLE_FILE_INFORMATION info))
|
||||
continue;
|
||||
if (CompareFile(binInfo, info))
|
||||
{
|
||||
p.Kill();
|
||||
p.WaitForExit();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult RemoveFrpFiles(Session session)
|
||||
{
|
||||
session.Log("Removing files");
|
||||
string installPath = session["CustomActionData"];
|
||||
if (!string.IsNullOrEmpty(installPath))
|
||||
{
|
||||
ForceDeleteDirectory(Path.Combine(installPath, "profiles"));
|
||||
ForceDeleteDirectory(Path.Combine(installPath, "logs"));
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine(installPath, "app.json"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session.Log(e.Message);
|
||||
}
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult EvaluateFrpServices(Session session)
|
||||
{
|
||||
session.Log("Evaluate FRP Services");
|
||||
string binPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(binPath))
|
||||
{
|
||||
return ActionResult.Success;
|
||||
}
|
||||
RemoveServices(session, "", binPath);
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult KillGUIProcesses(Session session)
|
||||
{
|
||||
session.Log("Killing FRP GUI processes");
|
||||
string binPath = session.Format(session["WixShellExecTarget"]);
|
||||
if (string.IsNullOrEmpty(binPath))
|
||||
{
|
||||
return ActionResult.Success;
|
||||
}
|
||||
SelectQuery q = new SelectQuery("Win32_Process", "ExecutablePath = '" + binPath.Replace(@"\", @"\\") + "' AND SessionId != 0");
|
||||
ManagementObjectSearcher s = new ManagementObjectSearcher(q);
|
||||
foreach (ManagementObject process in s.Get())
|
||||
{
|
||||
process.Delete();
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult SetLangConfig(Session session)
|
||||
{
|
||||
session.Log("Set language config");
|
||||
string installPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(installPath))
|
||||
{
|
||||
return ActionResult.Failure;
|
||||
}
|
||||
StringBuilder name = new StringBuilder(500);
|
||||
if (LCIDToLocaleName((uint)session.Language, name, name.Capacity, 0) == 0)
|
||||
{
|
||||
return ActionResult.Failure;
|
||||
}
|
||||
string cfgPath = Path.Combine(installPath, "app.json");
|
||||
if (!File.Exists(cfgPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(cfgPath, "{\n \"lang\": \"" + name.ToString() + "\"\n}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session.Log(e.Message);
|
||||
}
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult MoveFrpProfiles(Session session)
|
||||
{
|
||||
session.Log("Moving FRP profiles");
|
||||
string installPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(installPath))
|
||||
{
|
||||
return ActionResult.Failure;
|
||||
}
|
||||
string profilePath = Path.Combine(installPath, "profiles");
|
||||
Directory.CreateDirectory(profilePath);
|
||||
foreach (string profile in Directory.GetFiles(installPath, "*.ini"))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move(profile, Path.Combine(profilePath, Path.GetFileName(profile)));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session.Log(e.Message);
|
||||
}
|
||||
}
|
||||
foreach (string profile in Directory.GetFiles(profilePath, "*.ini"))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move(profile, Path.Combine(profilePath, Path.GetFileNameWithoutExtension(profile) + ".conf"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
session.Log(e.Message);
|
||||
}
|
||||
}
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
[CustomAction]
|
||||
public static ActionResult RemoveOldFrpServices(Session session)
|
||||
{
|
||||
session.Log("Remove old FRP Services");
|
||||
string binPath = session["CustomActionData"];
|
||||
if (string.IsNullOrEmpty(binPath))
|
||||
{
|
||||
return ActionResult.Success;
|
||||
}
|
||||
RemoveServices(session, "FRPC$", binPath);
|
||||
return ActionResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("FRP Manager Setup Custom Actions")]
|
||||
[assembly: AssemblyDescription("FRP Manager Setup Custom Actions")]
|
||||
[assembly: AssemblyCompany("FRP Manager Project")]
|
||||
[assembly: AssemblyProduct("FRP Manager")]
|
||||
[assembly: AssemblyCopyright("Copyright © FRP Manager Project")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("dc9743da-8782-4a7c-8b46-b2d4eea19d4e")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.21.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.21.1.0")]
|
@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>actions</RootNamespace>
|
||||
<AssemblyName>actions</AssemblyName>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.Deployment.WindowsInstaller">
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CustomAction.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Content Include="CustomAction.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(WixCATargetsPath)" Condition=" '$(WixCATargetsPath)' != '' " />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.CA.targets" Condition=" '$(WixCATargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.CA.targets') " />
|
||||
<Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixCATargetsImported)' != 'true' ">
|
||||
<Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
|
||||
</Target>
|
||||
</Project>
|
10
installer/actions/manifest.xml
Normal file
10
installer/actions/manifest.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
||||
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
38
installer/actions/version.rc
Normal file
38
installer/actions/version.rc
Normal file
@ -0,0 +1,38 @@
|
||||
#include <windows.h>
|
||||
|
||||
#pragma code_page(65001) // UTF-8
|
||||
|
||||
#define STRINGIZE(x) #x
|
||||
#define EXPAND(x) STRINGIZE(x)
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_ARRAY
|
||||
PRODUCTVERSION VERSION_ARRAY
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS 0x0
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "FRP Manager Project"
|
||||
VALUE "FileDescription", "FRP Manager Setup Custom Actions"
|
||||
VALUE "FileVersion", EXPAND(VERSION_STR)
|
||||
VALUE "InternalName", "frpmgr-actions"
|
||||
VALUE "LegalCopyright", "Copyright © FRP Manager Project"
|
||||
VALUE "OriginalFilename", "actions.dll"
|
||||
VALUE "ProductName", "FRP Manager"
|
||||
VALUE "ProductVersion", EXPAND(VERSION_STR)
|
||||
VALUE "Comments", "https://github.com/koho/frpmgr"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
ISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
|
@ -16,11 +16,6 @@ if "%ARCH%" == "" (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not defined WIX (
|
||||
echo ERROR: WIX was not found.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:build
|
||||
if not exist build md build
|
||||
set PLAT_DIR=build\%ARCH%
|
||||
@ -38,11 +33,15 @@ if not defined WIX (
|
||||
exit /b 0
|
||||
|
||||
:build_actions
|
||||
msbuild actions\actions.sln /t:Rebuild /p:Configuration=Release /p:Platform="%ARCH%" || goto :error
|
||||
copy actions\actions\bin\%ARCH%\Release\actions.CA.dll %PLAT_DIR%\actions.dll /y || goto :error
|
||||
rc /DVERSION_ARRAY=%VERSION:.=,% /DVERSION_STR=%VERSION% /Fo %PLAT_DIR%\actions.res actions\version.rc || goto :error
|
||||
cl /O2 /LD /MD /DNDEBUG /Fe%PLAT_DIR%\actions.dll /Fo%PLAT_DIR%\actions.obj actions\actions.c /link /DEF:actions\actions.def %PLAT_DIR%\actions.res msi.lib shell32.lib advapi32.lib shlwapi.lib ole32.lib || goto :error
|
||||
goto :eof
|
||||
|
||||
:build_msi
|
||||
if not defined WIX (
|
||||
echo ERROR: WIX was not found.
|
||||
exit /b 1
|
||||
)
|
||||
set WIX_CANDLE_FLAGS=-dVERSION=%VERSION%
|
||||
set WIX_LIGHT_FLAGS=-ext "%WIX%bin\WixUtilExtension.dll" -ext "%WIX%bin\WixUIExtension.dll" -sval
|
||||
set WIX_OBJ=%PLAT_DIR%\frpmgr.wixobj
|
||||
@ -58,8 +57,21 @@ if not defined WIX (
|
||||
goto :eof
|
||||
|
||||
:build_setup
|
||||
rc /DFILENAME=%SETUP_FILENAME% /DVERSION_ARRAY=%VERSION:.=,% /DVERSION_STR=%VERSION% /DMSI_FILE=%MSI_FILE:\=\\% /Fo %PLAT_DIR%\rsrc.res setup\resource.rc || goto :error
|
||||
cl /Fe%PLAT_DIR%\setup.exe /Fo%PLAT_DIR%\setup.obj /utf-8 setup\setup.c /link /subsystem:windows %PLAT_DIR%\rsrc.res shlwapi.lib msi.lib user32.lib advapi32.lib || goto :error
|
||||
rc /DFILENAME=%SETUP_FILENAME% /DVERSION_ARRAY=%VERSION:.=,% /DVERSION_STR=%VERSION% /DMSI_FILE=%MSI_FILE:\=\\% /Fo %PLAT_DIR%\setup.res setup\resource.rc || goto :error
|
||||
set ARCH_LINE=-1
|
||||
for /f "tokens=1 delims=:" %%a in ('findstr /n /r ".*=.*\"%ARCH%\"" msi\frpmgr.wxs') do set ARCH_LINE=%%a
|
||||
if %ARCH_LINE% lss 0 (
|
||||
echo ERROR: unsupported architecture.
|
||||
exit /b 1
|
||||
)
|
||||
for /f "tokens=1,5 delims=: " %%a in ('findstr /n /r "UpgradeCode.*=.*\"[0-9a-fA-F-]*\"" msi\frpmgr.wxs') do (
|
||||
if %%a gtr %ARCH_LINE% if not defined UPGRADE_CODE set UPGRADE_CODE=%%b
|
||||
)
|
||||
if not defined UPGRADE_CODE (
|
||||
echo ERROR: UpgradeCode was not found.
|
||||
exit /b 1
|
||||
)
|
||||
cl /O2 /MD /DUPGRADE_CODE=L\"{%UPGRADE_CODE%}\" /DVERSION=L\"%VERSION%\" /DNDEBUG /Fe%PLAT_DIR%\setup.exe /Fo%PLAT_DIR%\setup.obj setup\setup.c /link /subsystem:windows %PLAT_DIR%\setup.res shlwapi.lib msi.lib user32.lib advapi32.lib ole32.lib || goto :error
|
||||
goto :eof
|
||||
|
||||
:dist
|
||||
|
@ -1,19 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?if $(sys.BUILDARCH) = x64 ?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?if $(sys.BUILDARCH) = "x64" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?define UpgradeCode = "c9f7c2b3-291a-454a-9871-150d98dc2645" ?>
|
||||
<?else?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define UpgradeCode = "C9F7C2B3-291A-454A-9871-150D98DC2645" ?>
|
||||
<?elseif $(sys.BUILDARCH) = "x86" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?define UpgradeCode = "46E3AA36-10BB-4CD9-92E3-5F990AB5FC88" ?>
|
||||
<?else?>
|
||||
<?error Unknown platform ?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*" Name="!(loc.ApplicationName)" Language="!(loc.Language)" Version="$(var.VERSION)" Manufacturer="FRP Manager Project" UpgradeCode="$(var.UpgradeCode)">
|
||||
|
||||
<Package InstallerVersion="400" Compressed="yes" InstallScope="perMachine" Languages="1033,1041,1042,2052,1028,3082" Description="!(loc.ApplicationName)" />
|
||||
<Package InstallerVersion="400" Compressed="yes" InstallScope="perMachine" Languages="1033,1041,1042,2052,1028,3082" Description="!(loc.ApplicationName)" ReadOnly="yes" />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
@ -26,10 +26,10 @@
|
||||
-->
|
||||
<MajorUpgrade AllowDowngrades="yes" />
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="..\icon\app.ico" />
|
||||
<Icon Id="app.ico" SourceFile="..\icon\app.ico" />
|
||||
<Binary Id="actions.dll" SourceFile="build\$(sys.BUILDARCH)\actions.dll" />
|
||||
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPPRODUCTICON" Value="app.ico" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://github.com/koho/frpmgr" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" />
|
||||
<Property Id="DISABLEADVTSHORTCUTS" Value="yes" />
|
||||
@ -45,8 +45,8 @@
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
<Property Id="LicenseAccepted" Value="1" />
|
||||
|
||||
<Feature Id="ProductFeature" Title="FRP" Level="1">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
<Feature Id="CoreFeature" Title="!(loc.ApplicationName)" Level="1">
|
||||
<ComponentGroupRef Id="CoreComponents" />
|
||||
</Feature>
|
||||
|
||||
<UI>
|
||||
@ -54,8 +54,12 @@
|
||||
<UIRef Id="WixUI_ErrorProgressText" />
|
||||
|
||||
<!-- Skip license dialog -->
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">NOT WIX_UPGRADE_DETECTED</Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">NOT WIX_UPGRADE_DETECTED</Publish>
|
||||
|
||||
<!-- Skip directory selection on upgrade -->
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="2">WIX_UPGRADE_DETECTED</Publish>
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">WIX_UPGRADE_DETECTED</Publish>
|
||||
|
||||
<!-- Launch application after installation -->
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">NOT Installed</Publish>
|
||||
@ -78,9 +82,9 @@
|
||||
Components
|
||||
-->
|
||||
<Fragment>
|
||||
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
|
||||
<Component Guid="{E39EABEF-A7EB-4EAF-AD3E-A1254450BBE1}" Id="MainApplication" Win64="$(var.Win64)">
|
||||
<File Id="MainApplication" Source="..\bin\$(sys.BUILDARCH)\frpmgr.exe" KeyPath="yes">
|
||||
<ComponentGroup Id="CoreComponents" Directory="INSTALLFOLDER">
|
||||
<Component Id="frpmgr.exe">
|
||||
<File Id="frpmgr.exe" Source="..\bin\$(sys.BUILDARCH)\frpmgr.exe" KeyPath="yes">
|
||||
<Shortcut Id="StartMenuShortcut" Name="!(loc.ApplicationName)" Directory="ProgramMenuFolder" WorkingDirectory="INSTALLFOLDER" Advertise="yes"/>
|
||||
</File>
|
||||
<!-- A dummy to make WiX create ServiceControl table for us. -->
|
||||
@ -94,43 +98,40 @@
|
||||
-->
|
||||
<Fragment>
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
|
||||
<CustomAction Id="KillProcesses.SetProperty" Return="check" Property="KillProcesses" Value="[#MainApplication]" />
|
||||
<CustomAction Id="EvaluateFrpServices.SetProperty" Return="check" Property="EvaluateFrpServices" Value="[#MainApplication]" />
|
||||
<CustomAction Id="KillFrpProcesses.SetProperty" Return="check" Property="KillFrpProcesses" Value="[#frpmgr.exe]" />
|
||||
<CustomAction Id="RemoveFrpFiles.SetProperty" Return="check" Property="RemoveFrpFiles" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="SetLangConfig.SetProperty" Return="check" Property="SetLangConfig" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="MoveFrpProfiles.SetProperty" Return="check" Property="MoveFrpProfiles" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="RemoveOldFrpServices.SetProperty" Return="check" Property="RemoveOldFrpServices" Value="[#MainApplication]" />
|
||||
|
||||
<!--
|
||||
Launch application
|
||||
-->
|
||||
<Property Id="WixShellExecTarget" Value="[#MainApplication]" />
|
||||
<Property Id="WixShellExecTarget" Value="[#frpmgr.exe]" />
|
||||
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
|
||||
|
||||
<!--
|
||||
Close GUI windows
|
||||
-->
|
||||
<CustomAction Id="KillGUIProcesses" BinaryKey="actions.dll" DllEntry="KillGUIProcesses" Impersonate="yes" Execute="immediate" />
|
||||
<CustomAction Id="KillFrpGUIProcesses" BinaryKey="actions.dll" DllEntry="KillFrpGUIProcesses" Impersonate="yes" Execute="immediate" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="KillGUIProcesses" Before="InstallValidate">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
<Custom Action="KillFrpGUIProcesses" Before="InstallValidate">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!--
|
||||
Evaluate FRP services
|
||||
-->
|
||||
<CustomAction Id="EvaluateFrpServices" BinaryKey="actions.dll" DllEntry="EvaluateFrpServices" Impersonate="no" Execute="deferred" />
|
||||
<CustomAction Id="EvaluateFrpServices" BinaryKey="actions.dll" DllEntry="EvaluateFrpServices" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="EvaluateFrpServices.SetProperty" After="InstallInitialize" />
|
||||
<Custom Action="EvaluateFrpServices" After="EvaluateFrpServices.SetProperty">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
<Custom Action="EvaluateFrpServices" After="InstallInitialize">NOT (UPGRADINGPRODUCTCODE AND (REMOVE="ALL"))</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!--
|
||||
Kill lingering processes
|
||||
-->
|
||||
<CustomAction Id="KillProcesses" BinaryKey="actions.dll" DllEntry="KillProcesses" Impersonate="no" Execute="deferred" />
|
||||
<CustomAction Id="KillFrpProcesses" BinaryKey="actions.dll" DllEntry="KillFrpProcesses" Impersonate="no" Execute="deferred" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="KillProcesses.SetProperty" After="StopServices" />
|
||||
<Custom Action="KillProcesses" After="KillProcesses.SetProperty">REMOVE="ALL"</Custom>
|
||||
<Custom Action="KillFrpProcesses.SetProperty" After="StopServices" />
|
||||
<Custom Action="KillFrpProcesses" After="KillFrpProcesses.SetProperty">REMOVE="ALL"</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!--
|
||||
@ -159,14 +160,5 @@
|
||||
<Custom Action="MoveFrpProfiles.SetProperty" After="InstallFiles" />
|
||||
<Custom Action="MoveFrpProfiles" After="MoveFrpProfiles.SetProperty">NOT (REMOVE="ALL")</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!--
|
||||
Delete old version frp services
|
||||
-->
|
||||
<CustomAction Id="RemoveOldFrpServices" BinaryKey="actions.dll" DllEntry="RemoveOldFrpServices" Impersonate="no" Execute="deferred" />
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="RemoveOldFrpServices.SetProperty" After="InstallFiles" />
|
||||
<Custom Action="RemoveOldFrpServices" After="RemoveOldFrpServices.SetProperty">NOT (REMOVE="ALL")</Custom>
|
||||
</InstallExecuteSequence>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
@ -5,7 +5,13 @@
|
||||
|
||||
#define STRINGIZE(x) #x
|
||||
#define EXPAND(x) STRINGIZE(x)
|
||||
#define TITLE "FRP Manager Setup"
|
||||
|
||||
#define TITLE_EN_US "FRP Manager Setup"
|
||||
#define TITLE_ZH_CN "FRP 管理器安装程序"
|
||||
#define TITLE_ZH_TW "FRP 管理器安裝程式"
|
||||
#define TITLE_JA_JP "FRP マネージャーインストーラー"
|
||||
#define TITLE_KO_KR "FRP 관리자 설치 프로그램"
|
||||
#define TITLE_ES_ES "Instalación de Administrador de FRP"
|
||||
|
||||
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
|
||||
@ -46,54 +52,84 @@ END
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"040904B0", 0x0409, 1200,
|
||||
TITLE,
|
||||
TITLE_EN_US,
|
||||
"FRP Manager"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"080404B0", 0x0804, 1200,
|
||||
"FRP 管理器安装程序",
|
||||
TITLE_ZH_CN,
|
||||
"FRP 管理器"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"040404B0", 0x0404, 1200,
|
||||
"FRP 管理器安裝程式",
|
||||
TITLE_ZH_TW,
|
||||
"FRP 管理器"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"041104B0", 0x0411, 1200,
|
||||
"FRP マネージャーインストーラー",
|
||||
TITLE_JA_JP,
|
||||
"FRP マネージャ"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"041204B0", 0x0412, 1200,
|
||||
"FRP 관리자 설치 프로그램",
|
||||
TITLE_KO_KR,
|
||||
"FRP 관리자"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH
|
||||
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
|
||||
VERSIONINFO_TEMPLATE(
|
||||
"0C0A04B0", 0x0C0A, 1200,
|
||||
"Instalación de Administrador de FRP",
|
||||
TITLE_ES_ES,
|
||||
"Administrador de FRP"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
||||
IDD_LANG_DIALOG DIALOGEX 0, 0, 252, 79
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION TITLE
|
||||
FONT 8, "Tahoma"
|
||||
BEGIN
|
||||
COMBOBOX IDC_LANG_COMBO, 34, 40, 211, 374, CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
|
||||
DEFPUSHBUTTON "OK", IDOK, 141, 58, 50, 14, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP
|
||||
PUSHBUTTON "Cancel", IDCANCEL, 195, 58, 50, 14, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP
|
||||
LTEXT "Select the language for the installation from the choices below.", IDC_STATIC, 34, 8, 211, 25, SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP
|
||||
ICON IDI_ICON, IDC_STATIC, 7, 7, 21, 20, SS_ICON | WS_CHILD | WS_VISIBLE
|
||||
#define LANG_DIALOG_TEMPLATE(title, description, ok, cancel) \
|
||||
IDD_LANG_DIALOG DIALOGEX 0, 0, 252, 69 \
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU \
|
||||
CAPTION title \
|
||||
FONT 9, "Segoe UI" \
|
||||
BEGIN \
|
||||
COMBOBOX IDC_LANG_COMBO, 34, 30, 211, 374, CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP \
|
||||
DEFPUSHBUTTON ok, IDOK, 141, 48, 50, 14, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP \
|
||||
PUSHBUTTON cancel, IDCANCEL, 195, 48, 50, 14, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP \
|
||||
LTEXT description, IDC_STATIC, 34, 8, 211, 20, SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP \
|
||||
ICON IDI_ICON, IDC_STATIC, 7, 7, 21, 20, SS_ICON | WS_CHILD | WS_VISIBLE \
|
||||
END
|
||||
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
LANG_DIALOG_TEMPLATE(
|
||||
TITLE_EN_US, "Select the language for the installation from the choices below.", "OK", "Cancel"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||
LANG_DIALOG_TEMPLATE(
|
||||
TITLE_ZH_CN, "从下列选项中选择安装语言。", "确定", "取消"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
|
||||
LANG_DIALOG_TEMPLATE(
|
||||
TITLE_ZH_TW, "從下列選項中選擇安裝語言。", "確定", "取消"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
|
||||
LANG_DIALOG_TEMPLATE(
|
||||
TITLE_JA_JP, "以下のオプションからインストール言語を選択してください。", "OK", "キャンセル"
|
||||
)
|
||||
|
||||
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
|
||||
LANG_DIALOG_TEMPLATE(
|
||||
TITLE_KO_KR, "아래 옵션에서 설치 언어를 선택하세요.", "확인", "취소"
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
@ -1,169 +1,273 @@
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
#define UNICODE
|
||||
#define _UNICODE
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#include <ntsecapi.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <msi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <sddl.h>
|
||||
#include <stdio.h>
|
||||
#include "resource.h"
|
||||
|
||||
static WCHAR msiPath[MAX_PATH];
|
||||
static HANDLE msiFile = INVALID_HANDLE_VALUE;
|
||||
|
||||
typedef struct {
|
||||
LCID id;
|
||||
TCHAR name[16];
|
||||
char code[10];
|
||||
WCHAR id[5];
|
||||
WCHAR name[16];
|
||||
WCHAR code[10];
|
||||
} Language;
|
||||
|
||||
static TCHAR msiFile[MAX_PATH];
|
||||
static HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
typedef struct {
|
||||
WCHAR path[MAX_PATH];
|
||||
DWORD pathLen;
|
||||
WCHAR lang[10];
|
||||
DWORD langLen;
|
||||
WCHAR version[20];
|
||||
DWORD versionLen;
|
||||
} Product;
|
||||
|
||||
static Language languages[] = {
|
||||
{2052, TEXT("简体中文"), "zh-CN"},
|
||||
{1028, TEXT("繁體中文"), "zh-TW"},
|
||||
{1033, TEXT("English"), "en-US"},
|
||||
{1041, TEXT("日本語"), "ja-JP"},
|
||||
{1042, TEXT("한국어"), "ko-KR"},
|
||||
{3082, TEXT("Español"), "es-ES"},
|
||||
{L"2052", L"简体中文", L"zh-CN"},
|
||||
{L"1028", L"繁體中文", L"zh-TW"},
|
||||
{L"1033", L"English", L"en-US"},
|
||||
{L"1041", L"日本語", L"ja-JP"},
|
||||
{L"1042", L"한국어", L"ko-KR"},
|
||||
{L"3082", L"Español", L"es-ES"},
|
||||
};
|
||||
|
||||
static BOOL RandomString(TCHAR ss[32]) {
|
||||
uint8_t bytes[32];
|
||||
if (!RtlGenRandom(bytes, sizeof(bytes)))
|
||||
return FALSE;
|
||||
for (int i = 0; i < 31; ++i) {
|
||||
ss[i] = (TCHAR) (bytes[i] % 26 + 97);
|
||||
static INT MatchLanguageCode(LPWSTR langCode)
|
||||
{
|
||||
for (size_t i = 0; i < _countof(languages); i++)
|
||||
{
|
||||
if (wcscmp(languages[i].code, langCode) == 0)
|
||||
return i;
|
||||
}
|
||||
ss[31] = '\0';
|
||||
return TRUE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int Cleanup(void) {
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
for (int i = 0; i < 200 && !DeleteFile(msiFile) && GetLastError() != ERROR_FILE_NOT_FOUND; ++i)
|
||||
Sleep(200);
|
||||
static INT GetApplicationLanguage(LPWSTR path, DWORD pathLen)
|
||||
{
|
||||
if (!PathAppendW(path, L"lang.config"))
|
||||
return -1;
|
||||
DWORD bytesRead = 0;
|
||||
HANDLE hFile = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
path[pathLen] = L'\0';
|
||||
if (hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CHAR buf[LOCALE_NAME_MAX_LENGTH];
|
||||
WCHAR localeName[LOCALE_NAME_MAX_LENGTH];
|
||||
BOOL ok = ReadFile(hFile, buf, sizeof(buf) - 1, &bytesRead, NULL);
|
||||
CloseHandle(hFile);
|
||||
if (ok && bytesRead != 0)
|
||||
{
|
||||
buf[bytesRead] = 0;
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, buf, -1, localeName, _countof(localeName)) > 0)
|
||||
{
|
||||
INT i = MatchLanguageCode(localeName);
|
||||
if (i >= 0)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!PathAppendW(path, L"app.json"))
|
||||
return -1;
|
||||
hFile = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
path[pathLen] = L'\0';
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
CHAR buf[100];
|
||||
// To avoid JSON dependency, we require the first field to be the language setting.
|
||||
static const CHAR* langKey = "{\"lang\":\"*\"";
|
||||
WCHAR langCode[10];
|
||||
DWORD langCodeLen = 0;
|
||||
INT j = 0;
|
||||
while (ReadFile(hFile, buf, sizeof(buf), &bytesRead, NULL) && bytesRead != 0)
|
||||
{
|
||||
for (DWORD i = 0; i < bytesRead; i++)
|
||||
{
|
||||
if (langKey[j] == '*')
|
||||
{
|
||||
if (buf[i] == '"')
|
||||
j++;
|
||||
else
|
||||
{
|
||||
langCode[langCodeLen++] = buf[i];
|
||||
if (langCodeLen >= sizeof(langCode) - 1)
|
||||
goto out;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (buf[i] == langKey[j])
|
||||
{
|
||||
j++;
|
||||
if (langKey[j] == 0)
|
||||
goto out;
|
||||
}
|
||||
else if (buf[i] != '\t' && buf[i] != ' ' && buf[i] != '\r' && buf[i] != '\n')
|
||||
goto out;
|
||||
else if (langKey[j] != '{' && langKey[j] != ':' && j > 0 && langKey[j - 1] != '{' && langKey[j - 1] != ':')
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
CloseHandle(hFile);
|
||||
if (langKey[j] != 0 || langCodeLen == 0)
|
||||
return -1;
|
||||
langCode[langCodeLen] = 0;
|
||||
return MatchLanguageCode(langCode);
|
||||
}
|
||||
|
||||
INT_PTR CALLBACK LanguageDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch (message) {
|
||||
case WM_INITDIALOG:
|
||||
for (size_t i = 0; i < _countof(languages); i++)
|
||||
SendDlgItemMessageW(hDlg, IDC_LANG_COMBO, CB_ADDSTRING, 0, (LPARAM)languages[i].name);
|
||||
SendDlgItemMessageW(hDlg, IDC_LANG_COMBO, CB_SETCURSEL, lParam, 0);
|
||||
return (INT_PTR)TRUE;
|
||||
|
||||
case WM_COMMAND:
|
||||
INT_PTR nResult = LOWORD(wParam);
|
||||
if (nResult == IDOK || nResult == IDCANCEL)
|
||||
{
|
||||
if (nResult == IDOK)
|
||||
{
|
||||
LRESULT i = SendDlgItemMessageW(hDlg, IDC_LANG_COMBO, CB_GETCURSEL, 0, 0);
|
||||
nResult = (i >= 0 && i < _countof(languages)) ? (INT_PTR)&languages[i] : 0;
|
||||
}
|
||||
else
|
||||
nResult = 0;
|
||||
EndDialog(hDlg, nResult);
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (INT_PTR)FALSE;
|
||||
}
|
||||
|
||||
static int cleanup(void)
|
||||
{
|
||||
if (msiFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(msiFile);
|
||||
msiFile = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
for (INT i = 0; i < 200 && !DeleteFileW(msiPath) && GetLastError() != ERROR_FILE_NOT_FOUND; i++)
|
||||
Sleep(200);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Language *GetPreferredLang(TCHAR *folder) {
|
||||
TCHAR langPath[MAX_PATH];
|
||||
if (PathCombine(langPath, folder, L"app.json") == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
FILE *file;
|
||||
if (_wfopen_s(&file, langPath, L"rb") != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fseek(file, 0L, SEEK_END);
|
||||
long fileSize = ftell(file);
|
||||
fseek(file, 0L, SEEK_SET);
|
||||
char *buf = malloc(fileSize + 1);
|
||||
size_t size = fread(buf, 1, fileSize, file);
|
||||
buf[size] = 0;
|
||||
fclose(file);
|
||||
const char *p1 = strstr(buf, "\"lang\"");
|
||||
if (p1 == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
const char *p2 = strstr(p1, ":");
|
||||
if (p2 == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
const char *p3 = strstr(p2, "\"");
|
||||
if (p3 == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
for (int i = 0; i < sizeof(languages) / sizeof(languages[0]); i++) {
|
||||
if (strncmp(p3 + 1, languages[i].code, strlen(languages[i].code)) == 0) {
|
||||
free(buf);
|
||||
return &languages[i];
|
||||
}
|
||||
}
|
||||
cleanup:
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
|
||||
{
|
||||
INT langIndex = -1;
|
||||
BOOL installed = FALSE, showDlg = TRUE;
|
||||
Product product = {
|
||||
.path = { 0 },
|
||||
.pathLen = _countof(product.path),
|
||||
.lang = { 0 },
|
||||
.langLen = _countof(product.lang),
|
||||
.version = { 0 },
|
||||
.versionLen = _countof(product.version)
|
||||
};
|
||||
|
||||
INT_PTR CALLBACK LangDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_INITDIALOG:
|
||||
for (int i = 0; i < sizeof(languages) / sizeof(languages[0]); i++) {
|
||||
SendDlgItemMessage(hDlg, IDC_LANG_COMBO, CB_ADDSTRING, 0, (LPARAM) languages[i].name);
|
||||
}
|
||||
SendDlgItemMessage(hDlg, IDC_LANG_COMBO, CB_SETCURSEL, 0, 0);
|
||||
return (INT_PTR) TRUE;
|
||||
|
||||
case WM_COMMAND:
|
||||
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
|
||||
INT_PTR nResult = LOWORD(wParam);
|
||||
if (LOWORD(wParam) == IDOK) {
|
||||
int idx = SendDlgItemMessage(hDlg, IDC_LANG_COMBO, CB_GETCURSEL, 0, 0);
|
||||
nResult = (INT_PTR) &languages[idx];
|
||||
#ifdef UPGRADE_CODE
|
||||
WCHAR productCode[39];
|
||||
if (MsiEnumRelatedProductsW(UPGRADE_CODE, 0, 0, productCode) == ERROR_SUCCESS)
|
||||
{
|
||||
MsiGetProductInfo(productCode, INSTALLPROPERTY_VERSIONSTRING, product.version, &product.versionLen);
|
||||
if (MsiGetProductInfo(productCode, INSTALLPROPERTY_INSTALLLOCATION, product.path, &product.pathLen) == ERROR_SUCCESS && product.path[0])
|
||||
langIndex = GetApplicationLanguage(product.path, product.pathLen);
|
||||
if (MsiGetProductInfo(productCode, INSTALLPROPERTY_INSTALLEDLANGUAGE, product.lang, &product.langLen) == ERROR_SUCCESS && langIndex < 0)
|
||||
{
|
||||
for (size_t i = 0; i < _countof(languages); i++)
|
||||
{
|
||||
if (wcscmp(languages[i].id, product.lang) == 0)
|
||||
{
|
||||
langIndex = i;
|
||||
break;
|
||||
}
|
||||
EndDialog(hDlg, nResult);
|
||||
return (INT_PTR) TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (INT_PTR) FALSE;
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) {
|
||||
_onexit(Cleanup);
|
||||
// Retrieve install location
|
||||
TCHAR installPath[MAX_PATH];
|
||||
DWORD dwSize = MAX_PATH;
|
||||
memset(installPath, 0, dwSize);
|
||||
Language *lang = NULL;
|
||||
if (MsiLocateComponent(L"{E39EABEF-A7EB-4EAF-AD3E-A1254450BBE1}", installPath, &dwSize) >= 0 && wcslen(installPath) > 0) {
|
||||
PathRemoveFileSpec(installPath);
|
||||
lang = GetPreferredLang(installPath);
|
||||
}
|
||||
if (lang == NULL) {
|
||||
INT_PTR nResult = DialogBox(hInstance, MAKEINTRESOURCE(IDD_LANG_DIALOG), NULL, LangDialog);
|
||||
if (nResult == IDCANCEL) {
|
||||
return 0;
|
||||
}
|
||||
lang = (Language *) nResult;
|
||||
}
|
||||
TCHAR randFile[32];
|
||||
if (!GetWindowsDirectory(msiFile, sizeof(msiFile)) || !PathAppend(msiFile, L"Temp"))
|
||||
return 1;
|
||||
if (!RandomString(randFile))
|
||||
return 1;
|
||||
if (!PathAppend(msiFile, randFile))
|
||||
return 1;
|
||||
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_MSI), RT_RCDATA);
|
||||
if (hRes == NULL) {
|
||||
return 1;
|
||||
#ifdef VERSION
|
||||
installed = wcscmp(VERSION, product.version) == 0;
|
||||
showDlg = !product.path[0] || installed || langIndex < 0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (langIndex < 0)
|
||||
{
|
||||
PZZWSTR langList = NULL;
|
||||
ULONG langNum, langLen = 0;
|
||||
if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langNum, NULL, &langLen))
|
||||
{
|
||||
langList = (PZZWSTR)LocalAlloc(LMEM_FIXED, langLen * sizeof(WCHAR));
|
||||
if (langList)
|
||||
{
|
||||
if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langNum, langList, &langLen) && langNum > 0)
|
||||
{
|
||||
for (size_t i = 0; i < langLen && langList[i] != L'\0'; i += wcsnlen_s(&langList[i], langLen - i) + 1)
|
||||
{
|
||||
langIndex = MatchLanguageCode(&langList[i]);
|
||||
if (langIndex >= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
LocalFree(langList);
|
||||
}
|
||||
}
|
||||
}
|
||||
HGLOBAL msiData = LoadResource(NULL, hRes);
|
||||
if (msiData == NULL) {
|
||||
|
||||
Language* lang = showDlg ? (Language*)DialogBoxParamW(
|
||||
hInstance, MAKEINTRESOURCE(IDD_LANG_DIALOG),
|
||||
NULL, LanguageDialog, langIndex
|
||||
) : &languages[langIndex];
|
||||
if (lang == NULL)
|
||||
return 0;
|
||||
|
||||
if (!GetWindowsDirectoryW(msiPath, _countof(msiPath)) || !PathAppendW(msiPath, L"Temp"))
|
||||
return 1;
|
||||
}
|
||||
DWORD msiSize = SizeofResource(NULL, hRes);
|
||||
if (msiSize == 0) {
|
||||
GUID guid;
|
||||
if (FAILED(CoCreateGuid(&guid)))
|
||||
return 1;
|
||||
}
|
||||
LPVOID pMsiData = LockResource(msiData);
|
||||
if (pMsiData == NULL) {
|
||||
WCHAR identifier[40];
|
||||
if (StringFromGUID2(&guid, identifier, _countof(identifier)) == 0 || !PathAppendW(msiPath, identifier))
|
||||
return 1;
|
||||
}
|
||||
SECURITY_ATTRIBUTES security_attributes = {.nLength = sizeof(security_attributes)};
|
||||
hFile = CreateFile(msiFile, GENERIC_WRITE | DELETE, 0, &security_attributes, CREATE_NEW,
|
||||
FILE_ATTRIBUTE_TEMPORARY, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
|
||||
HRSRC hRes = FindResourceW(NULL, MAKEINTRESOURCE(IDR_MSI), RT_RCDATA);
|
||||
if (hRes == NULL)
|
||||
return 1;
|
||||
}
|
||||
HGLOBAL hResData = LoadResource(NULL, hRes);
|
||||
if (hResData == NULL)
|
||||
return 1;
|
||||
DWORD resSize = SizeofResource(NULL, hRes);
|
||||
if (resSize == 0)
|
||||
return 1;
|
||||
LPVOID pResData = LockResource(hResData);
|
||||
if (pResData == NULL)
|
||||
return 1;
|
||||
|
||||
SECURITY_ATTRIBUTES sa = { .nLength = sizeof(sa) };
|
||||
if (!ConvertStringSecurityDescriptorToSecurityDescriptorA("O:BAD:PAI(A;;FA;;;BA)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL))
|
||||
return 1;
|
||||
msiFile = CreateFileW(msiPath, GENERIC_WRITE, 0, &sa, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, NULL);
|
||||
if (sa.lpSecurityDescriptor)
|
||||
LocalFree(sa.lpSecurityDescriptor);
|
||||
if (msiFile == INVALID_HANDLE_VALUE)
|
||||
return 1;
|
||||
_onexit(cleanup);
|
||||
DWORD bytesWritten;
|
||||
if (!WriteFile(hFile, pMsiData, msiSize, &bytesWritten, NULL) || bytesWritten != msiSize) {
|
||||
CloseHandle(hFile);
|
||||
BOOL ok = WriteFile(msiFile, pResData, resSize, &bytesWritten, NULL);
|
||||
CloseHandle(msiFile);
|
||||
msiFile = INVALID_HANDLE_VALUE;
|
||||
if (!ok || bytesWritten != resSize)
|
||||
return 1;
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
|
||||
MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
|
||||
TCHAR cmd[500];
|
||||
wsprintf(cmd, L"ProductLanguage=%d PREVINSTALLFOLDER=\"%s\"", lang->id, installPath);
|
||||
return MsiInstallProduct(msiFile, cmd);
|
||||
#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)
|
||||
return 1;
|
||||
return MsiInstallProductW(msiPath, cmd);
|
||||
}
|
||||
|
@ -7,7 +7,10 @@ import (
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
)
|
||||
|
||||
const DefaultAppFile = "app.json"
|
||||
const (
|
||||
DefaultAppFile = "app.json"
|
||||
LangFile = "lang.config"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Lang string `json:"lang,omitempty"`
|
||||
@ -50,12 +53,18 @@ func (dv *DefaultValue) AsClientConfig() ClientCommon {
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalAppConf(path string, dst *App) error {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
func UnmarshalAppConf(path string, dst *App) (lang *string, err error) {
|
||||
b, err := os.ReadFile(LangFile)
|
||||
if err == nil {
|
||||
s := string(b)
|
||||
lang = &s
|
||||
}
|
||||
return json.Unmarshal(b, dst)
|
||||
b, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(b, dst)
|
||||
return
|
||||
}
|
||||
|
||||
func (conf *App) Save(path string) error {
|
||||
|
@ -35,11 +35,19 @@ func TestUnmarshalAppConfFromIni(t *testing.T) {
|
||||
LegacyFormat: true,
|
||||
},
|
||||
}
|
||||
expectedLang := "en-US"
|
||||
if err := os.WriteFile(LangFile, []byte(expectedLang), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var actual App
|
||||
if err := UnmarshalAppConf(DefaultAppFile, &actual); err != nil {
|
||||
lang, err := UnmarshalAppConf(DefaultAppFile, &actual)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Expected: %v, got: %v", expected, actual)
|
||||
}
|
||||
if lang == nil || *lang != expectedLang {
|
||||
t.Errorf("Expected: %v, got: %v", expectedLang, lang)
|
||||
}
|
||||
}
|
||||
|
15
ui/conf.go
15
ui/conf.go
@ -9,6 +9,7 @@ import (
|
||||
"github.com/lxn/walk"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/koho/frpmgr/i18n"
|
||||
"github.com/koho/frpmgr/pkg/config"
|
||||
"github.com/koho/frpmgr/pkg/consts"
|
||||
"github.com/koho/frpmgr/pkg/util"
|
||||
@ -101,8 +102,18 @@ var (
|
||||
)
|
||||
|
||||
func loadAllConfs() ([]*Conf, error) {
|
||||
_ = config.UnmarshalAppConf(config.DefaultAppFile, &appConf)
|
||||
// Find all config files in `profiles` directory
|
||||
// Load and migrate application configuration.
|
||||
if lang, _ := config.UnmarshalAppConf(config.DefaultAppFile, &appConf); lang != nil {
|
||||
if _, ok := i18n.IDToName[*lang]; ok {
|
||||
appConf.Lang = *lang
|
||||
if saveAppConfig() == nil {
|
||||
os.Remove(config.LangFile)
|
||||
}
|
||||
} else {
|
||||
os.Remove(config.LangFile)
|
||||
}
|
||||
}
|
||||
// Find all config files in `profiles` directory.
|
||||
files, err := filepath.Glob(PathOfConf("*.conf"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
Reference in New Issue
Block a user