diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 0f5b667603..e7d1b78ce7 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -3,6 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.2.32616.157 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dev", "dev", "{448ED2E5-0B37-4D97-9E6B-8C10A507976A}" + ProjectSection(SolutionItems) = preProject + test\Deployment\UnitTests\DeploymentUnitTests.vcxproj = test\Deployment\UnitTests\DeploymentUnitTests.vcxproj + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{F3659DFF-232D-46E0-967E-61FCC9A1132F}" ProjectSection(SolutionItems) = preProject @@ -746,6 +749,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAppSDK.Test.NetCore" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAppSDK.Test.SampleTests", "test\WindowsAppSDK.Test.SampleTests\WindowsAppSDK.Test.SampleTests.csproj", "{346E099B-45E4-FF40-E63D-8B34915223C1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{E20DA14F-B819-4DE5-865B-CAC91918AA4A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DeploymentUnitTests", "test\Deployment\UnitTests\DeploymentUnitTests.vcxproj", "{F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StoragePickers", "StoragePickers", "{06AA7FD7-36BE-41AC-9008-56919D1612C6}" EndProject Global @@ -2626,6 +2633,22 @@ Global {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x64.Build.0 = Release|x64 {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x86.ActiveCfg = Release|x86 {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x86.Build.0 = Release|x86 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|Any CPU.ActiveCfg = Debug|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|Any CPU.Build.0 = Debug|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|ARM64.Build.0 = Debug|ARM64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|x64.ActiveCfg = Debug|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|x64.Build.0 = Debug|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|x86.ActiveCfg = Debug|Win32 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Debug|x86.Build.0 = Debug|Win32 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|Any CPU.ActiveCfg = Release|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|Any CPU.Build.0 = Release|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|ARM64.ActiveCfg = Release|ARM64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|ARM64.Build.0 = Release|ARM64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|x64.ActiveCfg = Release|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|x64.Build.0 = Release|x64 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|x86.ActiveCfg = Release|Win32 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2851,6 +2874,8 @@ Global {C40AE1D8-FD5F-472E-86B5-DDA5ABA6FF99} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} {BF580B26-B869-3AF1-43EC-D0FD55A49E4D} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} {346E099B-45E4-FF40-E63D-8B34915223C1} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {E20DA14F-B819-4DE5-865B-CAC91918AA4A} = {68E63911-6283-4212-BFFE-3F972AF8F835} + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34} = {E20DA14F-B819-4DE5-865B-CAC91918AA4A} {06AA7FD7-36BE-41AC-9008-56919D1612C6} = {3B706C5C-55E0-4B76-BF59-89E20FE46795} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution @@ -2904,5 +2929,7 @@ Global dev\VersionInfo\VersionInfo.vcxitems*{e3edec7f-a24e-4766-bb1d-6bdfba157c51}*SharedItemsImports = 9 dev\AppNotifications\AppNotificationBuilder\AppNotificationBuilder.vcxitems*{e49329f3-5196-4bba-b5c4-e11ce7efb07a}*SharedItemsImports = 9 test\inc\inc.vcxitems*{e5659a29-fe68-417b-9bc5-613073dd54df}*SharedItemsImports = 4 + dev\Common\Common.vcxitems*{f2a9b8e7-4c62-4d89-9a4f-829f8e2a7e34}*SharedItemsImports = 4 + test\inc\inc.vcxitems*{f2a9b8e7-4c62-4d89-9a4f-829f8e2a7e34}*SharedItemsImports = 4 EndGlobalSection EndGlobal diff --git a/dev/Deployment/Deployment.vcxitems b/dev/Deployment/Deployment.vcxitems index 9636a5884f..bf4bdd099c 100644 --- a/dev/Deployment/Deployment.vcxitems +++ b/dev/Deployment/Deployment.vcxitems @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -27,6 +27,11 @@ + + + + + @@ -34,6 +39,10 @@ + + + + diff --git a/dev/Deployment/DeploymentManager.cpp b/dev/Deployment/DeploymentManager.cpp index 911ed92660..b97e8b0c37 100644 --- a/dev/Deployment/DeploymentManager.cpp +++ b/dev/Deployment/DeploymentManager.cpp @@ -2,14 +2,20 @@ // Licensed under the MIT License. #include +#include #include #include #include -#include #include #include #include #include "WindowsAppRuntime-License.h" +#include "LicenseInstallerProxy.h" +#include "PackageDefinitions.h" +#include "PackageUtilities.h" +#include "Licensing.h" +#include "PackageDeployment.h" +#include "PackageRegistrar.h" using namespace winrt; using namespace winrt::Windows::Foundation; @@ -54,7 +60,7 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::DeploymentResult DeploymentManager::GetStatus() { FAIL_FAST_HR_IF(HRESULT_FROM_WIN32(APPMODEL_ERROR_NO_PACKAGE), !AppModel::Identity::IsPackagedProcess()); - return GetStatus(GetCurrentFrameworkPackageFullName()); + return GetStatus(::WindowsAppRuntime::Deployment::Package::GetCurrentFrameworkPackageFullName()); } winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::DeploymentResult DeploymentManager::Initialize() @@ -66,13 +72,13 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::DeploymentResult DeploymentManager::Initialize( winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::DeploymentInitializeOptions const& deploymentInitializeOptions) { - return Initialize(GetCurrentFrameworkPackageFullName(), deploymentInitializeOptions); + return Initialize(::WindowsAppRuntime::Deployment::Package::GetCurrentFrameworkPackageFullName(), deploymentInitializeOptions); } winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::DeploymentResult DeploymentManager::Repair() { winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::DeploymentInitializeOptions options{}; - return Initialize(GetCurrentFrameworkPackageFullName(), options, true); + return Initialize(::WindowsAppRuntime::Deployment::Package::GetCurrentFrameworkPackageFullName(), options, true); } std::wstring ExtractFormattedVersionTag(const std::wstring& versionTag) @@ -94,7 +100,7 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem { // Get PackageInfo for WinAppSDK framework package std::wstring frameworkPackageFullName{ packageFullName }; - auto frameworkPackageInfo{ GetPackageInfoForPackage(frameworkPackageFullName) }; + auto frameworkPackageInfo{ ::WindowsAppRuntime::Deployment::Package::GetPackageInfoForPackage(frameworkPackageFullName) }; // Should only be called with a framework name that exists. FAIL_FAST_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), frameworkPackageInfo.Count() != 1); @@ -167,7 +173,7 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem // Get target version based on the framework. auto targetPackageVersion{ frameworkPackageInfo.Package(0).packageId.version }; - verifyResult = VerifyPackage(packageFamilyName, targetPackageVersion, package.identifier); + verifyResult = ::WindowsAppRuntime::Deployment::Package::VerifyPackage(packageFamilyName, targetPackageVersion, package.identifier); if (FAILED(verifyResult)) { break; @@ -290,7 +296,8 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem } std::wstring frameworkPackageFullName{ packageFullName }; - auto deployPackagesResult{ Deploy(frameworkPackageFullName, deploymentInitializeOptions.ForceDeployment()) }; + auto deployPackagesResult{ Deploy(frameworkPackageFullName, initializeActivityContext, deploymentInitializeOptions.ForceDeployment()) }; + DeploymentStatus status{}; if (SUCCEEDED(deployPackagesResult)) { @@ -328,349 +335,101 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem return winrt::make(status, deployPackagesResult); } - MddCore::PackageInfo DeploymentManager::GetPackageInfoForPackage(std::wstring const& packageFullName) - { - wil::unique_package_info_reference packageInfoReference; - THROW_IF_WIN32_ERROR(OpenPackageInfoByFullName(packageFullName.c_str(), 0, &packageInfoReference)); - return MddCore::PackageInfo::FromPackageInfoReference(packageInfoReference.get()); - } - - // Borrowed and repurposed from Dynamic Dependencies - std::vector DeploymentManager::FindPackagesByFamily(std::wstring const& packageFamilyName) - { - UINT32 count{}; - UINT32 bufferLength{}; - const auto rc{ FindPackagesByPackageFamily(packageFamilyName.c_str(), PACKAGE_FILTER_HEAD | PACKAGE_FILTER_DIRECT, &count, nullptr, &bufferLength, nullptr, nullptr) }; - if (rc == ERROR_SUCCESS) - { - // The package family has no packages registered to the user - return std::vector(); - } - else if (rc != ERROR_INSUFFICIENT_BUFFER) - { - THROW_WIN32(rc); - } - - auto packageFullNames{ wil::make_unique_cotaskmem(count) }; - auto buffer{ wil::make_unique_cotaskmem(bufferLength) }; - THROW_IF_WIN32_ERROR(FindPackagesByPackageFamily(packageFamilyName.c_str(), PACKAGE_FILTER_HEAD | PACKAGE_FILTER_DIRECT, &count, packageFullNames.get(), &bufferLength, buffer.get(), nullptr)); - - std::vector packageFullNamesList; - for (UINT32 index=0; index < count; ++index) - { - const auto packageFullName{ packageFullNames[index] }; - packageFullNamesList.push_back(std::wstring(packageFullName)); - } - return packageFullNamesList; - } - - HRESULT DeploymentManager::VerifyPackage(const std::wstring& packageFamilyName, const PACKAGE_VERSION targetVersion, - const std::wstring& packageIdentifier) try + // Deploys all of the packages carried by the specified framework. + HRESULT DeploymentManager::Deploy( + const std::wstring& frameworkPackageFullName, + ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, + const bool forceDeployment + ) try { - auto packageFullNames{ FindPackagesByFamily(packageFamilyName) }; - bool match{}; - for (const auto& packageFullName : packageFullNames) - { - auto packagePath{ GetPackagePath(packageFullName) }; - if (packagePath.empty()) - { - continue; - } - - auto packageId{ AppModel::Identity::PackageIdentity::FromPackageFullName(packageFullName.c_str()) }; - if (packageId.Version().Version >= targetVersion.Version) - { - match = true; - if (packageId.Version().Version > targetVersion.Version) - { - g_existingTargetPackagesIfHigherVersion.insert(std::make_pair(packageIdentifier, packageFullName)); - } - break; - } - } - - RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !match); + RETURN_IF_FAILED(InstallLicenses(frameworkPackageFullName, initializeActivityContext)); + RETURN_IF_FAILED(DeployPackages(frameworkPackageFullName, initializeActivityContext, forceDeployment)); return S_OK; } CATCH_RETURN() - // Gets the package path, which is a fast and reliable way to check if the package is - // at least staged on the device, even without package query capabilities. - std::wstring DeploymentManager::GetPackagePath(std::wstring const& packageFullName) - { - UINT32 pathLength{}; - const auto rc{ GetPackagePathByFullName(packageFullName.c_str(), &pathLength, nullptr) }; - if (rc == ERROR_NOT_FOUND) - { - return std::wstring(); - } - else if (rc != ERROR_INSUFFICIENT_BUFFER) - { - THROW_WIN32(rc); - } - - auto path{ wil::make_process_heap_string(nullptr, pathLength) }; - THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName.c_str(), &pathLength, path.get())); - return std::wstring{ path.get() }; - } - - // If useExistingPackageIfHigherVersion == false, Adds the current version package at the passed in path using PackageManager. - // If useExistingPackageIfHigherVersion == true, Registers the higher version package using the passed in path as manifest path and PackageManager. - // This requires the 'packageManagement' or 'runFullTrust' capabilities. - HRESULT DeploymentManager::AddOrRegisterPackage(const std::filesystem::path& path, const bool useExistingPackageIfHigherVersion, const bool forceDeployment) try - { - winrt::Windows::Management::Deployment::PackageManager packageManager; - - const auto options{ forceDeployment ? - winrt::Windows::Management::Deployment::DeploymentOptions::ForceTargetApplicationShutdown : - winrt::Windows::Management::Deployment::DeploymentOptions::None }; - - winrt::Windows::Foundation::IAsyncOperationWithProgress deploymentOperation; - - const auto pathUri { winrt::Windows::Foundation::Uri(path.c_str()) }; - if (useExistingPackageIfHigherVersion) - { - deploymentOperation = packageManager.RegisterPackageAsync(pathUri, nullptr, options); - } - else - { - deploymentOperation = packageManager.AddPackageAsync(pathUri, nullptr, options); - } - deploymentOperation.get(); - const auto deploymentResult{ deploymentOperation.GetResults() }; - HRESULT deploymentOperationHResult{}; - HRESULT deploymentOperationExtendedHResult{}; - - if (deploymentOperation.Status() != winrt::Windows::Foundation::AsyncStatus::Completed) - { - deploymentOperationHResult = static_cast(deploymentOperation.ErrorCode()); - deploymentOperationExtendedHResult = deploymentResult.ExtendedErrorCode(); - - ::WindowsAppRuntime::Deployment::Activity::Context::Get().SetDeploymentErrorInfo( - deploymentOperationExtendedHResult, - deploymentResult.ErrorText().c_str(), - deploymentResult.ActivityId()); - } - - // If deploymentOperationHResult indicates success, take that, ignore deploymentOperationExtendedHResult. - // Otherwise, return deploymentOperationExtendedHResult if there is an error in it, if not, return deploymentOperationHResult. - return SUCCEEDED(deploymentOperationHResult) ? deploymentOperationHResult : - (FAILED(deploymentOperationExtendedHResult) ? deploymentOperationExtendedHResult : deploymentOperationHResult); - } - CATCH_RETURN() - - std::wstring DeploymentManager::GenerateDeploymentAgentPath() + HRESULT DeploymentManager::InstallLicenses(const std::wstring& frameworkPackageFullName, ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext) { - // Calculate the path to the restart agent as being in the same directory as the current module. - wil::unique_hmodule module; - THROW_IF_WIN32_BOOL_FALSE(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(DeploymentManager::GenerateDeploymentAgentPath), &module)); - - std::filesystem::path modulePath{ wil::GetModuleFileNameW(module.get()) }; - return modulePath.parent_path() / c_deploymentAgentFilename; - } + auto licenseInstaller{ ::Microsoft::Windows::ApplicationModel::Licensing::Installer{} }; + auto licenseInstallerProxy{ ::WindowsAppRuntime::Deployment::Licensing::LicenseInstallerProxy{ licenseInstaller } }; - /// @warning This function is ONLY for processes with package identity. It's the caller's responsibility to ensure this. - HRESULT DeploymentManager::AddOrRegisterPackageInBreakAwayProcess(const std::filesystem::path& path, const bool useExistingPackageIfHigherVersion, const bool forceDeployment) try - { - auto exePath{ GenerateDeploymentAgentPath() }; - auto activityId{ winrt::to_hstring(*::WindowsAppRuntime::Deployment::Activity::Context::Get().GetActivity().Id()) }; - - // \deploymentagent.exe - auto cmdLine{ wil::str_printf(L"\"%s\" %u \"%s\" %u %s", exePath.c_str(), (useExistingPackageIfHigherVersion ? 1 : 0), path.c_str(), (forceDeployment ? 1 : 0), activityId.c_str()) }; - - SIZE_T attributeListSize{}; - auto attributeCount{ 1 }; - - // attributeCount is always >0 so we need to allocate a buffer. Call InitializeProcThreadAttributeList() - // to determine the size needed so we always expect ERROR_INSUFFICIENT_BUFFER. - THROW_HR_IF(E_UNEXPECTED, !!InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &attributeListSize)); - const auto lastError{ GetLastError() }; - THROW_HR_IF(HRESULT_FROM_WIN32(lastError), lastError != ERROR_INSUFFICIENT_BUFFER); - wistd::unique_ptr attributeListBuffer{ new BYTE[attributeListSize] }; - auto attributeList{ reinterpret_cast(attributeListBuffer.get()) }; - THROW_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(attributeList, attributeCount, 0, &attributeListSize)); - auto freeAttributeList{ wil::scope_exit([&] { DeleteProcThreadAttributeList(attributeList); }) }; - - // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute - // The process being created will create any child processes outside of the desktop app runtime environment. - // This behavior is the default for processes for which no policy has been set - DWORD policy{ PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_ENABLE_PROCESS_TREE }; - THROW_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_DESKTOP_APP_POLICY, &policy, sizeof(policy), nullptr, nullptr)); - - STARTUPINFOEX info{}; - info.StartupInfo.cb = sizeof(info); - info.lpAttributeList = attributeList; - - wil::unique_process_information processInfo; - THROW_IF_WIN32_BOOL_FALSE(CreateProcess(nullptr, cmdLine.get(), nullptr, nullptr, FALSE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &info.StartupInfo, &processInfo)); - - // This API is designed to only return to the caller on failure, otherwise block until process termination. - // Wait for the agent to exit. If the agent succeeds, it will terminate this process. If the agent fails, - // it can exit or crash. This API will be able to detect the failure and return. - wil::handle_wait(processInfo.hProcess); - - DWORD processExitCode{}; - THROW_IF_WIN32_BOOL_FALSE_MSG(GetExitCodeProcess(processInfo.hProcess, &processExitCode), "CmdLine: %ls, processExitCode: %u", cmdLine.get(), processExitCode); - RETURN_IF_FAILED_MSG(HRESULT_FROM_WIN32(processExitCode), "DeploymentAgent exitcode:0x%X", processExitCode); - return S_OK; - } - CATCH_RETURN() - - // Deploys all of the packages carried by the specified framework. - HRESULT DeploymentManager::Deploy(const std::wstring& frameworkPackageFullName, const bool forceDeployment) try - { - RETURN_IF_FAILED(InstallLicenses(frameworkPackageFullName)); - RETURN_IF_FAILED(DeployPackages(frameworkPackageFullName, forceDeployment)); - return S_OK; - } - CATCH_RETURN() + initializeActivityContext.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::GetLicensePath); - HRESULT DeploymentManager::InstallLicenses(const std::wstring& frameworkPackageFullName) - { - ::WindowsAppRuntime::Deployment::Activity::Context::Get().Reset(); - ::WindowsAppRuntime::Deployment::Activity::Context::Get().SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::GetLicensePath); + auto packagePath = ::WindowsAppRuntime::Deployment::Package::GetPackagePath(frameworkPackageFullName); // Build path for licenses - auto licensePath{ std::filesystem::path(GetPackagePath(frameworkPackageFullName)) }; + auto licensePath{ std::filesystem::path(packagePath) }; licensePath /= WINDOWSAPPRUNTIME_FRAMEWORK_PACKAGE_FOLDER; auto licenseFilespec{ licensePath }; licenseFilespec /= L"*_license.xml"; - ::WindowsAppRuntime::Deployment::Activity::Context::Get().SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::InstallLicense); - // Deploy the licenses (if any) - ::Microsoft::Windows::ApplicationModel::Licensing::Installer licenseInstaller; - WIN32_FIND_DATA findFileData{}; - wil::unique_hfind hfind{ FindFirstFileW(licenseFilespec.c_str(), &findFileData) }; - if (!hfind) - { - const auto lastError{ GetLastError() }; - RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(lastError), (lastError != ERROR_FILE_NOT_FOUND) && (lastError != ERROR_PATH_NOT_FOUND), - "FindFirstFile:%ls", licenseFilespec.c_str()); - return S_OK; - } - for (;;) - { - // Install the license file - auto licenseFilename{ licensePath }; - licenseFilename /= findFileData.cFileName; - - ::WindowsAppRuntime::Deployment::Activity::Context::Get().SetCurrentResourceId(licenseFilename); - - RETURN_IF_FAILED_MSG(licenseInstaller.InstallLicenseFile(licenseFilename.c_str()), - "LicenseFile:%ls", licenseFilename.c_str()); - - // Next! (if any) - if (!FindNextFileW(hfind.get(), &findFileData)) - { - const auto lastError{ GetLastError() }; - RETURN_HR_IF(HRESULT_FROM_WIN32(lastError), lastError != ERROR_NO_MORE_FILES); - break; - } - } + std::vector licenseFiles; + RETURN_IF_FAILED(::WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(licenseFilespec, licenseFiles)); + RETURN_IF_FAILED(::WindowsAppRuntime::Deployment::Licensing::InstallLicenses(licenseFiles, licensePath, licenseInstallerProxy, initializeActivityContext)); return S_OK; } - HRESULT DeploymentManager::DeployPackages(const std::wstring& frameworkPackageFullName, const bool forceDeployment) + HRESULT DeploymentManager::DeployPackages(const std::wstring& frameworkPackageFullName, ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, const bool forceDeployment) { - auto initializeActivity{ ::WindowsAppRuntime::Deployment::Activity::Context::Get() }; - initializeActivity.Reset(); + const auto frameworkPackagePath = std::filesystem::path(::WindowsAppRuntime::Deployment::Package::GetPackagePath(frameworkPackageFullName)); + + std::map existingTargetPackagesIfHigherVersion{}; + for (const auto& [packageIdentifier, packageFullName] : g_existingTargetPackagesIfHigherVersion) + { + const auto packagePath{ std::filesystem::path(::WindowsAppRuntime::Deployment::Package::GetPackagePath(packageFullName)) }; + existingTargetPackagesIfHigherVersion[packageIdentifier] = { packageFullName, packagePath }; + } - initializeActivity.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::GetPackagePath); - const auto frameworkPath{ std::filesystem::path(GetPackagePath(frameworkPackageFullName)) }; + const auto deploymentPackageArguments = ::WindowsAppRuntime::Deployment::PackageDeployment::GetDeploymentPackageArguments(frameworkPackagePath, initializeActivityContext, existingTargetPackagesIfHigherVersion); - for (auto package : c_targetPackages) + for (auto package : deploymentPackageArguments) { - auto isSingleton{ CompareStringOrdinal(package.identifier.c_str(), -1, WINDOWSAPPRUNTIME_PACKAGE_SUBTYPENAME_SINGLETON, -1, TRUE) == CSTR_EQUAL }; - initializeActivity.Reset(); - initializeActivity.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::AddPackage); - initializeActivity.SetCurrentResourceId(package.identifier); - - std::filesystem::path packagePath{}; - - // If there is exisiting target package version higher than that of framework current version package, then re-register it. - // Otherwise, deploy the target msix package from the current framework package version. - auto existingPackageIfHigherVersion = g_existingTargetPackagesIfHigherVersion.find(package.identifier); - auto useExistingPackageIfHigherVersion { existingPackageIfHigherVersion != g_existingTargetPackagesIfHigherVersion.end() }; - if (useExistingPackageIfHigherVersion) + initializeActivityContext.Reset(); + initializeActivityContext.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::AddPackage); + initializeActivityContext.SetCurrentResourceId(package.identifier); + if (package.useExistingPackageIfHigherVersion) { - initializeActivity.SetUseExistingPackageIfHigherVersion(); - packagePath = std::filesystem::path(GetPackagePath(existingPackageIfHigherVersion->second)); - packagePath /= WINDOWSAPPRUNTIME_PACKAGE_MANIFEST_FILE; - } - else - { - packagePath = frameworkPath; - packagePath /= WINDOWSAPPRUNTIME_FRAMEWORK_PACKAGE_FOLDER; - packagePath /= package.identifier + WINDOWSAPPRUNTIME_FRAMEWORK_PACKAGE_FILE_EXTENSION; + initializeActivityContext.SetUseExistingPackageIfHigherVersion(); } // If the current application has runFullTrust capability, then Deploy the target package in a Breakaway process. // Otherwise, call PackageManager API to deploy the target package. // The Singleton package will always set true for forceDeployment and the running process will be terminated to update the package. - if (initializeActivity.GetIsFullTrustPackage()) + if (initializeActivityContext.GetIsFullTrustPackage()) { - - RETURN_IF_FAILED(AddOrRegisterPackageInBreakAwayProcess(packagePath, useExistingPackageIfHigherVersion, forceDeployment || isSingleton)); + RETURN_IF_FAILED(::WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackageInBreakAwayProcess( + package.packagePath, + package.useExistingPackageIfHigherVersion, + forceDeployment || package.isSingleton, + initializeActivityContext, + ::WindowsAppRuntime::Deployment::PackageRegistrar::GenerateDeploymentAgentPath() + )); } else { - RETURN_IF_FAILED(AddOrRegisterPackage(packagePath, useExistingPackageIfHigherVersion, forceDeployment || isSingleton)); + auto packageManager = winrt::Windows::Management::Deployment::PackageManager{}; + RETURN_IF_FAILED(::WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackage( + package.packagePath, + package.useExistingPackageIfHigherVersion, + forceDeployment || package.isSingleton, + packageManager, + initializeActivityContext + )); } - - // Always restart Push Notifications Long Running Platform when Singleton package is processed and installed. - if (isSingleton) - { - // wil callback is set up to log telemetry events for Push Notifications LRP. - LOG_IF_FAILED_MSG(StartupNotificationsLongRunningPlatform(), "Restarting Notifications LRP failed in all 3 attempts."); - } - } - - return S_OK; - } - - hstring DeploymentManager::GetCurrentFrameworkPackageFullName() - { - // Get current package identity. - WCHAR packageFullName[PACKAGE_FULL_NAME_MAX_LENGTH + 1]{}; - UINT32 packageFullNameLength{ static_cast(ARRAYSIZE(packageFullName)) }; - const auto rc{ ::GetCurrentPackageFullName(&packageFullNameLength, packageFullName) }; - if (rc != ERROR_SUCCESS) - { - THROW_WIN32(rc); } - // Get the PackageInfo of current package and it's dependency packages - std::wstring currentPackageFullName{ packageFullName }; - auto currentPackageInfo{ GetPackageInfoForPackage(currentPackageFullName) }; - - // Index starts at 1 since the first package is the current package and we are interested in - // dependency packages only. - for (size_t i = 0; i < currentPackageInfo.Count(); ++i) + // Always restart Push Notifications Long Running Platform when Singleton package is processed and installed. + for (const auto& package : deploymentPackageArguments) { - auto dependencyPackage{ currentPackageInfo.Package(i) }; - - // Verify PublisherId matches. - if (CompareStringOrdinal(dependencyPackage.packageId.publisherId, -1, WINDOWSAPPRUNTIME_PACKAGE_PUBLISHERID, -1, TRUE) != CSTR_EQUAL) + if (package.isSingleton) { - continue; - } - - // Verify that the WindowsAppRuntime prefix identifier is in the name. - // This should also be the beginning of the name, so its find position is expected to be 0. - std::wstring dependencyPackageName{ dependencyPackage.packageId.name }; - if (dependencyPackageName.find(WINDOWSAPPRUNTIME_PACKAGE_NAME_PREFIX) != 0) - { - continue; + // WIL callback is set up to log telemetry events for Push Notifications LRP. + std::ignore = LOG_IF_FAILED(StartupNotificationsLongRunningPlatform()); + break; } - - // On WindowsAppSDK 1.1+, there is no need to check and rule out Main, Singleton and DDLM Package identifiers as their names don't have a overlap with WINDOWSAPPRUNTIME_PACKAGE_NAME_PREFIX. - - return hstring(dependencyPackage.packageFullName); } - - THROW_WIN32(ERROR_NOT_FOUND); + return S_OK; } HRESULT Initialize_Log( diff --git a/dev/Deployment/DeploymentManager.h b/dev/Deployment/DeploymentManager.h index f3310ab286..b27a6b98a8 100644 --- a/dev/Deployment/DeploymentManager.h +++ b/dev/Deployment/DeploymentManager.h @@ -1,7 +1,6 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #pragma once -#include #include #include #include "Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManager.g.h" @@ -10,8 +9,6 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implementation { - static PCWSTR c_deploymentAgentFilename{ L"DeploymentAgent.exe" }; - struct DeploymentManager { DeploymentManager() = default; @@ -27,26 +24,19 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem WindowsAppRuntime::DeploymentInitializeOptions const& deploymentInitializeOptions, bool isRepair = false); - private: static WindowsAppRuntime::DeploymentResult _Initialize( ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, hstring const& packageFullName, WindowsAppRuntime::DeploymentInitializeOptions const& deploymentInitializeOptions, bool isRepair); - private: - static MddCore::PackageInfo GetPackageInfoForPackage(std::wstring const& packageFullName); - static std::vector FindPackagesByFamily(std::wstring const& packageFamilyName); - static HRESULT VerifyPackage(const std::wstring& packageFamilyName, const PACKAGE_VERSION targetVersion, const std::wstring& matchedPackageFullName); - static std::wstring GetPackagePath(std::wstring const& packageFullName); - static HRESULT AddOrRegisterPackageInBreakAwayProcess(const std::filesystem::path& packagePath, const bool regiterHigherVersionPackage, const bool forceDeployment); - static std::wstring GenerateDeploymentAgentPath(); - static HRESULT AddOrRegisterPackage(const std::filesystem::path& package, const bool regiterHigherVersionPackage, const bool forceDeployment); - static HRESULT DeployPackages(const std::wstring& frameworkPackageFullName, const bool forceDeployment); - static HRESULT Deploy(const std::wstring& frameworkPackageFullName, const bool forceDeployment = false); - static HRESULT InstallLicenses(const std::wstring& frameworkPackageFullName); - static hstring GetCurrentFrameworkPackageFullName(); + static HRESULT Deploy( + const std::wstring& frameworkPackageFullName, + ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, + const bool forceDeployment); + static HRESULT InstallLicenses(const std::wstring& frameworkPackageFullName, ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext); + static HRESULT DeployPackages(const std::wstring& frameworkPackageFullName, ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, const bool forceDeployment); }; } namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::factory_implementation diff --git a/dev/Deployment/LicenseInstallerProxy.h b/dev/Deployment/LicenseInstallerProxy.h new file mode 100644 index 0000000000..893ab89345 --- /dev/null +++ b/dev/Deployment/LicenseInstallerProxy.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. +#pragma once + +#include "Licensing.h" +#include "WindowsAppRuntime-License.h" + +namespace WindowsAppRuntime::Deployment::Licensing +{ + class LicenseInstallerProxy : public ::WindowsAppRuntime::Deployment::Licensing::ILicenseInstaller + { + ::Microsoft::Windows::ApplicationModel::Licensing::Installer& m_installer; + + public: + LicenseInstallerProxy(::Microsoft::Windows::ApplicationModel::Licensing::Installer& installer) : m_installer(installer) {} + + HRESULT InstallLicenseFile(const std::wstring& licenseFilename) override + { + return m_installer.InstallLicenseFile(licenseFilename.c_str()); + } + }; +} diff --git a/dev/Deployment/Licensing.cpp b/dev/Deployment/Licensing.cpp new file mode 100644 index 0000000000..0e1b88b680 --- /dev/null +++ b/dev/Deployment/Licensing.cpp @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include +#include "DeploymentActivityContext.h" +#include "Licensing.h" + +using namespace winrt; + +namespace WindowsAppRuntime::Deployment::Licensing +{ + // licenseFileSpec: This parameter specifies the file specification (e.g., path and pattern) for the license files to be retrieved. + // licenseFiles: This is an output parameter that will be populated with the names of the license files found matching the specified file specification. + HRESULT GetLicenseFiles(const std::wstring& licenseFileSpec, std::vector& licenseFiles) + { + licenseFiles.clear(); + + WIN32_FIND_DATA findFileData{}; + wil::unique_hfind hfind{ FindFirstFileW(licenseFileSpec.c_str(), &findFileData) }; + if (!hfind) + { + const auto lastError{ GetLastError() }; + RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(lastError), (lastError != ERROR_FILE_NOT_FOUND) && (lastError != ERROR_PATH_NOT_FOUND), + "FindFirstFile:%ls", licenseFileSpec.c_str()); + return S_OK; + } + for (;;) + { + // Add the license file + licenseFiles.push_back(findFileData.cFileName); + + // Next! (if any) + if (!FindNextFileW(hfind.get(), &findFileData)) + { + const auto lastError{ GetLastError() }; + RETURN_HR_IF(HRESULT_FROM_WIN32(lastError), lastError != ERROR_NO_MORE_FILES); + break; + } + } + return S_OK; + } + + HRESULT InstallLicenses( + const std::vector& licenseFiles, + const std::filesystem::path& licensePath, + ILicenseInstaller& licenseInstaller, + ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext) + { + initializeActivityContext.Reset(); + initializeActivityContext.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::InstallLicense); + + // Deploy the licenses (if any) + for (const auto& licenseFileName : licenseFiles) + { + // Install the license file + auto licenseFileFullName{ licensePath }; + licenseFileFullName /= licenseFileName; + + initializeActivityContext.SetCurrentResourceId(licenseFileFullName); + + RETURN_IF_FAILED_MSG(licenseInstaller.InstallLicenseFile(licenseFileFullName.c_str()), + "LicenseFile:%ls", licenseFileFullName.c_str()); + } + return S_OK; + } +} diff --git a/dev/Deployment/Licensing.h b/dev/Deployment/Licensing.h new file mode 100644 index 0000000000..54d076756a --- /dev/null +++ b/dev/Deployment/Licensing.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include "DeploymentActivityContext.h" + +namespace WindowsAppRuntime::Deployment::Licensing +{ + // Proxy/Wrapper for license installer. Open possibility to add more methods if needed. + struct ILicenseInstaller + { + virtual HRESULT InstallLicenseFile(const std::wstring& licenseFilename) = 0; + }; + + // Get license files from the specified path pattern + HRESULT GetLicenseFiles(const std::wstring& licenseFileSpec, std::vector& licenseFiles); + + // Install license files + HRESULT InstallLicenses( + const std::vector& licenseFiles, + const std::filesystem::path& licensePath, + ILicenseInstaller& licenseInstaller, + ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext); +} diff --git a/dev/Deployment/PackageDeployment.cpp b/dev/Deployment/PackageDeployment.cpp new file mode 100644 index 0000000000..a631593093 --- /dev/null +++ b/dev/Deployment/PackageDeployment.cpp @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include +#include "DeploymentActivityContext.h" +#include "PackageDefinitions.h" +#include "PackageDeployment.h" + +namespace WindowsAppRuntime::Deployment::PackageDeployment +{ + std::vector GetDeploymentPackageArguments( + const std::filesystem::path& frameworkPackagePath, + ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, + const std::map& existingTargetPackagesIfHigherVersion) + { + initializeActivityContext.Reset(); + initializeActivityContext.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::GetPackagePath); + + std::vector deploymentPackageArguments{}; + + for (auto package : winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implementation::c_targetPackages) + { + auto isSingleton{ CompareStringOrdinal(package.identifier.c_str(), -1, WINDOWSAPPRUNTIME_PACKAGE_SUBTYPENAME_SINGLETON, -1, TRUE) == CSTR_EQUAL }; + + std::filesystem::path packagePath{}; + + // If there is exisiting target package version higher than that of framework current version package, then re-register it. + // Otherwise, deploy the target msix package from the current framework package version. + auto existingPackageIfHigherVersion = existingTargetPackagesIfHigherVersion.find(package.identifier); + auto useExistingPackageIfHigherVersion { existingPackageIfHigherVersion != existingTargetPackagesIfHigherVersion.end() }; + if (useExistingPackageIfHigherVersion) + { + packagePath = existingPackageIfHigherVersion->second.packagePath; + packagePath /= WINDOWSAPPRUNTIME_PACKAGE_MANIFEST_FILE; + } + else + { + packagePath = frameworkPackagePath; + packagePath /= WINDOWSAPPRUNTIME_FRAMEWORK_PACKAGE_FOLDER; + packagePath /= package.identifier + WINDOWSAPPRUNTIME_FRAMEWORK_PACKAGE_FILE_EXTENSION; + } + + deploymentPackageArguments.push_back(DeploymentPackageArguments{ package.identifier, packagePath, useExistingPackageIfHigherVersion, isSingleton }); + } + + return deploymentPackageArguments; + } +} diff --git a/dev/Deployment/PackageDeployment.h b/dev/Deployment/PackageDeployment.h new file mode 100644 index 0000000000..d162d19270 --- /dev/null +++ b/dev/Deployment/PackageDeployment.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include +#include "DeploymentActivityContext.h" + +namespace WindowsAppRuntime::Deployment::PackageDeployment +{ + // Structure to hold deployment package arguments + struct DeploymentPackageArguments + { + std::wstring identifier{}; + std::filesystem::path packagePath{}; + bool useExistingPackageIfHigherVersion{}; + bool isSingleton{}; + }; + + struct PackagePathInfo + { + std::wstring packageFullName{}; + std::filesystem::path packagePath{}; + }; + + // Get deployment package arguments + std::vector GetDeploymentPackageArguments( + const std::filesystem::path& frameworkPackagePath, + ::WindowsAppRuntime::Deployment::Activity::Context& initializeActivityContext, + const std::map& existingTargetPackagesIfHigherVersion); +} diff --git a/dev/Deployment/PackageRegistrar.cpp b/dev/Deployment/PackageRegistrar.cpp new file mode 100644 index 0000000000..70595d7ca4 --- /dev/null +++ b/dev/Deployment/PackageRegistrar.cpp @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include +#include +#include +#include +#include +#include +#include "PackageRegistrar.h" +#include "DeploymentActivityContext.h" + +namespace WindowsAppRuntime::Deployment::PackageRegistrar +{ + // If useExistingPackageIfHigherVersion == false, Adds the current version package at the passed in path using PackageManager. + // If useExistingPackageIfHigherVersion == true, Registers the higher version package using the passed in path as manifest path and PackageManager. + // This requires the 'packageManagement' or 'runFullTrust' capabilities. + HRESULT AddOrRegisterPackage( + const std::filesystem::path& path, + const bool useExistingPackageIfHigherVersion, + const bool forceDeployment, + winrt::Windows::Management::Deployment::IPackageManager& packageManager, + ::WindowsAppRuntime::Deployment::Activity::Context& activityContext) try + { + const auto options{ forceDeployment ? + winrt::Windows::Management::Deployment::DeploymentOptions::ForceTargetApplicationShutdown : + winrt::Windows::Management::Deployment::DeploymentOptions::None }; + + winrt::Windows::Foundation::IAsyncOperationWithProgress deploymentOperation; + + const auto pathUri { winrt::Windows::Foundation::Uri(path.c_str()) }; + if (useExistingPackageIfHigherVersion) + { + deploymentOperation = packageManager.RegisterPackageAsync(pathUri, nullptr, options); + } + else + { + deploymentOperation = packageManager.AddPackageAsync(pathUri, nullptr, options); + } + + deploymentOperation.get(); + + const auto deploymentResult{ deploymentOperation.GetResults() }; + HRESULT hrError{}; + HRESULT hrExtendedError{}; + + if (deploymentOperation.Status() != winrt::Windows::Foundation::AsyncStatus::Completed) + { + hrError = static_cast(deploymentOperation.ErrorCode()); + hrExtendedError = deploymentResult.ExtendedErrorCode(); + + activityContext.SetDeploymentErrorInfo( + hrExtendedError, + deploymentResult.ErrorText().c_str(), + deploymentResult.ActivityId()); + } + + // If hrError indicates success, take that, ignore hrExtendedError. + // Otherwise, return hrExtendedError if there is an error in it, if not, return hrError. + return (FAILED(hrError) && FAILED(hrExtendedError) ? hrExtendedError : hrError); + } + CATCH_RETURN() + + std::wstring GenerateDeploymentAgentPath() + { + // Calculate the path to the restart agent as being in the same directory as the current module. + wil::unique_hmodule module; + THROW_IF_WIN32_BOOL_FALSE(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(PackageRegistrar::GenerateDeploymentAgentPath), &module)); + + std::filesystem::path modulePath{ wil::GetModuleFileNameW(module.get()) }; + return modulePath.parent_path() / c_deploymentAgentFilename; + } + + /// @warning This function is ONLY for processes with package identity. It's the caller's responsibility to ensure this. + HRESULT AddOrRegisterPackageInBreakAwayProcess( + const std::filesystem::path& path, + const bool useExistingPackageIfHigherVersion, + const bool forceDeployment, + ::WindowsAppRuntime::Deployment::Activity::Context& activityContext, + const std::wstring& deploymentAgentPath) try + { + auto exePath{ deploymentAgentPath }; + auto activityId{ winrt::to_hstring(*activityContext.GetActivity().Id()) }; + + // \deploymentagent.exe + auto cmdLine{ wil::str_printf(L"\"%s\" %u \"%s\" %u %s", exePath.c_str(), (useExistingPackageIfHigherVersion ? 1 : 0), path.c_str(), (forceDeployment ? 1 : 0), activityId.c_str()) }; + + SIZE_T attributeListSize{}; + auto attributeCount{ 1 }; + + // attributeCount is always >0 so we need to allocate a buffer. Call InitializeProcThreadAttributeList() + // to determine the size needed so we always expect ERROR_INSUFFICIENT_BUFFER. + THROW_HR_IF(E_UNEXPECTED, !!InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &attributeListSize)); + const auto lastError{ GetLastError() }; + THROW_HR_IF(HRESULT_FROM_WIN32(lastError), lastError != ERROR_INSUFFICIENT_BUFFER); + wistd::unique_ptr attributeListBuffer{ new BYTE[attributeListSize] }; + auto attributeList{ reinterpret_cast(attributeListBuffer.get()) }; + THROW_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(attributeList, attributeCount, 0, &attributeListSize)); + auto freeAttributeList{ wil::scope_exit([&] { DeleteProcThreadAttributeList(attributeList); }) }; + + // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute + // The process being created will create any child processes outside of the desktop app runtime environment. + // This behavior is the default for processes for which no policy has been set + DWORD policy{ PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_ENABLE_PROCESS_TREE }; + THROW_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_DESKTOP_APP_POLICY, &policy, sizeof(policy), nullptr, nullptr)); + + STARTUPINFOEX info{}; + info.StartupInfo.cb = sizeof(info); + info.lpAttributeList = attributeList; + + wil::unique_process_information processInfo; + + THROW_IF_WIN32_BOOL_FALSE(CreateProcess(nullptr, cmdLine.get(), nullptr, nullptr, FALSE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &info.StartupInfo, &processInfo)); + + // This API is designed to only return to the caller on failure, otherwise block until process termination. + // Wait for the agent to exit. If the agent succeeds, it will terminate this process. If the agent fails, + // it can exit or crash. This API will be able to detect the failure and return. + wil::handle_wait(processInfo.hProcess); + + DWORD processExitCode{}; + THROW_IF_WIN32_BOOL_FALSE_MSG(GetExitCodeProcess(processInfo.hProcess, &processExitCode), "CmdLine: %ls, processExitCode: %u", cmdLine.get(), processExitCode); + RETURN_IF_FAILED_MSG(HRESULT_FROM_WIN32(processExitCode), "DeploymentAgent exitcode:0x%X", processExitCode); + return S_OK; + } + CATCH_RETURN() +} diff --git a/dev/Deployment/PackageRegistrar.h b/dev/Deployment/PackageRegistrar.h new file mode 100644 index 0000000000..c58ba20b4b --- /dev/null +++ b/dev/Deployment/PackageRegistrar.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include +#include "DeploymentActivityContext.h" + +namespace WindowsAppRuntime::Deployment::PackageRegistrar +{ + static constexpr PCWSTR c_deploymentAgentFilename{ L"DeploymentAgent.exe" }; + + std::wstring GenerateDeploymentAgentPath(); + + HRESULT AddOrRegisterPackage( + const std::filesystem::path& path, + const bool useExistingPackageIfHigherVersion, + const bool forceDeployment, + winrt::Windows::Management::Deployment::IPackageManager& packageManager, + ::WindowsAppRuntime::Deployment::Activity::Context& activityContext); + + HRESULT AddOrRegisterPackageInBreakAwayProcess( + const std::filesystem::path& path, + const bool useExistingPackageIfHigherVersion, + const bool forceDeployment, + ::WindowsAppRuntime::Deployment::Activity::Context& activityContext, + const std::wstring& deploymentAgentPath); +} diff --git a/dev/Deployment/PackageUtilities.cpp b/dev/Deployment/PackageUtilities.cpp new file mode 100644 index 0000000000..a9defccad7 --- /dev/null +++ b/dev/Deployment/PackageUtilities.cpp @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include +#include +#include +#include +#include "PackageUtilities.h" +#include "PackageDefinitions.h" +#include + +namespace WindowsAppRuntime::Deployment::Package +{ + // Gets the package path, which is a fast and reliable way to check if the package is + // at least staged on the device, even without package query capabilities. + std::wstring GetPackagePath(std::wstring const& packageFullName) + { + UINT32 pathLength{}; + const auto rc{ GetPackagePathByFullName(packageFullName.c_str(), &pathLength, nullptr) }; + if (rc == ERROR_NOT_FOUND) + { + return std::wstring(); + } + else if (rc != ERROR_INSUFFICIENT_BUFFER) + { + THROW_WIN32(rc); + } + + auto path{ wil::make_process_heap_string(nullptr, pathLength) }; + THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName.c_str(), &pathLength, path.get())); + return std::wstring{ path.get() }; + } + + // Borrowed and repurposed from Dynamic Dependencies + std::vector FindPackagesByFamily(std::wstring const& packageFamilyName) + { + UINT32 count{}; + UINT32 bufferLength{}; + const auto rc{ FindPackagesByPackageFamily(packageFamilyName.c_str(), PACKAGE_FILTER_HEAD | PACKAGE_FILTER_DIRECT, &count, nullptr, &bufferLength, nullptr, nullptr) }; + if (rc == ERROR_SUCCESS) + { + // The package family has no packages registered to the user + return std::vector(); + } + else if (rc != ERROR_INSUFFICIENT_BUFFER) + { + THROW_WIN32(rc); + } + + auto packageFullNames{ wil::make_unique_cotaskmem(count) }; + auto buffer{ wil::make_unique_cotaskmem(bufferLength) }; + THROW_IF_WIN32_ERROR(FindPackagesByPackageFamily(packageFamilyName.c_str(), PACKAGE_FILTER_HEAD | PACKAGE_FILTER_DIRECT, &count, packageFullNames.get(), &bufferLength, buffer.get(), nullptr)); + + std::vector packageFullNamesList; + for (UINT32 index=0; index < count; ++index) + { + const auto packageFullName{ packageFullNames[index] }; + packageFullNamesList.push_back(std::wstring(packageFullName)); + } + return packageFullNamesList; + } + + HRESULT VerifyPackage(const std::wstring& packageFamilyName, const PACKAGE_VERSION targetVersion, + const std::wstring& packageIdentifier) try + { + auto packageFullNames{ FindPackagesByFamily(packageFamilyName) }; + bool match{}; + for (const auto& packageFullName : packageFullNames) + { + auto packagePath{ GetPackagePath(packageFullName) }; + if (packagePath.empty()) + { + continue; + } + + auto packageId{ AppModel::Identity::PackageIdentity::FromPackageFullName(packageFullName.c_str()) }; + if (packageId.Version().Version >= targetVersion.Version) + { + match = true; + if (packageId.Version().Version > targetVersion.Version) + { + winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implementation::g_existingTargetPackagesIfHigherVersion.insert(std::make_pair(packageIdentifier, packageFullName)); + } + break; + } + } + + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !match); + return S_OK; + } + CATCH_RETURN() + + MddCore::PackageInfo GetPackageInfoForPackage(std::wstring const& packageFullName) + { + wil::unique_package_info_reference packageInfoReference; + THROW_IF_WIN32_ERROR(OpenPackageInfoByFullName(packageFullName.c_str(), 0, &packageInfoReference)); + return MddCore::PackageInfo::FromPackageInfoReference(packageInfoReference.get()); + } + + winrt::hstring GetCurrentFrameworkPackageFullName() + { + // Get current package identity. + WCHAR packageFullName[PACKAGE_FULL_NAME_MAX_LENGTH + 1]{}; + UINT32 packageFullNameLength{ static_cast(ARRAYSIZE(packageFullName)) }; + const auto rc{ ::GetCurrentPackageFullName(&packageFullNameLength, packageFullName) }; + if (rc != ERROR_SUCCESS) + { + THROW_WIN32(rc); + } + + // Get the PackageInfo of current package and it's dependency packages + std::wstring currentPackageFullName{ packageFullName }; + auto currentPackageInfo{ GetPackageInfoForPackage(currentPackageFullName) }; + + // Index starts at 1 since the first package is the current package and we are interested in + // dependency packages only. + for (size_t i = 0; i < currentPackageInfo.Count(); ++i) + { + auto dependencyPackage{ currentPackageInfo.Package(i) }; + + // Verify PublisherId matches. + if (CompareStringOrdinal(dependencyPackage.packageId.publisherId, -1, WINDOWSAPPRUNTIME_PACKAGE_PUBLISHERID, -1, TRUE) != CSTR_EQUAL) + { + continue; + } + + // Verify that the WindowsAppRuntime prefix identifier is in the name. + // This should also be the beginning of the name, so its find position is expected to be 0. + std::wstring dependencyPackageName{ dependencyPackage.packageId.name }; + if (dependencyPackageName.find(WINDOWSAPPRUNTIME_PACKAGE_NAME_PREFIX) != 0) + { + continue; + } + + // On WindowsAppSDK 1.1+, there is no need to check and rule out Main, Singleton and DDLM Package identifiers as their names don't have a overlap with WINDOWSAPPRUNTIME_PACKAGE_NAME_PREFIX. + + return winrt::hstring(dependencyPackage.packageFullName); + } + + THROW_WIN32(ERROR_NOT_FOUND); + } +} diff --git a/dev/Deployment/PackageUtilities.h b/dev/Deployment/PackageUtilities.h new file mode 100644 index 0000000000..3dd71a9e40 --- /dev/null +++ b/dev/Deployment/PackageUtilities.h @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace WindowsAppRuntime::Deployment::Package +{ + std::wstring GetPackagePath(std::wstring const& packageFullName); + + std::vector FindPackagesByFamily(std::wstring const& packageFamilyName); + + HRESULT VerifyPackage(const std::wstring& packageFamilyName, const PACKAGE_VERSION targetVersion, const std::wstring& packageIdentifier); + + MddCore::PackageInfo GetPackageInfoForPackage(std::wstring const& packageFullName); + + winrt::hstring GetCurrentFrameworkPackageFullName(); +} diff --git a/test/Deployment/API/packages.config b/test/Deployment/API/packages.config index bbbb6af252..d5bdb8bcfc 100644 --- a/test/Deployment/API/packages.config +++ b/test/Deployment/API/packages.config @@ -1,4 +1,4 @@ - + diff --git a/test/Deployment/API/pch.h b/test/Deployment/API/pch.h index ece1c28e92..a8b318f6ea 100644 --- a/test/Deployment/API/pch.h +++ b/test/Deployment/API/pch.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #ifndef PCH_H diff --git a/test/Deployment/UnitTests/DeploymentUnitTests.testdef b/test/Deployment/UnitTests/DeploymentUnitTests.testdef new file mode 100644 index 0000000000..d4c7858ec6 --- /dev/null +++ b/test/Deployment/UnitTests/DeploymentUnitTests.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Deployment Unit Tests", + "Filename": "DeploymentUnitTests.dll", + "Parameters": "", + "Architectures": ["x64", "x86", "arm64"], + "Status": "Enabled" + } + ] +} diff --git a/test/Deployment/UnitTests/DeploymentUnitTests.vcxproj b/test/Deployment/UnitTests/DeploymentUnitTests.vcxproj new file mode 100644 index 0000000000..65110223c2 --- /dev/null +++ b/test/Deployment/UnitTests/DeploymentUnitTests.vcxproj @@ -0,0 +1,273 @@ + + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {F2A9B8E7-4C62-4D89-9A4F-829F8E2A7E34} + Win32Proj + Test.DeploymentUnitTests + 10.0 + NativeUnitTestProject + DeploymentUnitTests + + + DynamicLibrary + true + v143 + Unicode + false + + + DynamicLibrary + false + v143 + Unicode + false + + + DynamicLibrary + true + v143 + Unicode + false + + + DynamicLibrary + true + v143 + Unicode + false + + + DynamicLibrary + false + v143 + Unicode + false + + + DynamicLibrary + false + v143 + Unicode + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Use + %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(RepoRoot)\test\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;$(RepoRoot)\dev\Deployment;$(RepoRoot)\dev\Common + WIN32;NDEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + + + $(RepoRoot)\dev\common + + + + + Use + %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(RepoRoot)\test\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;$(RepoRoot)\dev\Deployment;$(RepoRoot)\dev\Common + WIN32;_DEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + + + $(RepoRoot)\dev\common + + + + + Use + %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(RepoRoot)\test\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;$(RepoRoot)\dev\Deployment;$(RepoRoot)\dev\Common + _DEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + + + $(RepoRoot)\dev\common + + + + + Use + %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(RepoRoot)\test\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;$(RepoRoot)\dev\Deployment;$(RepoRoot)\dev\Common + _DEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + + + $(RepoRoot)\dev\common + + + + + Use + %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(RepoRoot)\test\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;$(RepoRoot)\dev\Deployment;$(RepoRoot)\dev\Common + NDEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + + + $(RepoRoot)\dev\common + + + + + Use + %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;$(RepoRoot)\test\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL;$(RepoRoot)\dev\Deployment;$(RepoRoot)\dev\Common + NDEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP + true + pch.h + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) + + + $(RepoRoot)\dev\common + + + + + Create + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + .Debug + + + + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.Windows.ApplicationModel.WindowsAppRuntime.winmd + true + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/test/Deployment/UnitTests/DeploymentUnitTests.vcxproj.filters b/test/Deployment/UnitTests/DeploymentUnitTests.vcxproj.filters new file mode 100644 index 0000000000..f9ceced99d --- /dev/null +++ b/test/Deployment/UnitTests/DeploymentUnitTests.vcxproj.filters @@ -0,0 +1,87 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {5F8E8F1A-1B2C-4D3E-9F4A-1B2C3D4E5F6A} + + + {6A9B8C7D-2C3D-4E5F-A6B7-2C3D4E5F6A7B} + + + + + {A1B2C3D4-E5F6-7890-1234-567890ABCDEF} + + + + + Header Files + + + + External\Deployment + + + External\Deployment + + + External\Deployment + + + External\Deployment + + + + + Source Files + + + Source Files + + + Source Files + + + + External\Deployment + + + External\Deployment + + + External\Deployment + + + External\Deployment + + + + + + + + Test Data\MSIX + + + Test Data\MSIX + + + Test Data\MSIX + + + Test Data\MSIX + + + diff --git a/test/Deployment/UnitTests/LicensingTests.cpp b/test/Deployment/UnitTests/LicensingTests.cpp new file mode 100644 index 0000000000..9ba9975bef --- /dev/null +++ b/test/Deployment/UnitTests/LicensingTests.cpp @@ -0,0 +1,383 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +namespace Test::Deployment::Licensing +{ + constexpr HRESULT c_unhandledExceptionHResult = 0x8007023E; + + // Mock implementation of ILicenseInstaller for testing + struct MockLicenseInstaller : public WindowsAppRuntime::Deployment::Licensing::ILicenseInstaller + { + std::vector m_installedFiles{}; + std::unordered_map m_expectedFailureMap{}; + bool m_shouldThrowException{ false }; + + MockLicenseInstaller() = default; + + // Set up the mock to fail on a specific file + void SetupFailureOnFile(const std::wstring& filename, HRESULT errorCode) + { + m_expectedFailureMap[filename] = errorCode; + } + + void SetShouldThrowException(bool shouldThrow) + { + m_shouldThrowException = shouldThrow; + } + + HRESULT InstallLicenseFile(const std::wstring& licenseFilename) override + { + if (m_shouldThrowException) + { + throw std::runtime_error("Test exception"); + } + + for (auto it : m_expectedFailureMap) + { + auto filename{ it.first }; + if(licenseFilename.find(filename) != std::wstring::npos) + { + return it.second; + } + } + + m_installedFiles.push_back(licenseFilename); + return S_OK; + } + + // Test helper methods + const std::vector& GetInstalledFiles() const + { + return m_installedFiles; + } + + void Reset() { + m_installedFiles.clear(); + m_expectedFailureMap.clear(); + m_shouldThrowException = false; + } + + size_t GetInstallCount() const { + return m_installedFiles.size(); + } + }; + + class LicensingTests + { + public: + BEGIN_TEST_CLASS(LicensingTests) + TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") + END_TEST_CLASS() + + TEST_CLASS_SETUP(ClassInit) + { + return true; + } + + TEST_CLASS_CLEANUP(ClassUninit) + { + return true; + } + + // GetLicenseFiles Tests + TEST_METHOD(GetLicenseFiles_NoFilesFound_ReturnsSuccessWithEmptyVector) + { + Log::Comment(L"Test GetLicenseFiles with non-existent path returns success with empty vector"); + + std::vector licenseFiles{}; + std::wstring nonExistentPath{ L"C:\\NonExistent\\Path\\*_license.xml" }; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(nonExistentPath, licenseFiles); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(licenseFiles.size(), 0u); + Log::Comment(L"GetLicenseFiles correctly handled non-existent path"); + } + + TEST_METHOD(GetLicenseFiles_EmptyFileSpec_Succeeds) + { + Log::Comment(L"Test GetLicenseFiles with empty file specification"); + + std::vector licenseFiles{}; + std::wstring emptyPath{ L"" }; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(emptyPath, licenseFiles); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(licenseFiles.size(), 0u); + Log::Comment(L"GetLicenseFiles correctly handled empty file specification"); + } + + TEST_METHOD(GetLicenseFiles_InvalidPath_ReturnsError) + { + Log::Comment(L"Test GetLicenseFiles with invalid path characters"); + + std::vector licenseFiles{}; + std::wstring invalidPath{ L"C:\\Invalid|Path\\*_license.xml" }; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(invalidPath, licenseFiles); + + VERIFY_IS_TRUE(FAILED(hr)); + Log::Comment(String().Format(L"GetLicenseFiles correctly failed with invalid path, HR: 0x%08X", hr)); + } + + TEST_METHOD(GetLicenseFiles_ClearsOutputVector) + { + Log::Comment(L"Test GetLicenseFiles clears the output vector"); + + std::vector licenseFiles { + L"existing_file.xml", + L"another_file.xml" + }; + + VERIFY_ARE_EQUAL(licenseFiles.size(), 2u); + + std::wstring nonExistentPath{ L"C:\\NonExistent\\Path\\*_license.xml" }; + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(nonExistentPath, licenseFiles); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(licenseFiles.size(), 0u); + Log::Comment(L"GetLicenseFiles correctly cleared the output vector"); + } + + TEST_METHOD(GetLicenseFiles_WithRealFiles_FindsAllLicenseFiles) + { + Log::Comment(L"Test GetLicenseFiles with real mock license files"); + + std::vector licenseFiles{}; + + // Get the current test directory and construct the MSIX path + wchar_t currentDir[MAX_PATH]{}; + GetCurrentDirectory(MAX_PATH, currentDir); + std::wstring testPath{ std::wstring(currentDir) + L"\\test\\Deployment\\UnitTests\\MSIX\\*_license.xml" }; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(testPath, licenseFiles); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(licenseFiles.size(), 3u); + + VERIFY_ARE_EQUAL(licenseFiles[0], L"a_license.xml"); + VERIFY_ARE_EQUAL(licenseFiles[1], L"b_license.xml"); + // Note: preserves case of original name + VERIFY_ARE_EQUAL(licenseFiles[2], L"c_License.xml"); + + Log::Comment(L"GetLicenseFiles correctly found all 3 license files"); + } + + // InstallLicenses Tests + TEST_METHOD(InstallLicenses_EmptyLicenseList_ReturnsSuccess) + { + Log::Comment(L"Test InstallLicenses with empty license file list"); + + std::vector licenseFiles{}; + std::filesystem::path licensePath{ L"C:\\TestPath" }; + MockLicenseInstaller mockInstaller{}; + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(mockInstaller.GetInstallCount(), 0u); + Log::Comment(L"InstallLicenses correctly handled empty license list"); + } + + TEST_METHOD(InstallLicenses_SingleLicenseFile_InstallsSuccessfully) + { + Log::Comment(L"Test InstallLicenses with single license file"); + + std::vector licenseFiles{ L"test_license.xml" }; + std::filesystem::path licensePath{ L"C:\\TestPath" }; + MockLicenseInstaller mockInstaller{}; + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(mockInstaller.GetInstallCount(), 1u); + + const auto& installedFiles = mockInstaller.GetInstalledFiles(); + VERIFY_IS_TRUE(installedFiles[0].find(L"test_license.xml") != std::wstring::npos); + VERIFY_IS_TRUE(installedFiles[0].find(L"C:\\TestPath") != std::wstring::npos); + + Log::Comment(String().Format(L"Successfully installed: %s", installedFiles[0].c_str())); + } + + TEST_METHOD(InstallLicenses_MultipleLicenseFiles_InstallsAll) + { + Log::Comment(L"Test InstallLicenses with multiple license files"); + + std::vector licenseFiles { + L"license1.xml", + L"license2.xml", + L"license3.xml" + }; + std::filesystem::path licensePath{ L"C:\\TestPath" }; + MockLicenseInstaller mockInstaller{}; + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(mockInstaller.GetInstallCount(), 3u); + + const auto& installedFiles = mockInstaller.GetInstalledFiles(); + for (size_t i = 0; i < licenseFiles.size(); ++i) + { + VERIFY_IS_TRUE(installedFiles[i].find(licenseFiles[i]) != std::wstring::npos); + Log::Comment(String().Format(L"Installed file %zu: %s", i + 1, installedFiles[i].c_str())); + } + } + + TEST_METHOD(InstallLicenses_InstallerFails_ReturnsError) + { + Log::Comment(L"Test InstallLicenses when installer fails"); + + std::vector licenseFiles{ L"failing_license.xml" }; + std::filesystem::path licensePath{ L"C:\\TestPath" }; + MockLicenseInstaller mockInstaller{}; + mockInstaller.SetupFailureOnFile(L"failing_license.xml", E_ACCESSDENIED); + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_ARE_EQUAL(hr, E_ACCESSDENIED); + Log::Comment(String().Format(L"InstallLicenses correctly returned error: 0x%08X", hr)); + } + + TEST_METHOD(InstallLicenses_PartialFailure_StopsOnFirstError) + { + Log::Comment(L"Test InstallLicenses stops on first error in batch"); + + std::vector licenseFiles { + L"good_license.xml", + L"failing_license.xml", + L"never_reached.xml" + }; + std::filesystem::path licensePath{ L"C:\\TestPath" }; + MockLicenseInstaller mockInstaller{}; + mockInstaller.SetupFailureOnFile(L"failing_license.xml", E_FAIL); + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_ARE_EQUAL(hr, E_FAIL); + // Should have installed the first file before failing on the second + VERIFY_ARE_EQUAL(mockInstaller.GetInstallCount(), 1u); + + const auto& installedFiles = mockInstaller.GetInstalledFiles(); + VERIFY_IS_TRUE(installedFiles[0].find(L"good_license.xml") != std::wstring::npos); + + Log::Comment(L"InstallLicenses correctly stopped after first failure"); + } + + TEST_METHOD(InstallLicenses_SetsActivityContext) + { + Log::Comment(L"Test InstallLicenses updates activity context properly"); + + std::vector licenseFiles{ L"test_license.xml" }; + std::filesystem::path licensePath{ L"C:\\TestPath" }; + MockLicenseInstaller mockInstaller{}; + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + // Reset context to initial state + activityContext.Reset(); + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_SUCCEEDED(hr); + + // The function should have set the install stage + VERIFY_ARE_EQUAL( + activityContext.GetInstallStage(), + WindowsAppRuntime::Deployment::Activity::DeploymentStage::InstallLicense); + + // The current resource ID should be set to the installed license file + std::wstring expectedResourceId = licensePath.wstring() + L"\\test_license.xml"; + VERIFY_ARE_EQUAL(activityContext.GetCurrentResourceId(), expectedResourceId); + + Log::Comment(L"InstallLicenses completed with activity context updates"); + } + + TEST_METHOD(InstallLicenses_CorrectPathCombination) + { + Log::Comment(L"Test InstallLicenses correctly combines path and filename"); + + std::vector licenseFiles{ L"myapp_license.xml" }; + std::filesystem::path licensePath{ L"C:\\Program Files\\TestApp\\Licenses" }; + MockLicenseInstaller mockInstaller{}; + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + HRESULT hr = WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + VERIFY_SUCCEEDED(hr); + VERIFY_ARE_EQUAL(mockInstaller.GetInstallCount(), 1u); + + const auto& installedFiles{ mockInstaller.GetInstalledFiles() }; + std::wstring expectedPath{ L"C:\\Program Files\\TestApp\\Licenses\\myapp_license.xml" }; + + // Normalize path separators for comparison + std::wstring actualPath = installedFiles[0]; + VERIFY_IS_TRUE(actualPath.find(L"TestApp") != std::wstring::npos); + VERIFY_IS_TRUE(actualPath.find(L"Licenses") != std::wstring::npos); + VERIFY_IS_TRUE(actualPath.find(L"myapp_license.xml") != std::wstring::npos); + + Log::Comment(String().Format(L"Correct path combination: %s", actualPath.c_str())); + } + + TEST_METHOD(InstallLicenses_CorruptedLicenseFile_HandlesException) + { + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Ignore", L"True") // Currently failing + END_TEST_METHOD_PROPERTIES() + + std::vector licenseFiles{ L"corrupted_license.xml" }; + std::filesystem::path licensePath{ L"C:\\Program Files\\TestApp\\Licenses" }; + MockLicenseInstaller mockInstaller{}; + ::WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + // Mock the installer to simulate exception during license processing + mockInstaller.SetShouldThrowException(true); + + auto hr = ::WindowsAppRuntime::Deployment::Licensing::InstallLicenses( + licenseFiles, licensePath, mockInstaller, activityContext); + + // Should handle license processing exceptions gracefully + VERIFY_IS_TRUE(FAILED(hr)); + VERIFY_ARE_NOT_EQUAL(hr, c_unhandledExceptionHResult); + } + + TEST_METHOD(GetLicenseFiles_FileSystemException_HandlesGracefully) + { + // Test with path that could cause file system exceptions + std::wstring problematicPath{ L"\\\\?\\C:\\System Volume Information\\*_license.xml" }; // Restricted access + std::vector licenseFiles{}; + + auto hr = ::WindowsAppRuntime::Deployment::Licensing::GetLicenseFiles(problematicPath, licenseFiles); + + // Should handle file system access issues gracefully + VERIFY_IS_TRUE(FAILED(hr)); // Either way, shouldn't throw unhandled exception + VERIFY_ARE_NOT_EQUAL(hr, c_unhandledExceptionHResult); + } + }; +} diff --git a/test/Deployment/UnitTests/MSIX/AppxManifest.xml b/test/Deployment/UnitTests/MSIX/AppxManifest.xml new file mode 100644 index 0000000000..f90e279eb0 --- /dev/null +++ b/test/Deployment/UnitTests/MSIX/AppxManifest.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/test/Deployment/UnitTests/MSIX/a_license.xml b/test/Deployment/UnitTests/MSIX/a_license.xml new file mode 100644 index 0000000000..2cb0693bc7 --- /dev/null +++ b/test/Deployment/UnitTests/MSIX/a_license.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/test/Deployment/UnitTests/MSIX/b_license.xml b/test/Deployment/UnitTests/MSIX/b_license.xml new file mode 100644 index 0000000000..89ed42eac7 --- /dev/null +++ b/test/Deployment/UnitTests/MSIX/b_license.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/test/Deployment/UnitTests/MSIX/c_License.xml b/test/Deployment/UnitTests/MSIX/c_License.xml new file mode 100644 index 0000000000..7f7d442561 --- /dev/null +++ b/test/Deployment/UnitTests/MSIX/c_License.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/test/Deployment/UnitTests/PackageDeploymentTests.cpp b/test/Deployment/UnitTests/PackageDeploymentTests.cpp new file mode 100644 index 0000000000..f31646152c --- /dev/null +++ b/test/Deployment/UnitTests/PackageDeploymentTests.cpp @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" +#include +#include +#include +#include +#include +#include + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +namespace Test::Deployment::PackageDeployment +{ + class PackageDeploymentTests + { + public: + BEGIN_TEST_CLASS(PackageDeploymentTests) + TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") + END_TEST_CLASS() + + TEST_CLASS_SETUP(ClassInit) + { + return true; + } + + TEST_CLASS_CLEANUP(ClassUninit) + { + return true; + } + + TEST_METHOD(GetDeploymentPackageArguments_WithHigherVersionPackage_UsesExistingPath) + { + std::wstring frameworkFullName{ L"Microsoft.WindowsAppRuntime.1.5_x64__8wekyb3d8bbwe" }; + std::wstring frameworkPath{ L"C:\\Program Files\\WindowsApps\\Framework.1.5" }; + + std::map higherVersionMap{}; + higherVersionMap[L"Main"] = { + L"Main.2.0_x64__8wekyb3d8bbwe", + L"C:\\Program Files\\WindowsApps\\Main.2.0" + }; + + ::WindowsAppRuntime::Deployment::Activity::Context context{}; + + auto args = WindowsAppRuntime::Deployment::PackageDeployment::GetDeploymentPackageArguments( + frameworkPath, + context, + higherVersionMap); + + VERIFY_ARE_EQUAL(args.size(), 2u); // Main and Singleton + + auto& mainPackage = args[0]; + VERIFY_ARE_EQUAL(mainPackage.identifier, L"Main"); + VERIFY_IS_TRUE(mainPackage.useExistingPackageIfHigherVersion); + VERIFY_ARE_EQUAL( + mainPackage.packagePath.wstring(), + L"C:\\Program Files\\WindowsApps\\Main.2.0\\AppxManifest.xml"); + + auto& singletonPackage = args[1]; + VERIFY_ARE_EQUAL(singletonPackage.identifier, L"Singleton"); + VERIFY_IS_FALSE(singletonPackage.useExistingPackageIfHigherVersion); + VERIFY_ARE_EQUAL( + singletonPackage.packagePath.wstring(), + L"C:\\Program Files\\WindowsApps\\Framework.1.5\\MSIX\\Singleton.msix"); + } + }; +} diff --git a/test/Deployment/UnitTests/PackageRegistrarTests.cpp b/test/Deployment/UnitTests/PackageRegistrarTests.cpp new file mode 100644 index 0000000000..e8e0d90c9f --- /dev/null +++ b/test/Deployment/UnitTests/PackageRegistrarTests.cpp @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +using namespace winrt::Windows::Management::Deployment; + +namespace Test::Deployment +{ + constexpr HRESULT c_unhandledExceptionHResult = 0x8007023E; + + class PackageRegistrarTests + { + public: + BEGIN_TEST_CLASS(PackageRegistrarTests) + TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") + END_TEST_CLASS() + + TEST_CLASS_SETUP(ClassInit) + { + return true; + } + + TEST_CLASS_CLEANUP(ClassUninit) + { + return true; + } + + // Path validation tests + TEST_METHOD(GenerateDeploymentAgentPath_PathExists) + { + Log::Comment(L"Test that generated path points to an existing location"); + + auto path = WindowsAppRuntime::Deployment::PackageRegistrar::GenerateDeploymentAgentPath(); + std::filesystem::path fsPath{ path }; + + // The directory should exist (even if the exe doesn't) + auto parentPath = fsPath.parent_path(); + VERIFY_IS_TRUE(std::filesystem::exists(parentPath)); + + Log::Comment(String().Format(L"Parent directory exists: %s", parentPath.c_str())); + } + + // AddOrRegisterPackageInBreakAwayProcess tests + TEST_METHOD(AddOrRegisterPackageInBreakAwayProcess_InvalidPath_ReturnsError) + { + Log::Comment(L"Test AddOrRegisterPackageInBreakAwayProcess with invalid path"); + + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + std::filesystem::path invalidPath{ L"C:\\NonExistent\\Invalid.msix" }; + + HRESULT hr = WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackageInBreakAwayProcess( + invalidPath, + false, // useExistingPackageIfHigherVersion + false, // forceDeployment + activityContext, + WindowsAppRuntime::Deployment::PackageRegistrar::GenerateDeploymentAgentPath() + ); + + VERIFY_IS_TRUE(FAILED(hr)); + } + + TEST_METHOD(AddOrRegisterPackageInBreakAwayProcess_CustomDeploymentAgentPath) + { + Log::Comment(L"Test AddOrRegisterPackageInBreakAwayProcess with custom deployment agent path"); + + WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + std::filesystem::path testPackagePath{ L"C:\\test.msix" }; + std::wstring customAgentPath{ L"C:\\CustomPath\\DeploymentAgent.exe" }; + + // Should fail because neither package nor agent exist, but we're testing parameter handling + HRESULT hr = WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackageInBreakAwayProcess( + testPackagePath, + false, + false, + activityContext, + customAgentPath + ); + + VERIFY_IS_TRUE(FAILED(hr)); + } + + TEST_METHOD(AddOrRegisterPackage_InvalidPath_HandlesException) + { + // TODO: Maybe we should mock PackageManager to throw exceptions? + auto packageManager = winrt::Windows::Management::Deployment::PackageManager{}; + ::WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + // Test with invalid/malformed path that could cause unhandled exceptions + std::filesystem::path invalidPath{ L"\\\\invalid-unc-path\\nonexistent\\path\\package.msix" }; + + auto hr = ::WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackage( + invalidPath, false, false, packageManager, activityContext); + + // Should return a proper HRESULT, not throw unhandled exception + VERIFY_IS_TRUE(FAILED(hr)); + VERIFY_ARE_NOT_EQUAL(hr, c_unhandledExceptionHResult); + } + + TEST_METHOD(AddOrRegisterPackage_CorruptedPackage_HandlesException) + { + auto packageManager = winrt::Windows::Management::Deployment::PackageManager{}; + ::WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + + // Create a corrupted/invalid MSIX file + std::filesystem::path tempPath{ std::filesystem::temp_directory_path() / L"corrupted.msix" }; + std::ofstream corruptedFile{ tempPath, std::ios::binary }; + corruptedFile << "This is not a valid MSIX file content"; + corruptedFile.close(); + + auto hr = ::WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackage( + tempPath, false, false, packageManager, activityContext); + + // Cleanup + std::filesystem::remove(tempPath); + + // Should handle the corruption gracefully, not throw unhandled exception + VERIFY_IS_TRUE(FAILED(hr)); + VERIFY_ARE_NOT_EQUAL(hr, c_unhandledExceptionHResult); + } + + TEST_METHOD(AddOrRegisterPackageInBreakAwayProcess_InvalidAgentPath_HandlesException) + { + ::WindowsAppRuntime::Deployment::Activity::Context activityContext{}; + std::filesystem::path validPackagePath{ std::filesystem::temp_directory_path() / L"test.msix" }; + std::wstring invalidAgentPath{ L"C:\\NonExistent\\deploymentagent.exe" }; + + auto hr = ::WindowsAppRuntime::Deployment::PackageRegistrar::AddOrRegisterPackageInBreakAwayProcess( + validPackagePath, false, false, activityContext, invalidAgentPath); + + // Should fail gracefully, not throw unhandled exception + VERIFY_IS_TRUE(FAILED(hr)); + VERIFY_ARE_NOT_EQUAL(hr, c_unhandledExceptionHResult); + } + }; +} diff --git a/test/Deployment/UnitTests/packages.config b/test/Deployment/UnitTests/packages.config new file mode 100644 index 0000000000..d5bdb8bcfc --- /dev/null +++ b/test/Deployment/UnitTests/packages.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/Deployment/UnitTests/pch.cpp b/test/Deployment/UnitTests/pch.cpp new file mode 100644 index 0000000000..a77728ba07 --- /dev/null +++ b/test/Deployment/UnitTests/pch.cpp @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/test/Deployment/UnitTests/pch.h b/test/Deployment/UnitTests/pch.h new file mode 100644 index 0000000000..ea8c5b2077 --- /dev/null +++ b/test/Deployment/UnitTests/pch.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#ifndef PCH_H +#define PCH_H +#endif //PCH_H + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include