diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h index 8b957bd5b954..ecfec2b0f353 100644 --- a/src/common/utils/modulesRegistry.h +++ b/src/common/utils/modulesRegistry.h @@ -8,6 +8,9 @@ namespace fs = std::filesystem; +// Import path normalization function from registry namespace +using registry::shellex::normalizePathForComRegistration; + namespace NonLocalizable { const static wchar_t* MONACO_LANGUAGES_FILE_NAME = L"Assets\\Monaco\\monaco_languages.json"; @@ -240,10 +243,11 @@ inline registry::ChangeSet getRegistryPreviewSetDefaultAppChangeSet(const std::w std::wstring registryKeyPrefix = L"Software\\Classes\\"; std::wstring appPath = installationDir + L"\\WinUI3Apps\\PowerToys.RegistryPreview.exe"; - std::wstring command = appPath + L" \"----ms-protocol:ms-encodedlaunch:App?ContractId=Windows.File&Verb=open&File=%1\""; + std::wstring normalizedAppPath = normalizePathForComRegistration(appPath); + std::wstring command = normalizedAppPath + L" \"----ms-protocol:ms-encodedlaunch:App?ContractId=Windows.File&Verb=open&File=%1\""; changes.push_back({ scope, registryKeyPrefix + fullAppName + L"\\" + L"Application", L"ApplicationName", appName }); - changes.push_back({ scope, registryKeyPrefix + fullAppName + L"\\" + L"DefaultIcon", std::nullopt, appPath }); + changes.push_back({ scope, registryKeyPrefix + fullAppName + L"\\" + L"DefaultIcon", std::nullopt, normalizedAppPath }); changes.push_back({ scope, registryKeyPrefix + fullAppName + L"\\" + L"shell\\open\\command", std::nullopt, command }); changes.push_back({ scope, registryKeyPrefix + L".reg\\OpenWithProgIDs", fullAppName, L"" }); @@ -257,13 +261,15 @@ inline registry::ChangeSet getRegistryPreviewChangeSet(const std::wstring instal using vec_t = std::vector; vec_t changes; - std::wstring command = installationDir; - command.append(L"\\WinUI3Apps\\PowerToys.RegistryPreview.exe \"%1\""); + std::wstring exePath = installationDir + L"\\WinUI3Apps\\PowerToys.RegistryPreview.exe"; + std::wstring normalizedExePath = normalizePathForComRegistration(exePath); + std::wstring command = normalizedExePath + L" \"%1\""; changes.push_back({ scope, L"Software\\Classes\\regfile\\shell\\preview\\command", std::nullopt, command }); std::wstring icon_path = installationDir; icon_path.append(L"\\WinUI3Apps\\Assets\\RegistryPreview\\RegistryPreview.ico"); - changes.push_back({ scope, L"Software\\Classes\\regfile\\shell\\preview", L"icon", icon_path }); + std::wstring normalizedIconPath = normalizePathForComRegistration(icon_path); + changes.push_back({ scope, L"Software\\Classes\\regfile\\shell\\preview", L"icon", normalizedIconPath }); return { changes }; } diff --git a/src/common/utils/registry.h b/src/common/utils/registry.h index 059589352d20..0c15b5257848 100644 --- a/src/common/utils/registry.h +++ b/src/common/utils/registry.h @@ -387,6 +387,37 @@ namespace registry thumbnail }; + // Helper function to properly quote DLL paths for COM registration + inline std::wstring quotePathIfNeeded(const std::wstring& path) + { + // If path contains spaces, it should be quoted for proper COM registration + if (path.find(L' ') != std::wstring::npos) + { + return L"\"" + path + L"\""; + } + return path; + } + + // Helper function to normalize and resolve DLL paths for COM registration + inline std::wstring normalizePathForComRegistration(const std::wstring& path) + { + try + { + // Normalize the path to handle any relative components or inconsistencies + auto normalizedPath = std::filesystem::canonical(std::filesystem::path(path)); + auto pathStr = normalizedPath.wstring(); + + // Quote if needed and return + return quotePathIfNeeded(pathStr); + } + catch (const std::filesystem::filesystem_error&) + { + // If canonicalization fails (file doesn't exist), fall back to the original path + // This can happen during installation when files aren't copied yet + return quotePathIfNeeded(path); + } + } + inline registry::ChangeSet generatePreviewHandler(const PreviewHandlerType handlerType, const bool perUser, std::wstring handlerClsid, @@ -426,11 +457,17 @@ namespace registry versionPath += L'\\'; versionPath += powertoysVersion; + // Normalize and quote the DLL path to ensure proper COM registration + std::wstring normalizedPathToHandler = normalizePathForComRegistration(fullPathToHandler); + + // Log the path transformation for debugging + Logger::info(L"Registering preview handler: {} -> {}", fullPathToHandler, normalizedPathToHandler); + using vec_t = std::vector; // TODO: verify that we actually need all of those vec_t changes = { { scope, clsidPath, L"DisplayName", displayName }, { scope, clsidPath, std::nullopt, className }, - { scope, inprocServerPath, std::nullopt, fullPathToHandler }, + { scope, inprocServerPath, std::nullopt, normalizedPathToHandler }, { scope, inprocServerPath, L"Assembly", assemblyKeyValue }, { scope, inprocServerPath, L"Class", className }, { scope, inprocServerPath, L"ThreadingModel", L"Apartment" } }; @@ -467,6 +504,20 @@ namespace registry changes.push_back({ scope, clsidPath, L"AppID", previewHostClsid }); changes.push_back({ scope, previewHandlerListPath, handlerClsid, displayName }); + + // For user-directory installations, add additional AppID configuration to ensure proper COM activation + if (perUser) + { + std::wstring appIdPath = L"Software\\Classes\\AppID\\" + previewHostClsid; + // These settings help ensure the preview handler can be activated properly from different security contexts + changes.push_back({ scope, appIdPath, L"DllSurrogate", L"" }); // Use default surrogate (prevhost.exe) + changes.push_back({ scope, appIdPath, L"AppIDFlags", DWORD(0x4) }); // APPIDREGFLAGS_ACTIVATE_IUSERVER_INDESKTOP + + // Add authentication level to ensure proper security context + changes.push_back({ scope, appIdPath, L"AuthenticationLevel", DWORD(1) }); // RPC_C_AUTHN_LEVEL_NONE + + Logger::info(L"Added per-user AppID configuration for preview handler in: {}", normalizedPathToHandler); + } } return registry::ChangeSet{ .changes = std::move(changes) };