From 472eba4b517069fb16d59c4dc343b8a3296491fa Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 25 Jun 2026 14:32:44 -0700 Subject: [PATCH 1/8] commit for merge --- src/windows/WslcSDK/wslcsdk.cpp | 36 ++++++++++++++++++++------------- src/windows/WslcSDK/wslcsdk.h | 14 +++++++++++-- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 6bff53674..6bb8e4448 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1664,33 +1664,36 @@ 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 -{ +{ + // This API cannot update the SDK that the client is using. + RETURN_HR_IF(E_INVALIDARG, 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)); + + bool isRepair = WI_IsFlagSet(options, WSLC_INSTALL_OPTION_REPAIR); - if (needsVirtualMachine) + 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) { @@ -1706,10 +1709,16 @@ try if (progressCallback) { 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) @@ -1720,13 +1729,12 @@ try } wsl::windows::common::WindowsUpdateContext wuContext; - wuContext.RunUpdateFlow(true, callback); + wuContext.RunUpdateFlow(isRepair, 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 From b07308917195492c4eeae552ea35fb0535e04702 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 25 Jun 2026 14:33:29 -0700 Subject: [PATCH 2/8] .\FormatSource.ps1 --- src/windows/WslcSDK/wslcsdk.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 6bb8e4448..4811f0b77 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1667,9 +1667,9 @@ CATCH_RETURN(); STDAPI WslcInstallWithDependencies( _In_ WslcComponentFlags components, _In_ WslcInstallOptions options, _In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context) try -{ +{ // This API cannot update the SDK that the client is using. - RETURN_HR_IF(E_INVALIDARG, WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE)); + RETURN_HR_IF(E_INVALIDARG, WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE)); HRESULT result = S_OK; @@ -1683,8 +1683,8 @@ try HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED), !wsl::windows::common::security::IsTokenElevated(GetCurrentThreadEffectiveToken()) && !wsl::windows::common::security::IsTokenLocalSystem(nullptr)); - - bool isRepair = WI_IsFlagSet(options, WSLC_INSTALL_OPTION_REPAIR); + + bool isRepair = WI_IsFlagSet(options, WSLC_INSTALL_OPTION_REPAIR); if (WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM)) { @@ -1692,7 +1692,7 @@ try { 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) @@ -1709,10 +1709,10 @@ try if (progressCallback) { 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)) + } + + // 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; } From 484712f980ba161bddd2c4a988b387b98a463803 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 25 Jun 2026 18:33:28 -0700 Subject: [PATCH 3/8] C changes and beginning of midl changes --- src/windows/WslcSDK/winrt/CMakeLists.txt | 2 ++ src/windows/WslcSDK/winrt/wslcsdk.idl | 10 +++++-- src/windows/WslcSDK/wslcsdk.cpp | 7 ++--- .../common/WindowsUpdateIntegration.cpp | 27 +++++++++---------- src/windows/common/WindowsUpdateIntegration.h | 20 ++++++++------ src/windows/common/helpers.cpp | 15 ++++++++++- src/windows/common/helpers.hpp | 4 ++- 7 files changed, 56 insertions(+), 29 deletions(-) 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/wslcsdk.idl b/src/windows/WslcSDK/winrt/wslcsdk.idl index e2d083cc6..a87063fe8 100644 --- a/src/windows/WslcSDK/winrt/wslcsdk.idl +++ b/src/windows/WslcSDK/winrt/wslcsdk.idl @@ -249,6 +249,12 @@ namespace Microsoft.WSL.Containers UInt32 Revision { get; }; }; + runtimeclass InstallOptions + { + IVectorView Components; + Boolean Repair; + }; + runtimeclass InstallProgress { Component Component { get; }; @@ -260,8 +266,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 72bab9cd1..6169ee8d5 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1727,9 +1727,10 @@ try progressCallback(WSLC_COMPONENT_FLAG_WSL_PACKAGE, progress, 100, context); }; } - - wsl::windows::common::WindowsUpdateContext wuContext; - wuContext.RunUpdateFlow(isRepair, callback); + + using WindowsUpdateContext = wsl::windows::common::WindowsUpdateContext; + WindowsUpdateContext wuContext; + wuContext.RunUpdateFlow(isRepair ? WindowsUpdateContext::UpdateOptions::ResetProductRegistration : WindowsUpdateContext::UpdateOptions::EnsureProductRegistration, callback); if (wuContext.GetUpdateCount() == 0) { diff --git a/src/windows/common/WindowsUpdateIntegration.cpp b/src/windows/common/WindowsUpdateIntegration.cpp index 15e0453db..05e09b41f 100644 --- a/src/windows/common/WindowsUpdateIntegration.cpp +++ b/src/windows/common/WindowsUpdateIntegration.cpp @@ -127,17 +127,12 @@ namespace anon { } // namespace anon WindowsUpdateContext::WindowsUpdateContext() : - WindowsUpdateContext(std::make_unique(), WslProductIdentifier()) + 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 +153,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 +334,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 +352,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..3a607b98c 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); @@ -44,8 +41,9 @@ struct WindowsUpdateContext static std::wstring WslProductIdentifier(); // 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; + // This is required to use the system for initial installs. + // 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. @@ -64,12 +62,18 @@ struct WindowsUpdateContext static constexpr uint32_t DownloadProgressPercent = 70; static constexpr uint32_t InstallProgressPercent = 30; + + 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. - // When `forceInstall` is true, `EnsureProductRegistryEntry` is called. // 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..5d651e6c6 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 { @@ -742,7 +743,19 @@ 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); + } + return {}; } +CATCH_LOG() void wsl::windows::common::helpers::RegisterWithDcat(_In_ bool IncludeVersionNumber) try @@ -758,7 +771,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..2c062a172 100644 --- a/src/windows/common/helpers.hpp +++ b/src/windows/common/helpers.hpp @@ -201,7 +201,9 @@ DWORD RunProcess(_Inout_ std::wstring& CommandLine); void SetHandleInheritable(_In_ HANDLE Handle, _In_ bool Inheritable = true); -bool TryAttachConsole(); +bool TryAttachConsole(); + +std::optional VersionRegisteredWithDcat(); void RegisterWithDcat(_In_ bool IncludeVersionNumber = true); From 6e29da6ce26bf0b1694dc1f0cc8d664dab1dbd8e Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 25 Jun 2026 18:35:39 -0700 Subject: [PATCH 4/8] format --- src/windows/WslcSDK/wslcsdk.cpp | 6 +++-- .../common/WindowsUpdateIntegration.cpp | 5 ++--- src/windows/common/WindowsUpdateIntegration.h | 22 +++++++++---------- src/windows/common/helpers.cpp | 6 ++--- src/windows/common/helpers.hpp | 4 ++-- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 6169ee8d5..b3600bcf8 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1727,10 +1727,12 @@ try progressCallback(WSLC_COMPONENT_FLAG_WSL_PACKAGE, progress, 100, context); }; } - + using WindowsUpdateContext = wsl::windows::common::WindowsUpdateContext; WindowsUpdateContext wuContext; - wuContext.RunUpdateFlow(isRepair ? WindowsUpdateContext::UpdateOptions::ResetProductRegistration : WindowsUpdateContext::UpdateOptions::EnsureProductRegistration, callback); + wuContext.RunUpdateFlow( + isRepair ? WindowsUpdateContext::UpdateOptions::ResetProductRegistration : WindowsUpdateContext::UpdateOptions::EnsureProductRegistration, + callback); if (wuContext.GetUpdateCount() == 0) { diff --git a/src/windows/common/WindowsUpdateIntegration.cpp b/src/windows/common/WindowsUpdateIntegration.cpp index 05e09b41f..1afcfe852 100644 --- a/src/windows/common/WindowsUpdateIntegration.cpp +++ b/src/windows/common/WindowsUpdateIntegration.cpp @@ -126,8 +126,7 @@ namespace anon { }; } // namespace anon -WindowsUpdateContext::WindowsUpdateContext() : - WindowsUpdateContext(std::make_unique()) +WindowsUpdateContext::WindowsUpdateContext() : WindowsUpdateContext(std::make_unique()) { } @@ -155,7 +154,7 @@ std::wstring WindowsUpdateContext::WslProductIdentifier() void WindowsUpdateContext::EnsureProductRegistryEntry(bool reset) const { - if (reset || !wsl::windows::common::helpers::VersionRegisteredWithDcat()) + if (reset || !wsl::windows::common::helpers::VersionRegisteredWithDcat()) { wsl::windows::common::helpers::RegisterWithDcat(false); } diff --git a/src/windows/common/WindowsUpdateIntegration.h b/src/windows/common/WindowsUpdateIntegration.h index 3a607b98c..5ec084e93 100644 --- a/src/windows/common/WindowsUpdateIntegration.h +++ b/src/windows/common/WindowsUpdateIntegration.h @@ -41,7 +41,7 @@ struct WindowsUpdateContext static std::wstring WslProductIdentifier(); // Ensures that the product is registered in with the Windows Update system. - // This is required to use the system for initial installs. + // This is required to use the system for initial installs. // When `reset` is true, always sets the entry to a value that will result in an install. void EnsureProductRegistryEntry(bool reset = false) const; @@ -62,16 +62,16 @@ struct WindowsUpdateContext static constexpr uint32_t DownloadProgressPercent = 70; static constexpr uint32_t InstallProgressPercent = 30; - - 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. + + 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(UpdateOptions options = UpdateOptions::EnsureProductRegistration, const std::function& progress = {}); diff --git a/src/windows/common/helpers.cpp b/src/windows/common/helpers.cpp index 5d651e6c6..9f878a342 100644 --- a/src/windows/common/helpers.cpp +++ b/src/windows/common/helpers.cpp @@ -743,13 +743,13 @@ 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)) + if (SUCCEEDED(result)) { return wsl::windows::common::registry::ReadOptionalString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName); } diff --git a/src/windows/common/helpers.hpp b/src/windows/common/helpers.hpp index 2c062a172..dcf81f78f 100644 --- a/src/windows/common/helpers.hpp +++ b/src/windows/common/helpers.hpp @@ -201,8 +201,8 @@ DWORD RunProcess(_Inout_ std::wstring& CommandLine); void SetHandleInheritable(_In_ HANDLE Handle, _In_ bool Inheritable = true); -bool TryAttachConsole(); - +bool TryAttachConsole(); + std::optional VersionRegisteredWithDcat(); void RegisterWithDcat(_In_ bool IncludeVersionNumber = true); From 0f0d5585ced08be1d2e4b8c6962c98277c75073c Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Mon, 29 Jun 2026 16:42:45 -0700 Subject: [PATCH 5/8] winrt changes --- src/windows/WslcSDK/winrt/InstallOptions.cpp | 41 ++++++++++++ src/windows/WslcSDK/winrt/InstallOptions.h | 41 ++++++++++++ src/windows/WslcSDK/winrt/WslcService.cpp | 68 ++++++++++++++++++-- src/windows/WslcSDK/winrt/WslcService.h | 5 +- src/windows/WslcSDK/winrt/wslcsdk.idl | 2 + src/windows/WslcSDK/wslcsdk.cpp | 2 +- src/windows/common/helpers.cpp | 13 ++-- 7 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 src/windows/WslcSDK/winrt/InstallOptions.cpp create mode 100644 src/windows/WslcSDK/winrt/InstallOptions.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 a87063fe8..fa1963e9c 100644 --- a/src/windows/WslcSDK/winrt/wslcsdk.idl +++ b/src/windows/WslcSDK/winrt/wslcsdk.idl @@ -251,6 +251,8 @@ namespace Microsoft.WSL.Containers runtimeclass InstallOptions { + InstallOptions(); + IVectorView Components; Boolean Repair; }; diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index b3600bcf8..7af7fa151 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1669,7 +1669,7 @@ STDAPI WslcInstallWithDependencies( try { // This API cannot update the SDK that the client is using. - RETURN_HR_IF(E_INVALIDARG, WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE)); + RETURN_HR_IF(WSLC_E_SDK_UPDATE_NEEDED, WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE)); HRESULT result = S_OK; diff --git a/src/windows/common/helpers.cpp b/src/windows/common/helpers.cpp index 9f878a342..c4d1ee98c 100644 --- a/src/windows/common/helpers.cpp +++ b/src/windows/common/helpers.cpp @@ -746,16 +746,19 @@ bool wsl::windows::common::helpers::TryAttachConsole() } 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)) + try { - return wsl::windows::common::registry::ReadOptionalString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName); + 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 {}; } -CATCH_LOG() void wsl::windows::common::helpers::RegisterWithDcat(_In_ bool IncludeVersionNumber) try From a1bd8af4254e7f754638eccc60f901909d5abe8b Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Mon, 29 Jun 2026 17:02:01 -0700 Subject: [PATCH 6/8] update tests --- test/windows/WindowsUpdateTests.cpp | 34 +++++++++++++------------- test/windows/WslcSdkTests.cpp | 14 +++++++++++ test/windows/WslcSdkWinRTTests.cpp | 38 ++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 18 deletions(-) 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..ddeb65674 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1513,6 +1513,20 @@ 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)); + } + // ----------------------------------------------------------------------- // 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 // ----------------------------------------------------------------------- From bc878572e3e782fa8d79bf31d4373a71e6ad4891 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Mon, 29 Jun 2026 17:27:39 -0700 Subject: [PATCH 7/8] add no-update install test --- test/windows/Common.cpp | 20 ++++++++++++++++++ test/windows/Common.h | 2 ++ test/windows/WslcSdkTests.cpp | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) 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/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index ddeb65674..0fd0c7aad 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1527,6 +1527,45 @@ class WslcSdkTests 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 // ----------------------------------------------------------------------- From 41d5894fd1dcbe2f625a4b7b0282f7c8230bc61f Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Wed, 1 Jul 2026 10:55:51 -0700 Subject: [PATCH 8/8] PR feedback --- src/windows/WslcSDK/wslcsdk.cpp | 7 +++++++ test/windows/WslcSdkTests.cpp | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 7af7fa151..9bc4bec44 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -1668,6 +1668,13 @@ 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)); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 0fd0c7aad..072b1ae0a 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1534,7 +1534,7 @@ class WslcSdkTests // 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 apiEndpoint = L"http://127.0.0.1:12345/"; constexpr auto assetEndpoint = L"http://127.0.0.1:12346/"; RegistryKeyChange urlOverride( @@ -1563,7 +1563,8 @@ class WslcSdkTests 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)); + VERIFY_ARE_EQUAL( + HTTP_E_STATUS_NOT_FOUND, WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_WSL_PACKAGE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr)); } // -----------------------------------------------------------------------