mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-13 01:25:19 +03:00
Compare commits
1 Commits
dependabot
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55c9707639 |
Submodule libs/hbb_common updated: f4ef3bca2d...42af0f0aed
@@ -31,17 +31,17 @@ LExit:
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
// Helper function to safely delete a file or directory using handle-based deletion.
|
||||
// This avoids TOCTOU (Time-Of-Check-Time-Of-Use) race conditions.
|
||||
// Helper function to safely delete a file using handle-based deletion.
|
||||
// Directories are refused after opening the handle.
|
||||
BOOL SafeDeleteItem(LPCWSTR fullPath)
|
||||
{
|
||||
// Open the file/directory with DELETE access and FILE_FLAG_OPEN_REPARSE_POINT
|
||||
// Open the file/directory with delete and attribute-read access plus FILE_FLAG_OPEN_REPARSE_POINT
|
||||
// to prevent following symlinks.
|
||||
// Use shared access to allow deletion even when other processes have the file open.
|
||||
DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT;
|
||||
HANDLE hFile = CreateFileW(
|
||||
fullPath,
|
||||
DELETE,
|
||||
DELETE | FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // Allow shared access
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
@@ -55,6 +55,21 @@ BOOL SafeDeleteItem(LPCWSTR fullPath)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BY_HANDLE_FILE_INFORMATION fileInfo;
|
||||
if (FALSE == GetFileInformationByHandle(hFile, &fileInfo))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "SafeDeleteItem: Failed to inspect '%ls'. Error: %lu", fullPath, GetLastError());
|
||||
CloseHandle(hFile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "SafeDeleteItem: Refusing to delete directory '%ls'.", fullPath);
|
||||
CloseHandle(hFile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Use SetFileInformationByHandle to mark for deletion.
|
||||
// The file will be deleted when the handle is closed.
|
||||
FILE_DISPOSITION_INFO dispInfo;
|
||||
@@ -77,98 +92,74 @@ BOOL SafeDeleteItem(LPCWSTR fullPath)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper function to recursively delete a directory's contents with detailed logging.
|
||||
void RecursiveDelete(LPCWSTR path)
|
||||
BOOL PathEndsWithSlash(LPCWSTR path)
|
||||
{
|
||||
// Ensure the path is not empty or null.
|
||||
if (path == NULL || path[0] == L'\0')
|
||||
size_t length = 0;
|
||||
HRESULT hr = StringCchLengthW(path, MAX_PATH, &length);
|
||||
if (FAILED(hr) || length == 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR last = path[length - 1];
|
||||
return last == L'\\' || last == L'/';
|
||||
}
|
||||
|
||||
void ClearReadOnlyAttribute(LPCWSTR fullPath, DWORD attributes)
|
||||
{
|
||||
if (!(attributes & FILE_ATTRIBUTE_READONLY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra safety: never operate directly on a root path.
|
||||
if (PathIsRootW(path))
|
||||
DWORD writableAttributes = attributes & ~FILE_ATTRIBUTE_READONLY;
|
||||
if (writableAttributes == 0)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: refusing to operate on root path '%ls'.", path);
|
||||
writableAttributes = FILE_ATTRIBUTE_NORMAL;
|
||||
}
|
||||
|
||||
if (SetFileAttributesW(fullPath, writableAttributes))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Runtime cleanup cleared read-only attribute for '%ls'.", fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// MAX_PATH is enough here since the installer should not be using longer paths.
|
||||
// No need to handle extended-length paths (\\?\) in this context.
|
||||
WCHAR searchPath[MAX_PATH];
|
||||
HRESULT hr = StringCchPrintfW(searchPath, MAX_PATH, L"%s\\*", path);
|
||||
if (FAILED(hr)) {
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: Path too long to enumerate: %ls", path);
|
||||
return;
|
||||
WcaLog(LOGMSG_STANDARD, "Runtime cleanup failed to clear read-only attribute for '%ls'. Error: %lu", fullPath, GetLastError());
|
||||
}
|
||||
|
||||
BOOL DeleteRuntimeGeneratedFile(LPCWSTR installFolder, LPCWSTR fileName)
|
||||
{
|
||||
WCHAR fullPath[MAX_PATH];
|
||||
LPCWSTR separator = PathEndsWithSlash(installFolder) ? L"" : L"\\";
|
||||
HRESULT hr = StringCchPrintfW(fullPath, MAX_PATH, L"%s%s%s", installFolder, separator, fileName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Runtime cleanup path is too long for '%ls'.", fileName);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW findData;
|
||||
HANDLE hFind = FindFirstFileW(searchPath, &findData);
|
||||
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
DWORD attributes = GetFileAttributesW(fullPath);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
// This can happen if the directory is empty or doesn't exist, which is not an error in our case.
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: Failed to enumerate directory '%ls'. It may be missing or inaccessible. Error: %lu", path, GetLastError());
|
||||
return;
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Runtime cleanup cannot stat '%ls'. Error: %lu", fullPath, error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
do
|
||||
if (attributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
// Skip '.' and '..' directories.
|
||||
if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// MAX_PATH is enough here since the installer should not be using longer paths.
|
||||
// No need to handle extended-length paths (\\?\) in this context.
|
||||
WCHAR fullPath[MAX_PATH];
|
||||
hr = StringCchPrintfW(fullPath, MAX_PATH, L"%s\\%s", path, findData.cFileName);
|
||||
if (FAILED(hr)) {
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: Path too long for item '%ls' in '%ls', skipping.", findData.cFileName, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Before acting, ensure the read-only attribute is not set.
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
|
||||
{
|
||||
if (FALSE == SetFileAttributesW(fullPath, findData.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: Failed to remove read-only attribute. Error: %lu", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
// Check for reparse points (symlinks/junctions) to prevent directory traversal attacks.
|
||||
// Do not follow reparse points, only remove the link itself.
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: Not recursing into reparse point (symlink/junction), deleting link itself: %ls", fullPath);
|
||||
SafeDeleteItem(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recursively delete directory contents first
|
||||
RecursiveDelete(fullPath);
|
||||
// Then delete the directory itself
|
||||
SafeDeleteItem(fullPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete file using safe handle-based deletion
|
||||
SafeDeleteItem(fullPath);
|
||||
}
|
||||
} while (FindNextFileW(hFind, &findData) != 0);
|
||||
|
||||
DWORD lastError = GetLastError();
|
||||
if (lastError != ERROR_NO_MORE_FILES)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "RecursiveDelete: FindNextFileW failed with error %lu", lastError);
|
||||
WcaLog(LOGMSG_STANDARD, "Runtime cleanup skipped directory '%ls'.", fullPath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
FindClose(hFind);
|
||||
ClearReadOnlyAttribute(fullPath, attributes);
|
||||
WcaLog(LOGMSG_STANDARD, "Runtime cleanup deleting '%ls'.", fullPath);
|
||||
return SafeDeleteItem(fullPath);
|
||||
}
|
||||
|
||||
// See `Package.wxs` for the sequence of this custom action.
|
||||
@@ -178,13 +169,13 @@ void RecursiveDelete(LPCWSTR path)
|
||||
// 2. RemoveExistingProducts
|
||||
// ├─ TerminateProcesses
|
||||
// ├─ TryStopDeleteService
|
||||
// ├─ RemoveInstallFolder - <-- Here
|
||||
// ├─ RemoveRuntimeGeneratedFiles - <-- Here
|
||||
// └─ RemoveFiles
|
||||
// 3. InstallValidate
|
||||
// 4. InstallFiles
|
||||
// 5. InstallExecute
|
||||
// 6. InstallFinalize
|
||||
UINT __stdcall RemoveInstallFolder(
|
||||
UINT __stdcall RemoveRuntimeGeneratedFiles(
|
||||
__in MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
@@ -194,7 +185,7 @@ UINT __stdcall RemoveInstallFolder(
|
||||
LPWSTR pwz = NULL;
|
||||
LPWSTR pwzData = NULL;
|
||||
|
||||
hr = WcaInitialize(hInstall, "RemoveInstallFolder");
|
||||
hr = WcaInitialize(hInstall, "RemoveRuntimeGeneratedFiles");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
hr = WcaGetProperty(L"CustomActionData", &pwzData);
|
||||
@@ -202,24 +193,20 @@ UINT __stdcall RemoveInstallFolder(
|
||||
|
||||
pwz = pwzData;
|
||||
hr = WcaReadStringFromCaData(&pwz, &installFolder);
|
||||
ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
|
||||
ExitOnFailure(hr, "failed to read install folder from custom action data: %ls", pwz);
|
||||
|
||||
if (installFolder == NULL || installFolder[0] == L'\0') {
|
||||
WcaLog(LOGMSG_STANDARD, "Install folder path is empty, skipping recursive delete.");
|
||||
WcaLog(LOGMSG_STANDARD, "Install folder path is empty, skipping runtime cleanup.");
|
||||
goto LExit;
|
||||
}
|
||||
|
||||
if (PathIsRootW(installFolder)) {
|
||||
WcaLog(LOGMSG_STANDARD, "Refusing to recursively delete root folder '%ls'.", installFolder);
|
||||
WcaLog(LOGMSG_STANDARD, "Refusing runtime cleanup in root folder '%ls'.", installFolder);
|
||||
goto LExit;
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Attempting to recursively delete contents of install folder: %ls", installFolder);
|
||||
|
||||
RecursiveDelete(installFolder);
|
||||
|
||||
// The standard MSI 'RemoveFolders' action will take care of removing the (now empty) directories.
|
||||
// We don't need to call RemoveDirectoryW on installFolder itself, as it might still be in use by the installer.
|
||||
WcaLog(LOGMSG_STANDARD, "Removing runtime-generated files from install folder: %ls", installFolder);
|
||||
DeleteRuntimeGeneratedFile(installFolder, L"RuntimeBroker_rustdesk.exe");
|
||||
|
||||
LExit:
|
||||
ReleaseStr(pwzData);
|
||||
|
||||
@@ -2,7 +2,7 @@ LIBRARY "CustomActions"
|
||||
|
||||
EXPORTS
|
||||
CustomActionHello
|
||||
RemoveInstallFolder
|
||||
RemoveRuntimeGeneratedFiles
|
||||
TerminateProcesses
|
||||
AddFirewallRules
|
||||
SetPropertyIsServiceRunning
|
||||
|
||||
@@ -16,8 +16,15 @@
|
||||
<!-- If a command line value was stored, restore it after the registry search has been performed -->
|
||||
<SetProperty Action="RestoreSavedInstallFolderValue" Id="INSTALLFOLDER" Value="[SavedInstallFolderCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedInstallFolderCmdLineValue" />
|
||||
|
||||
<!-- If a command line value or registry value was set, update the main properties with the value -->
|
||||
<SetProperty Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER" />
|
||||
<!-- Normalize INSTALLFOLDER from the command line or registry before assigning INSTALLFOLDER_INNER. -->
|
||||
<!-- Case 1: already ends with \$(var.Product)\, keep it unchanged. -->
|
||||
<SetProperty Action="SetInstallFolderInnerFromProductDir" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND INSTALLFOLDER ~>> "\$(var.Product)\"" />
|
||||
<!-- Case 2: already ends with \$(var.Product) but has no trailing slash, add the slash. -->
|
||||
<SetProperty Action="SetInstallFolderInnerFromProductDirNoSlash" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]\" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND INSTALLFOLDER ~>> "\$(var.Product)"" />
|
||||
<!-- Case 3: ends with a slash but not \$(var.Product)\, append $(var.Product)\. -->
|
||||
<SetProperty Action="SetInstallFolderInnerAppendProduct" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]$(var.Product)\" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND INSTALLFOLDER ~>> "\" AND NOT (INSTALLFOLDER ~>> "\$(var.Product)\" OR INSTALLFOLDER ~>> "\$(var.Product)")" />
|
||||
<!-- Case 4: has no trailing slash and does not end with \$(var.Product), append \$(var.Product)\. -->
|
||||
<SetProperty Action="SetInstallFolderInnerAppendSlashProduct" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]\$(var.Product)\" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND NOT INSTALLFOLDER ~>> "\" AND NOT (INSTALLFOLDER ~>> "\$(var.Product)\" OR INSTALLFOLDER ~>> "\$(var.Product)")" />
|
||||
|
||||
<!-- INSTALLFOLDER_INNER is defined for compatibility with previous versions of the installer. -->
|
||||
<!-- Because we need to use INSTALLFOLDER as the command line argument. -->
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<CustomAction Id="RemoveInstallFolder.SetParam" Return="check" Property="RemoveInstallFolder" Value="[INSTALLFOLDER_INNER]" />
|
||||
<CustomAction Id="RemoveRuntimeGeneratedFiles.SetParam" Return="check" Property="RemoveRuntimeGeneratedFiles" Value="[INSTALLFOLDER_INNER]" />
|
||||
<CustomAction Id="AddFirewallRules.SetParam" Return="check" Property="AddFirewallRules" Value="1[INSTALLFOLDER_INNER]$(var.Product).exe" />
|
||||
<CustomAction Id="RemoveFirewallRules.SetParam" Return="check" Property="RemoveFirewallRules" Value="0[INSTALLFOLDER_INNER]$(var.Product).exe" />
|
||||
<CustomAction Id="CreateStartService.SetParam" Return="check" Property="CreateStartService" Value="$(var.Product);"[INSTALLFOLDER_INNER]$(var.Product).exe" --service" />
|
||||
@@ -77,21 +77,21 @@
|
||||
|
||||
<Custom Action="AddRegSoftwareSASGeneration" Before="InstallFinalize" Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE) AND (NOT CC_CONNECTION_TYPE="outgoing")"/>
|
||||
|
||||
<Custom Action="RemoveInstallFolder" Before="RemoveFiles"/>
|
||||
<Custom Action="RemoveInstallFolder.SetParam" Before="RemoveInstallFolder"/>
|
||||
<Custom Action="TryStopDeleteService" Before="RemoveInstallFolder.SetParam" />
|
||||
<Custom Action="RemoveRuntimeGeneratedFiles" Before="RemoveFiles" Condition="Installed AND (REMOVE="ALL" OR UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="RemoveRuntimeGeneratedFiles.SetParam" Before="RemoveRuntimeGeneratedFiles" Condition="Installed AND (REMOVE="ALL" OR UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="TryStopDeleteService" Before="RemoveRuntimeGeneratedFiles.SetParam" />
|
||||
<Custom Action="TryStopDeleteService.SetParam" Before="TryStopDeleteService" />
|
||||
|
||||
<Custom Action="RemoveFirewallRules" Before="RemoveFiles"/>
|
||||
<Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/>
|
||||
|
||||
<Custom Action="UninstallPrinter" Before="RemoveInstallFolder" Condition="VersionNT >= 603" />
|
||||
<Custom Action="UninstallPrinter" Before="RemoveRuntimeGeneratedFiles" Condition="VersionNT >= 603" />
|
||||
|
||||
<Custom Action="TerminateProcesses" Before="RemoveInstallFolder"/>
|
||||
<Custom Action="TerminateProcesses" Before="RemoveRuntimeGeneratedFiles"/>
|
||||
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
|
||||
<Custom Action="TerminateBrokers" Before="RemoveInstallFolder"/>
|
||||
<Custom Action="TerminateBrokers" Before="RemoveRuntimeGeneratedFiles"/>
|
||||
<Custom Action="TerminateBrokers.SetParam" Before="TerminateBrokers"/>
|
||||
<Custom Action="RemoveAmyuniIdd" Before="RemoveInstallFolder"/>
|
||||
<Custom Action="RemoveAmyuniIdd" Before="RemoveRuntimeGeneratedFiles"/>
|
||||
<Custom Action="RemoveAmyuniIdd.SetParam" Before="RemoveAmyuniIdd"/>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).dll" />
|
||||
|
||||
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveInstallFolder" DllEntry="RemoveInstallFolder" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveRuntimeGeneratedFiles" DllEntry="RemoveRuntimeGeneratedFiles" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TerminateProcesses" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TerminateBrokers" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="AddFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
|
||||
@@ -23,12 +23,13 @@ Patch dialog sequence:
|
||||
-->
|
||||
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<?include ../Includes.wxi?>
|
||||
<?foreach WIXUIARCH in X86;X64;A64 ?>
|
||||
<Fragment>
|
||||
<UI Id="UI_MyInstallDialog_$(WIXUIARCH)">
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Print" Event="DoAction" Value="WixUIPrintEula_$(WIXUIARCH)" />
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="3" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="2" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="5" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
</UI>
|
||||
|
||||
<UIRef Id="UI_MyInstallDialog" />
|
||||
@@ -64,9 +65,16 @@ Patch dialog sequence:
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="MyInstallDirDlg" Condition="LicenseAccepted = "1"" />
|
||||
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3" Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4" Condition="WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"" />
|
||||
<!-- Normalize INSTALLFOLDER_INNER before SetTargetPath and WixUIValidatePath run. -->
|
||||
<!-- UI case 1: already ends with \$(var.Product) but has no trailing slash, add the slash. -->
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Property="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER_INNER]\" Order="1" Condition="INSTALLFOLDER_INNER AND INSTALLFOLDER_INNER ~>> "\$(var.Product)"" />
|
||||
<!-- UI case 2: ends with a slash but not \$(var.Product)\, append $(var.Product)\. -->
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Property="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER_INNER]$(var.Product)\" Order="2" Condition="INSTALLFOLDER_INNER AND INSTALLFOLDER_INNER ~>> "\" AND NOT (INSTALLFOLDER_INNER ~>> "\$(var.Product)\" OR INSTALLFOLDER_INNER ~>> "\$(var.Product)")" />
|
||||
<!-- UI case 3: has no trailing slash and does not end with \$(var.Product), append \$(var.Product)\. -->
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Property="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER_INNER]\$(var.Product)\" Order="3" Condition="INSTALLFOLDER_INNER AND NOT INSTALLFOLDER_INNER ~>> "\" AND NOT (INSTALLFOLDER_INNER ~>> "\$(var.Product)\" OR INSTALLFOLDER_INNER ~>> "\$(var.Product)")" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="4" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="6" Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="7" Condition="WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MyInstallDirDlg" Order="1" Condition="NOT Installed" />
|
||||
|
||||
Reference in New Issue
Block a user