diff --git a/installer/PowerToysSetup/CmdPal.wxs b/installer/PowerToysSetup/CmdPal.wxs index 89a813979b..c74e27d6c1 100644 --- a/installer/PowerToysSetup/CmdPal.wxs +++ b/installer/PowerToysSetup/CmdPal.wxs @@ -40,7 +40,6 @@ - @@ -50,7 +49,6 @@ - diff --git a/src/common/utils/package.h b/src/common/utils/package.h index a3ce07db22..60bde7ea53 100644 --- a/src/common/utils/package.h +++ b/src/common/utils/package.h @@ -2,11 +2,14 @@ #include +#include #include #include #include #include #include +#include +#include #include #include @@ -15,11 +18,12 @@ #include "../logger/logger.h" #include "../version/version.h" -namespace package { - +namespace package +{ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::Management::Deployment; + using Microsoft::WRL::ComPtr; inline BOOL IsWin11OrGreater() { @@ -46,6 +50,118 @@ namespace package { dwlConditionMask); } + struct PACKAGE_VERSION + { + UINT16 Major; + UINT16 Minor; + UINT16 Build; + UINT16 Revision; + }; + + class ComInitializer + { + public: + explicit ComInitializer(DWORD coInitFlags = COINIT_MULTITHREADED) : + _initialized(false) + { + const HRESULT hr = CoInitializeEx(nullptr, coInitFlags); + _initialized = SUCCEEDED(hr); + } + + ~ComInitializer() + { + if (_initialized) + { + CoUninitialize(); + } + } + + bool Succeeded() const { return _initialized; } + + private: + bool _initialized; + }; + + inline bool GetPackageNameAndVersionFromAppx( + const std::wstring& appxPath, + std::wstring& outName, + PACKAGE_VERSION& outVersion) + { + try + { + ComInitializer comInit; + if (!comInit.Succeeded()) + { + Logger::error(L"COM initialization failed."); + return false; + } + + ComPtr factory; + ComPtr stream; + ComPtr reader; + ComPtr manifest; + ComPtr packageId; + + HRESULT hr = CoCreateInstance(__uuidof(AppxFactory), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)); + if (FAILED(hr)) + return false; + + hr = SHCreateStreamOnFileEx(appxPath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &stream); + if (FAILED(hr)) + return false; + + hr = factory->CreatePackageReader(stream.Get(), &reader); + if (FAILED(hr)) + return false; + + hr = reader->GetManifest(&manifest); + if (FAILED(hr)) + return false; + + hr = manifest->GetPackageId(&packageId); + if (FAILED(hr)) + return false; + + LPWSTR name = nullptr; + hr = packageId->GetName(&name); + if (FAILED(hr)) + return false; + + UINT64 version = 0; + hr = packageId->GetVersion(&version); + if (FAILED(hr)) + return false; + + outName = std::wstring(name); + CoTaskMemFree(name); + + outVersion.Major = static_cast((version >> 48) & 0xFFFF); + outVersion.Minor = static_cast((version >> 32) & 0xFFFF); + outVersion.Build = static_cast((version >> 16) & 0xFFFF); + outVersion.Revision = static_cast(version & 0xFFFF); + + Logger::info(L"Package name: {}, version: {}.{}.{}.{}, appxPath: {}", + outName, + outVersion.Major, + outVersion.Minor, + outVersion.Build, + outVersion.Revision, + appxPath); + + return true; + } + catch (const std::exception& ex) + { + Logger::error(L"Standard exception: {}", winrt::to_hstring(ex.what())); + return false; + } + catch (...) + { + Logger::error(L"Unknown or non-standard exception occurred."); + return false; + } + } + inline std::optional GetRegisteredPackage(std::wstring packageDisplayName, bool checkVersion) { PackageManager packageManager; @@ -229,6 +345,59 @@ namespace package { return matchedFiles; } + inline bool IsPackageSatisfied(const std::wstring& appxPath) + { + std::wstring targetName; + PACKAGE_VERSION targetVersion{}; + + if (!GetPackageNameAndVersionFromAppx(appxPath, targetName, targetVersion)) + { + Logger::error(L"Failed to get package name and version from appx: " + appxPath); + return false; + } + + PackageManager pm; + + for (const auto& package : pm.FindPackagesForUser({})) + { + const auto& id = package.Id(); + if (std::wstring(id.Name()) == targetName) + { + const auto& version = id.Version(); + + if (version.Major > targetVersion.Major || + (version.Major == targetVersion.Major && version.Minor > targetVersion.Minor) || + (version.Major == targetVersion.Major && version.Minor == targetVersion.Minor && version.Build > targetVersion.Build) || + (version.Major == targetVersion.Major && version.Minor == targetVersion.Minor && version.Build == targetVersion.Build && version.Revision >= targetVersion.Revision)) + { + Logger::info( + L"Package {} is already satisfied with version {}.{}.{}.{}; target version {}.{}.{}.{}; appxPath: {}", + id.Name(), + version.Major, + version.Minor, + version.Build, + version.Revision, + targetVersion.Major, + targetVersion.Minor, + targetVersion.Build, + targetVersion.Revision, + appxPath); + return true; + } + } + } + + Logger::info( + L"Package {} is not satisfied. Target version: {}.{}.{}.{}; appxPath: {}", + targetName, + targetVersion.Major, + targetVersion.Minor, + targetVersion.Build, + targetVersion.Revision, + appxPath); + return false; + } + inline bool RegisterPackage(std::wstring pkgPath, std::vector dependencies) { try @@ -247,7 +416,14 @@ namespace package { { try { - uris.Append(Uri(dependency)); + if (IsPackageSatisfied(dependency)) + { + Logger::info(L"Dependency already satisfied: {}", dependency); + } + else + { + uris.Append(Uri(dependency)); + } } catch (const winrt::hresult_error& ex) { @@ -282,7 +458,6 @@ namespace package { { Logger::debug(L"Register {} package started.", pkgPath); } - } catch (std::exception& e) { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj index 9a4061f6bc..e1f891558d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj @@ -13,6 +13,7 @@ true enable enable + true $(CmdPalVersion)