diff --git a/src/windows/WslcSDK/winrt/CMakeLists.txt b/src/windows/WslcSDK/winrt/CMakeLists.txt index 7d59cc67d..bb1749fb9 100644 --- a/src/windows/WslcSDK/winrt/CMakeLists.txt +++ b/src/windows/WslcSDK/winrt/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES ContainerVolume.cpp ImageInfo.cpp ImageProgress.cpp + InstallOptions.cpp InstallProgress.cpp Process.cpp ProcessCrashInformation.cpp @@ -33,6 +34,7 @@ set(HEADERS Helpers.h ImageInfo.h ImageProgress.h + InstallOptions.h InstallProgress.h Process.h ProcessCrashInformation.h diff --git a/src/windows/WslcSDK/winrt/InstallOptions.cpp b/src/windows/WslcSDK/winrt/InstallOptions.cpp new file mode 100644 index 000000000..a0fc7d2a8 --- /dev/null +++ b/src/windows/WslcSDK/winrt/InstallOptions.cpp @@ -0,0 +1,41 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + InstallOptions.cpp + +Abstract: + + This file contains the implementation of the WinRT wrapper for the WSLC SDK InstallOptions class. + +--*/ + +#include "precomp.h" +#include "InstallOptions.h" +#include "Microsoft.WSL.Containers.InstallOptions.g.cpp" + +namespace winrt::Microsoft::WSL::Containers::implementation { + +winrt::Windows::Foundation::Collections::IVectorView InstallOptions::Components() +{ + return m_components; +} + +void InstallOptions::Components(winrt::Windows::Foundation::Collections::IVectorView value) +{ + m_components = std::move(value); +} + +bool InstallOptions::Repair() +{ + return m_repair; +} + +void InstallOptions::Repair(bool value) +{ + m_repair = value; +} + +} // namespace winrt::Microsoft::WSL::Containers::implementation diff --git a/src/windows/WslcSDK/winrt/InstallOptions.h b/src/windows/WslcSDK/winrt/InstallOptions.h new file mode 100644 index 000000000..811d162c2 --- /dev/null +++ b/src/windows/WslcSDK/winrt/InstallOptions.h @@ -0,0 +1,41 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + InstallOptions.h + +Abstract: + + This file contains the definition of the WinRT wrapper for the WSLC SDK InstallOptions class. + +--*/ + +#pragma once +#include "Microsoft.WSL.Containers.InstallOptions.g.h" +#include "Helpers.h" + +namespace winrt::Microsoft::WSL::Containers::implementation { +struct InstallOptions : InstallOptionsT +{ + InstallOptions() = default; + + winrt::Windows::Foundation::Collections::IVectorView Components(); + void Components(winrt::Windows::Foundation::Collections::IVectorView value); + bool Repair(); + void Repair(bool value); + +private: + winrt::Windows::Foundation::Collections::IVectorView m_components = nullptr; + bool m_repair = false; +}; +} // namespace winrt::Microsoft::WSL::Containers::implementation + +namespace winrt::Microsoft::WSL::Containers::factory_implementation { +struct InstallOptions : InstallOptionsT +{ +}; +} // namespace winrt::Microsoft::WSL::Containers::factory_implementation + +DEFINE_TYPE_HELPERS(InstallOptions); diff --git a/src/windows/WslcSDK/winrt/WslcService.cpp b/src/windows/WslcSDK/winrt/WslcService.cpp index e94068874..220d66206 100644 --- a/src/windows/WslcSDK/winrt/WslcService.cpp +++ b/src/windows/WslcSDK/winrt/WslcService.cpp @@ -24,6 +24,60 @@ using namespace winrt::Windows::Foundation::Collections; namespace winrt::Microsoft::WSL::Containers::implementation { namespace { + WslcComponentFlags GetComponentsForInstall(const InstallOptions& options) + { + WslcComponentFlags result = WslcComponentFlags::WSLC_COMPONENT_FLAG_NONE; + bool shouldCheckMissingComponents = true; + + if (options) + { + auto components = options.Components(); + if (components) + { + shouldCheckMissingComponents = false; + + for (const auto& component : components) + { + switch (component) + { + case Component::VirtualMachinePlatform: + result |= WslcComponentFlags::WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM; + break; + case Component::WslPackage: + result |= WslcComponentFlags::WSLC_COMPONENT_FLAG_WSL_PACKAGE; + break; + case Component::SdkNeedsUpdate: + THROW_HR(WSLC_E_SDK_UPDATE_NEEDED); + default: + THROW_HR(E_INVALIDARG); + } + } + } + } + + if (shouldCheckMissingComponents) + { + winrt::check_hresult(WslcGetMissingComponents(&result)); + } + + return result; + } + + WslcInstallOptions GetOptionsForInstall(const InstallOptions& options) + { + WslcInstallOptions result = WslcInstallOptions::WSLC_INSTALL_OPTION_NONE; + + if (options) + { + if (options.Repair()) + { + result |= WslcInstallOptions::WSLC_INSTALL_OPTION_REPAIR; + } + } + + return result; + } + void CALLBACK InstallProgressCallback(WslcComponentFlags component, uint32_t progressSteps, uint32_t totalSteps, PVOID context) noexcept { try @@ -64,16 +118,22 @@ winrt::Microsoft::WSL::Containers::ServiceVersion WslcService::GetVersion() return winrt::make(version.major, version.minor, version.revision); } -IAsyncActionWithProgress WslcService::InstallWithDependenciesAsync() +IAsyncActionWithProgress WslcService::InstallWithDependenciesAsync( + winrt::Microsoft::WSL::Containers::InstallOptions options) { + auto components = GetComponentsForInstall(options); + auto wslcOptions = GetOptionsForInstall(options); + co_await winrt::resume_background(); auto context = ProgressCallbackHelper{co_await winrt::get_progress_token()}; - winrt::check_hresult(WslcInstallWithDependencies(InstallProgressCallback, &context)); + winrt::check_hresult(WslcInstallWithDependencies(components, wslcOptions, InstallProgressCallback, &context)); } -void WslcService::InstallWithDependencies() +void WslcService::InstallWithDependencies(winrt::Microsoft::WSL::Containers::InstallOptions options) { - winrt::check_hresult(WslcInstallWithDependencies(nullptr, nullptr)); + auto components = GetComponentsForInstall(options); + auto wslcOptions = GetOptionsForInstall(options); + winrt::check_hresult(WslcInstallWithDependencies(components, wslcOptions, nullptr, nullptr)); } } // namespace winrt::Microsoft::WSL::Containers::implementation diff --git a/src/windows/WslcSDK/winrt/WslcService.h b/src/windows/WslcSDK/winrt/WslcService.h index 39b4f52ca..d08373dc5 100644 --- a/src/windows/WslcSDK/winrt/WslcService.h +++ b/src/windows/WslcSDK/winrt/WslcService.h @@ -23,8 +23,9 @@ struct WslcService static winrt::Windows::Foundation::Collections::IVectorView GetMissingComponents(); static winrt::Microsoft::WSL::Containers::ServiceVersion GetVersion(); - static void InstallWithDependencies(); - static winrt::Windows::Foundation::IAsyncActionWithProgress InstallWithDependenciesAsync(); + static void InstallWithDependencies(winrt::Microsoft::WSL::Containers::InstallOptions options); + static winrt::Windows::Foundation::IAsyncActionWithProgress InstallWithDependenciesAsync( + winrt::Microsoft::WSL::Containers::InstallOptions options); }; } // namespace winrt::Microsoft::WSL::Containers::implementation namespace winrt::Microsoft::WSL::Containers::factory_implementation { diff --git a/src/windows/WslcSDK/winrt/wslcsdk.idl b/src/windows/WslcSDK/winrt/wslcsdk.idl index d6ae00297..521822a6d 100644 --- a/src/windows/WslcSDK/winrt/wslcsdk.idl +++ b/src/windows/WslcSDK/winrt/wslcsdk.idl @@ -249,6 +249,14 @@ namespace Microsoft.WSL.Containers UInt32 Revision { get; }; }; + runtimeclass InstallOptions + { + InstallOptions(); + + IVectorView Components; + Boolean Repair; + }; + runtimeclass InstallProgress { Component Component { get; }; @@ -260,8 +268,8 @@ namespace Microsoft.WSL.Containers { static IVectorView GetMissingComponents(); static ServiceVersion GetVersion(); - static void InstallWithDependencies(); - static Windows.Foundation.IAsyncActionWithProgress InstallWithDependenciesAsync(); + static void InstallWithDependencies(InstallOptions options); + static Windows.Foundation.IAsyncActionWithProgress InstallWithDependenciesAsync(InstallOptions options); }; enum VhdType diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index c106f8de9..9bc4bec44 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1664,33 +1664,43 @@ try } CATCH_RETURN(); -STDAPI WslcInstallWithDependencies(_In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context) +STDAPI WslcInstallWithDependencies( + _In_ WslcComponentFlags components, _In_ WslcInstallOptions options, _In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context) try { + // Reject unknown flag bits. + constexpr WslcComponentFlags c_knownComponents = + WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM | WSLC_COMPONENT_FLAG_WSL_PACKAGE | WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE; + RETURN_HR_IF(E_INVALIDARG, (components & ~c_knownComponents) != WSLC_COMPONENT_FLAG_NONE); + constexpr WslcInstallOptions c_knownOptions = WSLC_INSTALL_OPTION_REPAIR; + RETURN_HR_IF(E_INVALIDARG, (options & ~c_knownOptions) != WSLC_INSTALL_OPTION_NONE); + + // This API cannot update the SDK that the client is using. + RETURN_HR_IF(WSLC_E_SDK_UPDATE_NEEDED, WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE)); + HRESULT result = S_OK; - bool needsVirtualMachine = NeedsVirtualMachineServicesInstalled(); - auto runtimeResult = CreateSessionManagerRaw().second; - if (!needsVirtualMachine && SUCCEEDED(runtimeResult)) + if (components == WSLC_COMPONENT_FLAG_NONE) { return result; } - THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED); - - // Installing these components requires elevation. + // Installing components requires elevation. RETURN_HR_IF( HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED), !wsl::windows::common::security::IsTokenElevated(GetCurrentThreadEffectiveToken()) && !wsl::windows::common::security::IsTokenLocalSystem(nullptr)); - if (needsVirtualMachine) + bool isRepair = WI_IsFlagSet(options, WSLC_INSTALL_OPTION_REPAIR); + + if (WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM)) { if (progressCallback) { progressCallback(WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM, 0, 1, context); } + // No difference between install and repair, just let DISM attempt to enable the feature. auto exitCode = WslInstall::InstallOptionalComponent(WslInstall::c_optionalFeatureNameVmp, false); if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED) { @@ -1707,9 +1717,15 @@ try { progressCallback(WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM, 1, 1, context); } + + // If a reboot is required, we need the reboot to happen before attempting to install the WSL package. + if (result == HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED)) + { + return result; + } } - if (!SUCCEEDED(runtimeResult)) + if (WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_WSL_PACKAGE)) { std::function callback; if (progressCallback) @@ -1719,14 +1735,16 @@ try }; } - wsl::windows::common::WindowsUpdateContext wuContext; - wuContext.RunUpdateFlow(true, callback); + using WindowsUpdateContext = wsl::windows::common::WindowsUpdateContext; + WindowsUpdateContext wuContext; + wuContext.RunUpdateFlow( + isRepair ? WindowsUpdateContext::UpdateOptions::ResetProductRegistration : WindowsUpdateContext::UpdateOptions::EnsureProductRegistration, + callback); - // Because we do a forced install here, we expect an update. if (wuContext.GetUpdateCount() == 0) { // During the preview period, the package may not be published yet, so fall back to getting it from GH. - // When moving to GA, change this to a hard error to indicate a service configuration issue. + // When moving to GA, change this to an error like WSL_E_NO_UPDATE_AVAILABLE or similar. if (callback) { callback(0); diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 97dc2b09f..778e61b03 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -624,8 +624,18 @@ STDAPI WslcGetVersion(_Out_writes_(1) WslcVersion* version); typedef __callback void(CALLBACK* WslcInstallCallback)( _In_ WslcComponentFlags component, _In_ uint32_t progressSteps, _In_ uint32_t totalSteps, _In_opt_ PVOID context); +typedef enum WslcInstallOptions +{ + WSLC_INSTALL_OPTION_NONE = 0, + // Allows components to be reinstalled. + WSLC_INSTALL_OPTION_REPAIR = 1, +} WslcInstallOptions; + +DEFINE_ENUM_FLAG_OPERATORS(WslcInstallOptions); + // Callbacks will only be made for components that are actively installed by this call. -// That list can be acquired prior to this call with `WslcCanRun`. -STDAPI WslcInstallWithDependencies(_In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context); +// The list of required components can be acquired prior to this call with `WslcGetMissingComponents`. +STDAPI WslcInstallWithDependencies( + _In_ WslcComponentFlags components, _In_ WslcInstallOptions options, _In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context); EXTERN_C_END diff --git a/src/windows/common/WindowsUpdateIntegration.cpp b/src/windows/common/WindowsUpdateIntegration.cpp index 15e0453db..1afcfe852 100644 --- a/src/windows/common/WindowsUpdateIntegration.cpp +++ b/src/windows/common/WindowsUpdateIntegration.cpp @@ -126,18 +126,12 @@ namespace anon { }; } // namespace anon -WindowsUpdateContext::WindowsUpdateContext() : - WindowsUpdateContext(std::make_unique(), WslProductIdentifier()) +WindowsUpdateContext::WindowsUpdateContext() : WindowsUpdateContext(std::make_unique()) { } -WindowsUpdateContext::WindowsUpdateContext(std::wstring product) : - WindowsUpdateContext(std::make_unique(), std::move(product)) -{ -} - -WindowsUpdateContext::WindowsUpdateContext(std::unique_ptr factory, std::wstring product) : - m_factory(std::move(factory)), m_product(std::move(product)) +WindowsUpdateContext::WindowsUpdateContext(std::unique_ptr factory) : + m_factory(std::move(factory)), m_product(WslProductIdentifier()) { m_session = m_factory->CreateUpdateSession(); @@ -158,9 +152,12 @@ std::wstring WindowsUpdateContext::WslProductIdentifier() return STRING_TO_WIDE_STRING(DCAT_PRODUCT_NAME); } -void WindowsUpdateContext::EnsureProductRegistryEntry() const +void WindowsUpdateContext::EnsureProductRegistryEntry(bool reset) const { - wsl::windows::common::helpers::RegisterWithDcat(false); + if (reset || !wsl::windows::common::helpers::VersionRegisteredWithDcat()) + { + wsl::windows::common::helpers::RegisterWithDcat(false); + } } size_t WindowsUpdateContext::SearchForUpdates() @@ -336,14 +333,15 @@ void WindowsUpdateContext::InstallUpdates(const std::function& p THROW_IF_FAILED(installationHResult); } -void WindowsUpdateContext::RunUpdateFlow(bool forceInstall, const std::function& progress) +void WindowsUpdateContext::RunUpdateFlow(UpdateOptions options, const std::function& progress) { TraceLoggingWriteTagged( *m_activity, "RunUpdateFlow", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage), - TraceLoggingBool(forceInstall, "forceInstall")); + TraceLoggingBool(options == UpdateOptions::ResetProductRegistration, "forceInstall"), + TraceLoggingBool(options == UpdateOptions::EnsureProductRegistration, "ensureInstall")); static_assert( DownloadProgressPercent + InstallProgressPercent == 100, "Download and Install progress values must add up to 100."); @@ -353,9 +351,9 @@ void WindowsUpdateContext::RunUpdateFlow(bool forceInstall, const std::function< progress(0); } - if (forceInstall) + if (options != UpdateOptions::None) { - EnsureProductRegistryEntry(); + EnsureProductRegistryEntry(options == UpdateOptions::ResetProductRegistration); } size_t updateCount = SearchForUpdates(); diff --git a/src/windows/common/WindowsUpdateIntegration.h b/src/windows/common/WindowsUpdateIntegration.h index 768d5d0be..5ec084e93 100644 --- a/src/windows/common/WindowsUpdateIntegration.h +++ b/src/windows/common/WindowsUpdateIntegration.h @@ -31,11 +31,8 @@ struct WindowsUpdateContext // Create a context using the default class factory and WSL product. WindowsUpdateContext(); - // Create a context using the default class factory. - WindowsUpdateContext(std::wstring product); - // Create a context using the provided class factory. - WindowsUpdateContext(std::unique_ptr factory, std::wstring product); + WindowsUpdateContext(std::unique_ptr factory); NON_COPYABLE(WindowsUpdateContext); DEFAULT_MOVABLE(WindowsUpdateContext); @@ -45,7 +42,8 @@ struct WindowsUpdateContext // Ensures that the product is registered in with the Windows Update system. // This is required to use the system for initial installs. - void EnsureProductRegistryEntry() const; + // When `reset` is true, always sets the entry to a value that will result in an install. + void EnsureProductRegistryEntry(bool reset = false) const; // Searches for updates for the product. // Returns the number of updates found. @@ -65,11 +63,17 @@ struct WindowsUpdateContext static constexpr uint32_t DownloadProgressPercent = 70; static constexpr uint32_t InstallProgressPercent = 30; - // Performs a complete update flow. This is a convenience method to remove the need to call and coordinate the individual actions. - // When `forceInstall` is true, `EnsureProductRegistryEntry` is called. - // Calls the progress callback, if provided, with the overall update progress estimate. + enum class UpdateOptions + { + None, + EnsureProductRegistration, + ResetProductRegistration, + }; + + // Performs a complete update flow. This is a convenience method to remove the need to call and coordinate the individual + // actions. Calls the progress callback, if provided, with the overall update progress estimate. // Download and install phases are split according to the values defined above. - void RunUpdateFlow(bool forceInstall = false, const std::function& progress = {}); + void RunUpdateFlow(UpdateOptions options = UpdateOptions::EnsureProductRegistration, const std::function& progress = {}); private: using ActivityType = TraceLoggingActivity; diff --git a/src/windows/common/helpers.cpp b/src/windows/common/helpers.cpp index f8f3cdaa4..c4d1ee98c 100644 --- a/src/windows/common/helpers.cpp +++ b/src/windows/common/helpers.cpp @@ -38,6 +38,7 @@ using wsl::windows::common::helpers::LaunchWslRelayFlags; constexpr auto c_WslSupportInterfaceKey = L"Software\\Classes\\Interface\\{46f3c96d-ffa3-42f0-b052-52f5e7ecbb08}"; constexpr auto c_WslSupportInterfaceName = L"IWslSupport"; +constexpr auto c_DcatRegistryVersionValueName = L"Version"; namespace { @@ -744,6 +745,21 @@ bool wsl::windows::common::helpers::TryAttachConsole() return ReopenStdHandles(); } +std::optional wsl::windows::common::helpers::VersionRegisteredWithDcat() +{ + try + { + auto [dcatKey, result] = wsl::windows::common::registry::OpenKeyNoThrow(HKEY_LOCAL_MACHINE, TEXT(DCAT_REGISTRATION_KEY), KEY_READ); + if (SUCCEEDED(result)) + { + return wsl::windows::common::registry::ReadOptionalString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName); + } + } + CATCH_LOG() + + return {}; +} + void wsl::windows::common::helpers::RegisterWithDcat(_In_ bool IncludeVersionNumber) try { @@ -758,7 +774,7 @@ try } wil::unique_hkey dcatKey = wsl::windows::common::registry::CreateKey(HKEY_LOCAL_MACHINE, TEXT(DCAT_REGISTRATION_KEY), KEY_SET_VALUE); - wsl::windows::common::registry::WriteString(dcatKey.get(), nullptr, L"Version", registeredVersion.c_str()); + wsl::windows::common::registry::WriteString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName, registeredVersion.c_str()); } CATCH_LOG() diff --git a/src/windows/common/helpers.hpp b/src/windows/common/helpers.hpp index e83fc1832..dcf81f78f 100644 --- a/src/windows/common/helpers.hpp +++ b/src/windows/common/helpers.hpp @@ -203,6 +203,8 @@ void SetHandleInheritable(_In_ HANDLE Handle, _In_ bool Inheritable = true); bool TryAttachConsole(); +std::optional VersionRegisteredWithDcat(); + void RegisterWithDcat(_In_ bool IncludeVersionNumber = true); void AppendCommonKernelCommandLine(_Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder, _In_ ULONG64 swiotlbSizeBytes); diff --git a/test/windows/Common.cpp b/test/windows/Common.cpp index cea54324e..21c579996 100644 --- a/test/windows/Common.cpp +++ b/test/windows/Common.cpp @@ -2601,6 +2601,26 @@ while ($true) m_process = LxsstuStartProcess(cmd.data()); } +UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, UINT StatusCode) +{ + auto cmd = std::format( + LR"(Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command " +$ErrorActionPreference = 'Stop' +$server = New-Object System.Net.HttpListener +$server.Prefixes.Add('{}') +$server.Start() +while ($true) +{{ + $context = $server.GetContext() + $context.Response.StatusCode = {} + $context.Response.close() +}}")", + Endpoint, + StatusCode); + + m_process = LxsstuStartProcess(cmd.data()); +} + UniqueWebServer::~UniqueWebServer() { if (!TerminateProcess(m_process.get(), 0)) diff --git a/test/windows/Common.h b/test/windows/Common.h index 86c27e667..f70f812aa 100644 --- a/test/windows/Common.h +++ b/test/windows/Common.h @@ -358,6 +358,8 @@ class UniqueWebServer public: UniqueWebServer(LPCWSTR Endpoint, LPCWSTR ResponseContent); UniqueWebServer(LPCWSTR Endpoint, const std::filesystem::path& path); + // Starts a server that returns the given HTTP status code with an empty body for every request. + UniqueWebServer(LPCWSTR Endpoint, UINT StatusCode); ~UniqueWebServer(); UniqueWebServer(const UniqueWebServer&) = delete; UniqueWebServer(UniqueWebServer&&) = delete; diff --git a/test/windows/WindowsUpdateTests.cpp b/test/windows/WindowsUpdateTests.cpp index 13d1e55eb..ee3975bf7 100644 --- a/test/windows/WindowsUpdateTests.cpp +++ b/test/windows/WindowsUpdateTests.cpp @@ -830,7 +830,7 @@ class WindowsUpdateTests TEST_METHOD(SearchForUpdates_NoUpdates) { auto factory = std::make_unique(); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); VERIFY_ARE_EQUAL(0u, ctx.SearchForUpdates()); VERIFY_ARE_EQUAL(0u, ctx.GetUpdateCount()); @@ -845,7 +845,7 @@ class WindowsUpdateTests AddMockUpdate(col, VARIANT_FALSE); AddMockUpdate(col, VARIANT_FALSE); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); VERIFY_ARE_EQUAL(3u, ctx.SearchForUpdates()); VERIFY_ARE_EQUAL(3u, ctx.GetUpdateCount()); @@ -858,7 +858,7 @@ class WindowsUpdateTests fp->session->searcher->searchResult->resultCode = OperationResultCode::orcSucceededWithErrors; AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); // orcSucceededWithErrors must succeed — the update count is still returned. VERIFY_ARE_EQUAL(1u, ctx.SearchForUpdates()); @@ -869,7 +869,7 @@ class WindowsUpdateTests auto factory = std::make_unique(); factory->session->searcher->searchResult->resultCode = OperationResultCode::orcFailed; - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); VERIFY_ARE_EQUAL(WSLC_E_WU_SEARCH_FAILED, CaptureHResult([&] { ctx.SearchForUpdates(); })); } @@ -886,7 +886,7 @@ class WindowsUpdateTests AddMockUpdate(col, VARIANT_TRUE); AddMockUpdate(col, VARIANT_TRUE); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); ctx.SearchForUpdates(); std::vector progressCalls; @@ -909,7 +909,7 @@ class WindowsUpdateTests AddMockUpdate(col, VARIANT_FALSE); // needs download AddMockUpdate(col, VARIANT_FALSE); // needs download - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); ctx.SearchForUpdates(); ctx.DownloadUpdates(); @@ -924,7 +924,7 @@ class WindowsUpdateTests AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE); fp->session->downloader->downloadResult->downloadHResult = E_FAIL; - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); ctx.SearchForUpdates(); VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.DownloadUpdates(); })); @@ -940,7 +940,7 @@ class WindowsUpdateTests auto* fp = factory.get(); AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_TRUE); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); ctx.SearchForUpdates(); // Should not throw. @@ -957,7 +957,7 @@ class WindowsUpdateTests AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_TRUE); fp->session->installer->installResult->installHResult = E_ACCESSDENIED; - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); ctx.SearchForUpdates(); VERIFY_ARE_EQUAL(E_ACCESSDENIED, CaptureHResult([&] { ctx.InstallUpdates(); })); @@ -972,10 +972,10 @@ class WindowsUpdateTests auto factory = std::make_unique(); auto* fp = factory.get(); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); std::vector progressCalls; - ctx.RunUpdateFlow(false, [&](uint32_t p) { progressCalls.push_back(p); }); + ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None, [&](uint32_t p) { progressCalls.push_back(p); }); // progress(0) at the start, progress(100) because there are no updates. VERIFY_ARE_EQUAL(2u, progressCalls.size()); @@ -993,10 +993,10 @@ class WindowsUpdateTests auto* fp = factory.get(); AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); std::vector progressCalls; - ctx.RunUpdateFlow(false, [&](uint32_t p) { progressCalls.push_back(p); }); + ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None, [&](uint32_t p) { progressCalls.push_back(p); }); // progress(0) is emitted at the start. VERIFY_IS_FALSE(progressCalls.empty()); @@ -1027,9 +1027,9 @@ class WindowsUpdateTests AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE); fp->session->downloader->downloadResult->downloadHResult = E_FAIL; - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); - VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.RunUpdateFlow(); })); + VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None); })); // Install should not have been called after download failure. VERIFY_IS_FALSE(fp->session->installer->beginInstallCalled); @@ -1041,9 +1041,9 @@ class WindowsUpdateTests auto factory = std::make_unique(); AddMockUpdate(factory->session->searcher->searchResult->updates.get(), VARIANT_TRUE); - WindowsUpdateContext ctx(std::move(factory), L"TestProduct"); + WindowsUpdateContext ctx(std::move(factory)); // Should complete without crashing even with no progress callback. - ctx.RunUpdateFlow(); + ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None); } }; diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index d314462bb..072b1ae0a 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1513,6 +1513,60 @@ class WslcSdkTests VERIFY_ARE_EQUAL(missing, WSLC_COMPONENT_FLAG_NONE); } + WSLC_TEST_METHOD(InstallWithDependencies_NoComponents_Succeeds) + { + // Passing WSLC_COMPONENT_FLAG_NONE must return S_OK immediately without requiring elevation. + VERIFY_SUCCEEDED(WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_NONE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr)); + } + + WSLC_TEST_METHOD(InstallWithDependencies_SdkNeedsUpdate_ReturnsError) + { + // Passing SDK_NEEDS_UPDATE must always return WSLC_E_SDK_UPDATE_NEEDED — the caller must update their SDK. + VERIFY_ARE_EQUAL( + WSLC_E_SDK_UPDATE_NEEDED, + WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr)); + } + + WSLC_TEST_METHOD(InstallWithDependencies_WslPackage_GhFallback404) + { + // Without repair semantics, DCAT uses EnsureProductRegistration so it should find no update + // (the product is already registered at the current version). The code then falls back to the + // GitHub release endpoint. This test intercepts that fallback: the fake API server returns a + // release whose asset URL points at a second local server that always responds with HTTP 404, + // so the download fails and WslcInstallWithDependencies surfaces an error HRESULT. + constexpr auto apiEndpoint = L"http://127.0.0.1:12345/"; + constexpr auto assetEndpoint = L"http://127.0.0.1:12346/"; + + RegistryKeyChange urlOverride( + HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss", + wsl::windows::common::wslutil::c_githubUrlOverrideRegistryValue, + apiEndpoint); + + // Version 1.0.0 is below the current package version, so without repair=true the version + // check in UpdatePackageImpl would short-circuit and return early. Using a sub-2.0 version + // here confirms that the repair flag (always set in the GH fallback) is what drives the + // download attempt rather than the version being newer than the installed one. + constexpr auto GitHubApiResponse = + LR"([{ + \"name\": \"1.0.0\", + \"created_at\": \"2023-06-14T16:56:30Z\", + \"assets\": [ + { + \"url\": \"http://127.0.0.1:12346/fake.msixbundle\", + \"id\": 1, + \"name\": \"Microsoft.WSL_1.0.0.0_x64_ARM64.msixbundle\" + } + ] + }])"; + + UniqueWebServer apiServer(apiEndpoint, GitHubApiResponse); + UniqueWebServer assetServer(assetEndpoint, 404u); + + VERIFY_ARE_EQUAL( + HTTP_E_STATUS_NOT_FOUND, WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_WSL_PACKAGE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr)); + } + // ----------------------------------------------------------------------- // WslcSetProcessSettingsCallbacks tests // ----------------------------------------------------------------------- diff --git a/test/windows/WslcSdkWinRTTests.cpp b/test/windows/WslcSdkWinRTTests.cpp index 577ed0d4d..f13b60e0f 100644 --- a/test/windows/WslcSdkWinRTTests.cpp +++ b/test/windows/WslcSdkWinRTTests.cpp @@ -1164,10 +1164,46 @@ class WslcSdkWinRtTests WSLC_TEST_METHOD(InstallWithDependencies) { - WSLCSDK::WslcService::InstallWithDependenciesAsync().get(); + // Pass null options to auto-detect and install any missing components (same behavior as the old no-arg call). + WSLCSDK::WslcService::InstallWithDependenciesAsync(nullptr).get(); VERIFY_ARE_EQUAL(WSLCSDK::WslcService::GetMissingComponents().Size(), 0u); } + WSLC_TEST_METHOD(InstallOptions_DefaultValues) + { + // Default-constructed InstallOptions must have null Components and Repair=false. + auto options = WSLCSDK::InstallOptions(); + VERIFY_IS_NULL(options.Components()); + VERIFY_IS_FALSE(options.Repair()); + } + + WSLC_TEST_METHOD(InstallOptions_SetRepair) + { + auto options = WSLCSDK::InstallOptions(); + options.Repair(true); + VERIFY_IS_TRUE(options.Repair()); + options.Repair(false); + VERIFY_IS_FALSE(options.Repair()); + } + + WSLC_TEST_METHOD(InstallOptions_SetComponents) + { + auto options = WSLCSDK::InstallOptions(); + auto components = winrt::single_threaded_vector({WSLCSDK::Component::WslPackage}); + options.Components(components.GetView()); + VERIFY_ARE_EQUAL(1u, options.Components().Size()); + VERIFY_ARE_EQUAL(WSLCSDK::Component::WslPackage, options.Components().GetAt(0)); + } + + WSLC_TEST_METHOD(InstallWithDependencies_SdkNeedsUpdate_Throws) + { + // Passing SdkNeedsUpdate in the component list must throw WSLC_E_SDK_UPDATE_NEEDED. + auto options = WSLCSDK::InstallOptions(); + auto components = winrt::single_threaded_vector({WSLCSDK::Component::SdkNeedsUpdate}); + options.Components(components.GetView()); + VERIFY_THROWS_HR(WSLCSDK::WslcService::InstallWithDependenciesAsync(options).get(), WSLC_E_SDK_UPDATE_NEEDED); + } + // ----------------------------------------------------------------------- // Process IO event tests // -----------------------------------------------------------------------